mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 22:29:48 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7f623158c | |||
| 2c800c670a | |||
| 5b20f9a746 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.107.0"
|
"version": "1.108.0"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ description: Universal release workflow. Auto-detects version files and changelo
|
|||||||
|
|
||||||
Universal release workflow supporting any project type with multi-language changelog.
|
Universal release workflow supporting any project type with multi-language changelog.
|
||||||
|
|
||||||
|
## User Input Tools
|
||||||
|
|
||||||
|
When this skill prompts the user, follow this tool-selection rule (priority order):
|
||||||
|
|
||||||
|
1. **Prefer built-in user-input tools** exposed by the current agent runtime — e.g., `AskUserQuestion`, `request_user_input`, `clarify`, `ask_user`, or any equivalent.
|
||||||
|
2. **Fallback**: if no such tool exists, emit a numbered plain-text message and ask the user to reply with the chosen number/answer for each question.
|
||||||
|
3. **Batching**: if the tool supports multiple questions per call, combine all applicable questions into a single call; if only single-question, ask them one at a time in priority order.
|
||||||
|
|
||||||
|
Concrete `AskUserQuestion` references below are examples — substitute the local equivalent in other runtimes.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Just run `/release-skills` - auto-detects your project configuration.
|
Just run `/release-skills` - auto-detects your project configuration.
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@ set -eu
|
|||||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
node scripts/sync-shared-skill-packages.mjs --repo-root "$REPO_ROOT" --enforce-clean
|
node scripts/verify-shared-package-deps.mjs --repo-root "$REPO_ROOT"
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ out
|
|||||||
.nuxt
|
.nuxt
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
# Shared package builds are published to npm.
|
||||||
|
!packages/*/dist/
|
||||||
|
!packages/*/dist/**
|
||||||
|
|
||||||
# Gatsby files
|
# Gatsby files
|
||||||
.cache/
|
.cache/
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|||||||
+1
-1
@@ -2,5 +2,5 @@ release:
|
|||||||
target_globs:
|
target_globs:
|
||||||
- skills/*
|
- skills/*
|
||||||
hooks:
|
hooks:
|
||||||
prepare_artifact: node scripts/sync-shared-skill-packages.mjs --repo-root "{project_root}" --target "{target}"
|
prepare_artifact: node scripts/verify-shared-package-deps.mjs --repo-root "{project_root}" --target "{target}"
|
||||||
publish_artifact: node scripts/publish-skill.mjs --skill-dir "{target}" --version "{version}" --changelog-file "{release_notes_file}" --dry-run "{dry_run}"
|
publish_artifact: node scripts/publish-skill.mjs --skill-dir "{target}" --version "{version}" --changelog-file "{release_notes_file}" --dry-run "{dry_run}"
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.108.0 - 2026-04-19
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
- Refactor skills into focused reference files for better maintainability
|
||||||
|
- Use npm packages for shared skill code across skills
|
||||||
|
|
||||||
## 1.107.0 - 2026-04-15
|
## 1.107.0 - 2026-04-15
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.108.0 - 2026-04-19
|
||||||
|
|
||||||
|
### 重构
|
||||||
|
- 将技能文档拆分为聚焦的参考文件,提升可维护性
|
||||||
|
- 将多技能共享代码迁移至 npm 包管理
|
||||||
|
|
||||||
## 1.107.0 - 2026-04-15
|
## 1.107.0 - 2026-04-15
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -46,16 +46,32 @@ Execute: `${BUN_X} skills/<skill>/scripts/main.ts [options]`
|
|||||||
| Rule | Description |
|
| Rule | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| **Load project skills first** | Project skills override system/user-level skills with same name |
|
| **Load project skills first** | Project skills override system/user-level skills with same name |
|
||||||
| **Default image generation** | Use `skills/baoyu-imagine/SKILL.md` unless user specifies otherwise |
|
| **Default image generation** | Use whatever image backend is available in the current runtime; if multiple are available, ask the user which to use. See `## Image Generation Tools` below. |
|
||||||
|
|
||||||
Priority: project `skills/` → `$HOME/.baoyu-skills/` → system-level.
|
Priority: project `skills/` → `$HOME/.baoyu-skills/` → system-level.
|
||||||
|
|
||||||
|
## Skill Self-Containment
|
||||||
|
|
||||||
|
Each skill under `skills/` (and `.claude/skills/`) is distributed and consumed independently — the folder may be extracted, copied into another project, or loaded without the rest of this repo. Therefore:
|
||||||
|
|
||||||
|
- **Never link from `SKILL.md` or its `references/` to files outside the skill's own directory.** This includes `docs/`, sibling skills, and the repo root. Relative paths like `../../docs/foo.md` break when the skill is used standalone.
|
||||||
|
- **Inline any shared convention** (e.g., user-input rules, image-generation backend selection) directly in the skill rather than referencing an out-of-skill doc.
|
||||||
|
- Shared docs under `docs/` exist for **repo-author guidance only** — they may be referenced from `CLAUDE.md` and `docs/creating-skills.md`, but NOT from any `SKILL.md`. This applies to `docs/user-input-tools.md`, `docs/image-generation-tools.md`, `docs/image-generation.md`, and any other `docs/` file.
|
||||||
|
|
||||||
|
## User Input Tools
|
||||||
|
|
||||||
|
Skills that prompt users for choices MUST declare the tool-selection convention **inline** in exactly one place per `SKILL.md` — a `## User Input Tools` section near the top. Do NOT link out to [docs/user-input-tools.md](docs/user-input-tools.md); that doc is the author-side canonical source — copy its body into each SKILL.md. Concrete `AskUserQuestion` mentions elsewhere in a skill are treated as examples — other runtimes substitute their local equivalent under the rule.
|
||||||
|
|
||||||
|
## Image Generation Tools
|
||||||
|
|
||||||
|
Skills that render images MUST declare the backend-selection convention **inline** in exactly one place per `SKILL.md` — a `## Image Generation Tools` section near the top (after `## User Input Tools`). Do NOT link out to [docs/image-generation-tools.md](docs/image-generation-tools.md); that doc is the author-side canonical source — copy its body into each SKILL.md. Concrete tool names (`imagegen`, `image_generate`, `baoyu-imagine`) elsewhere in a skill are treated as examples — other runtimes substitute their local equivalent under the rule. The rule is stateless: use whatever backend is available; if multiple, ask the user once; if none, ask how to proceed. Every rendered image's full prompt must be written to a standalone `prompts/NN-*.md` file before any backend is invoked. Backend skills (`baoyu-imagine`, `baoyu-image-gen`, `baoyu-danger-gemini-web`) are exempt — they render directly rather than selecting a backend.
|
||||||
|
|
||||||
## Deprecated Skills
|
## Deprecated Skills
|
||||||
|
|
||||||
| Skill | Note |
|
| Skill | Note |
|
||||||
|-------|------|
|
|-------|------|
|
||||||
| `baoyu-image-gen` | Migrated to `baoyu-imagine`. Do NOT add to `.claude-plugin/marketplace.json`. Do NOT update README for this skill. |
|
| `baoyu-image-gen` | Superseded by `baoyu-imagine`. Not in `.claude-plugin/marketplace.json`. Kept functional — sync any cross-cutting changes with `baoyu-imagine`. |
|
||||||
| `baoyu-xhs-images` | Migrated to `baoyu-image-cards`. Do NOT add to `.claude-plugin/marketplace.json`. Do NOT update README for this skill. |
|
| `baoyu-xhs-images` | Superseded by `baoyu-image-cards`. Not in `.claude-plugin/marketplace.json`. Kept functional — sync any cross-cutting changes with `baoyu-image-cards`. Do NOT update README for this skill. |
|
||||||
|
|
||||||
## Release Process
|
## Release Process
|
||||||
|
|
||||||
@@ -77,7 +93,9 @@ All skills MUST use `baoyu-` prefix. Details: [docs/creating-skills.md](docs/cre
|
|||||||
|
|
||||||
| Topic | File |
|
| Topic | File |
|
||||||
|-------|------|
|
|-------|------|
|
||||||
| Image generation guidelines | [docs/image-generation.md](docs/image-generation.md) |
|
| Image generation output guidelines | [docs/image-generation.md](docs/image-generation.md) |
|
||||||
|
| Image generation backend selection | [docs/image-generation-tools.md](docs/image-generation-tools.md) |
|
||||||
|
| User input tool convention | [docs/user-input-tools.md](docs/user-input-tools.md) |
|
||||||
| Chrome profile platform paths | [docs/chrome-profile.md](docs/chrome-profile.md) |
|
| Chrome profile platform paths | [docs/chrome-profile.md](docs/chrome-profile.md) |
|
||||||
| Comic style maintenance | [docs/comic-style-maintenance.md](docs/comic-style-maintenance.md) |
|
| Comic style maintenance | [docs/comic-style-maintenance.md](docs/comic-style-maintenance.md) |
|
||||||
| ClawHub/OpenClaw publishing | [docs/publishing.md](docs/publishing.md) |
|
| ClawHub/OpenClaw publishing | [docs/publishing.md](docs/publishing.md) |
|
||||||
|
|||||||
+39
-1
@@ -127,7 +127,7 @@ test -f "$HOME/.baoyu-skills/<skill-name>/EXTEND.md" && echo "user"
|
|||||||
| Result | Action |
|
| Result | Action |
|
||||||
|--------|--------|
|
|--------|--------|
|
||||||
| Found | Read, parse, display summary |
|
| Found | Read, parse, display summary |
|
||||||
| Not found | Ask user with AskUserQuestion |
|
| Not found | Ask user via the runtime's user-input tool (see [user-input-tools.md](user-input-tools.md)) |
|
||||||
```
|
```
|
||||||
|
|
||||||
End of SKILL.md should include:
|
End of SKILL.md should include:
|
||||||
@@ -135,3 +135,41 @@ End of SKILL.md should include:
|
|||||||
## Extension Support
|
## Extension Support
|
||||||
Custom configurations via EXTEND.md. See **Step 1.1** for paths and supported options.
|
Custom configurations via EXTEND.md. See **Step 1.1** for paths and supported options.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## User Input Tools Section (Required)
|
||||||
|
|
||||||
|
Every SKILL.md that prompts the user for choices MUST include exactly one `## User Input Tools` section near the top (right after the intro, before the main workflow). The rule must be **inlined** — do NOT link to `docs/user-input-tools.md` (skills are self-contained; see [CLAUDE.md → Skill Self-Containment](../CLAUDE.md)). The author-side canonical reference lives at [user-input-tools.md](user-input-tools.md); copy its body into each new SKILL.md.
|
||||||
|
|
||||||
|
Standard snippet (copy verbatim):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## User Input Tools
|
||||||
|
|
||||||
|
When this skill prompts the user, follow this tool-selection rule (priority order):
|
||||||
|
|
||||||
|
1. **Prefer built-in user-input tools** exposed by the current agent runtime — e.g., `AskUserQuestion`, `request_user_input`, `clarify`, `ask_user`, or any equivalent.
|
||||||
|
2. **Fallback**: if no such tool exists, emit a numbered plain-text message and ask the user to reply with the chosen number/answer for each question.
|
||||||
|
3. **Batching**: if the tool supports multiple questions per call, combine all applicable questions into a single call; if only single-question, ask them one at a time in priority order.
|
||||||
|
|
||||||
|
Concrete `AskUserQuestion` references below are examples — substitute the local equivalent in other runtimes.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Image Generation Tools Section (Required for image-gen skills)
|
||||||
|
|
||||||
|
Every SKILL.md that renders images — whether by calling an image-generation API directly or by delegating to another skill — MUST include exactly one `## Image Generation Tools` section near the top (after `## User Input Tools`, before the main workflow). The rule must be **inlined** — do NOT link to `docs/image-generation-tools.md` (skills are self-contained; see [CLAUDE.md → Skill Self-Containment](../CLAUDE.md)). The author-side canonical reference lives at [image-generation-tools.md](image-generation-tools.md); copy its body into each new SKILL.md.
|
||||||
|
|
||||||
|
Standard snippet (copy verbatim):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Image Generation Tools
|
||||||
|
|
||||||
|
When this skill needs to render an image:
|
||||||
|
|
||||||
|
- **Use whatever image-generation tool or skill is available** in the current runtime — e.g., Codex `imagegen`, Hermes `image_generate`, `baoyu-imagine`, or any equivalent the user has installed.
|
||||||
|
- **If multiple are available**, ask the user **once** at the start which to use (batch with any other initial questions).
|
||||||
|
- **If none are available**, tell the user and ask how to proceed.
|
||||||
|
|
||||||
|
**Prompt file requirement (hard)**: write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and lets you switch backends without regenerating prompts.
|
||||||
|
|
||||||
|
Concrete tool names (`imagegen`, `image_generate`, `baoyu-imagine`) above are examples — substitute the local equivalents under the same rule.
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Image Generation Tools
|
||||||
|
|
||||||
|
Skills in this repo are loaded by multiple agent runtimes (Claude Code, Codex, Hermes, other agents, bare CLI). Each runtime exposes a different image-generation capability — some have a runtime-native tool (Codex `imagegen`, Hermes `image_generate`), others rely on an installed skill (`baoyu-imagine`, or user-defined). This document defines the canonical **backend-selection rule** every skill that renders images follows so skills stay portable.
|
||||||
|
|
||||||
|
## The Rule
|
||||||
|
|
||||||
|
When a skill needs to render an image:
|
||||||
|
|
||||||
|
- **Use whatever image-generation tool or skill is available** in the current runtime — e.g., Codex `imagegen`, Hermes `image_generate`, `baoyu-imagine`, or any equivalent the user has installed.
|
||||||
|
- **If multiple are available**, ask the user **once** at the start which to use (batch with any other initial questions).
|
||||||
|
- **If none are available**, tell the user and ask how to proceed.
|
||||||
|
|
||||||
|
No explicit priority between runtime-native tools and repo skills — treat them equivalently and let the user decide when there's a choice. No persisted preference mechanism; the question is cheap, and the rule is stateless.
|
||||||
|
|
||||||
|
## Prompt File Requirement (hard)
|
||||||
|
|
||||||
|
Regardless of which backend is chosen, every skill that renders images MUST write each image's full, final prompt to a standalone file under `prompts/` (naming: `NN-{type}-[slug].md`) BEFORE invoking any backend. The backend receives the prompt file (or its content); the file is the reproducibility record and allows switching backends without regenerating prompts.
|
||||||
|
|
||||||
|
## How Skills Declare This
|
||||||
|
|
||||||
|
Each `SKILL.md` that renders images includes **exactly one** `## Image Generation Tools` section (near the top, after `## User Input Tools` and before the main workflow) that **inlines** this rule. Skills are self-contained and cannot link to `docs/` — each skill folder must ship the rule inside its own `SKILL.md`. See [CLAUDE.md → Skill Self-Containment](../CLAUDE.md).
|
||||||
|
|
||||||
|
Concrete tool names (`imagegen`, `image_generate`, `baoyu-imagine`) in this document and in SKILL.md are **examples** — agents in other runtimes apply the rule above and substitute the local equivalent. Skill-specific parameters for these backends are illustrative; runtimes without those knobs can omit them.
|
||||||
|
|
||||||
|
## Backend Skills Are Exempt
|
||||||
|
|
||||||
|
Skills that **are themselves** image-generation backends — currently `baoyu-imagine`, `baoyu-image-gen` (deprecated), and `baoyu-danger-gemini-web` — do NOT include a `## Image Generation Tools` section. They render directly via their own provider integrations and have no need to "select a backend." The rule applies only to consumer skills that delegate rendering to whatever backend the runtime exposes.
|
||||||
+15
-14
@@ -1,30 +1,31 @@
|
|||||||
# Image Generation Guidelines
|
# Image Generation Guidelines
|
||||||
|
|
||||||
Skills that require image generation MUST delegate to available image generation skills.
|
Skills that require image generation MUST delegate to available image generation tools (runtime-native tools or installed skills).
|
||||||
|
|
||||||
|
**Backend selection convention**: see [image-generation-tools.md](image-generation-tools.md) for the runtime-neutral rule. Short version: use whatever backend is available; if multiple, ask the user once; if none, ask how to proceed. This document covers output conventions (naming, paths) that apply regardless of which backend is selected.
|
||||||
|
|
||||||
## Skill Selection
|
## Skill Selection
|
||||||
|
|
||||||
**Default**: `skills/baoyu-imagine/SKILL.md` (unless user specifies otherwise).
|
1. Follow the rule in [image-generation-tools.md](image-generation-tools.md): use whatever backend is available; ask only on ambiguity.
|
||||||
|
2. Read the chosen backend's documentation for parameters and capabilities.
|
||||||
1. Read skill's SKILL.md for parameters and capabilities
|
3. If user requests a specific backend, honor it.
|
||||||
2. If user requests different skill, check `skills/` for alternatives
|
|
||||||
3. Only ask user when multiple viable options exist
|
|
||||||
|
|
||||||
## Generation Flow Template
|
## Generation Flow Template
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
### Step N: Generate Images
|
### Step N: Generate Images
|
||||||
|
|
||||||
**Skill Selection**:
|
**Backend Selection**:
|
||||||
1. Check available skills (`baoyu-imagine` default, or `baoyu-danger-gemini-web`)
|
1. Detect available image-generation tools/skills (runtime-native + installed)
|
||||||
2. Read selected skill's SKILL.md for parameters
|
2. If one available → use it. If multiple → ask user once. If none → ask how to proceed.
|
||||||
3. If multiple skills available, ask user to choose
|
3. Read the chosen backend's docs for parameters
|
||||||
|
|
||||||
**Generation Flow**:
|
**Generation Flow**:
|
||||||
1. Call skill with prompt, output path, and skill-specific parameters
|
1. Write the full prompt to `prompts/NN-{type}-[slug].md` BEFORE invoking the backend
|
||||||
2. Generate sequentially by default (batch parallel only when user has multiple prompts)
|
2. Call backend with the prompt (or prompt file), output path, and parameters
|
||||||
3. Output progress: "Generated X/N"
|
3. Generate sequentially by default (batch parallel only when backend supports it and user has multiple prompts)
|
||||||
4. On failure, auto-retry once before reporting error
|
4. Output progress: "Generated X/N"
|
||||||
|
5. On failure, auto-retry once before reporting error
|
||||||
```
|
```
|
||||||
|
|
||||||
**Batch Parallel** (`baoyu-imagine` only): concurrent workers with per-provider throttling via `batch.max_workers` in EXTEND.md.
|
**Batch Parallel** (`baoyu-imagine` only): concurrent workers with per-provider throttling via `batch.max_workers` in EXTEND.md.
|
||||||
|
|||||||
+10
-7
@@ -21,21 +21,24 @@ bash scripts/sync-clawhub.sh # sync all skills
|
|||||||
bash scripts/sync-clawhub.sh <skill> # sync one skill
|
bash scripts/sync-clawhub.sh <skill> # sync one skill
|
||||||
```
|
```
|
||||||
|
|
||||||
Release hooks are configured via `.releaserc.yml`. This repo does not stage a separate release directory: release prep only syncs `packages/` into each skill's committed `scripts/vendor/`, and publish reads the skill directory directly.
|
Release hooks are configured via `.releaserc.yml`. This repo does not stage a separate release directory: release prep verifies that skills depend on published npm package versions, and publish reads the skill directory directly.
|
||||||
|
|
||||||
## Shared Workspace Packages
|
## Shared Workspace Packages
|
||||||
|
|
||||||
`packages/` is the **only** source of truth. Never edit `skills/*/scripts/vendor/` directly.
|
`packages/` is the **only** source of truth for shared runtime code. Publish shared packages to npm and reference them from skill script `package.json` files with semver ranges. Do not vendor shared packages into `skills/*/scripts/vendor/`.
|
||||||
|
|
||||||
Current packages:
|
Current packages:
|
||||||
- `baoyu-chrome-cdp` (Chrome CDP utilities), consumed by 6 skills (`baoyu-danger-gemini-web`, `baoyu-danger-x-to-markdown`, `baoyu-post-to-wechat`, `baoyu-post-to-weibo`, `baoyu-post-to-x`, `baoyu-url-to-markdown`)
|
- `baoyu-chrome-cdp` (Chrome CDP utilities), consumed by 5 skills (`baoyu-danger-gemini-web`, `baoyu-danger-x-to-markdown`, `baoyu-post-to-wechat`, `baoyu-post-to-weibo`, `baoyu-post-to-x`)
|
||||||
- `baoyu-md` (shared Markdown rendering and placeholder pipeline), consumed by 3 skills (`baoyu-markdown-to-html`, `baoyu-post-to-wechat`, `baoyu-post-to-weibo`)
|
- `baoyu-md` (shared Markdown rendering and placeholder pipeline), consumed by 3 skills (`baoyu-markdown-to-html`, `baoyu-post-to-wechat`, `baoyu-post-to-weibo`)
|
||||||
|
- `baoyu-fetch` (URL-to-Markdown CLI), consumed by 1 skill (`baoyu-url-to-markdown`)
|
||||||
|
|
||||||
**How it works**: Sync script copies packages into each consuming skill's `vendor/` directory and rewrites dependency specs to `file:./vendor/<name>`. Vendor copies are committed to git, making skills self-contained.
|
**How it works**: npm packages are built from `packages/` and published to the public npm registry. Skills depend on those packages with `^<version>` specs. Release prep runs `node scripts/verify-shared-package-deps.mjs` so `file:` dependencies and vendored workspace packages cannot slip back in.
|
||||||
|
|
||||||
**Update workflow**:
|
**Update workflow**:
|
||||||
1. Edit package under `packages/`
|
1. Edit package under `packages/`
|
||||||
2. Run `node scripts/sync-shared-skill-packages.mjs`
|
2. Run the package build, e.g. `bun run --cwd packages/baoyu-md build`
|
||||||
3. Commit synced `vendor/`, `package.json`, and `bun.lock` together
|
3. Publish the changed npm package with `npm publish --access public`
|
||||||
|
4. Update consuming skill `package.json` semver ranges if the package version changed
|
||||||
|
5. Run `node scripts/verify-shared-package-deps.mjs`
|
||||||
|
|
||||||
**Git hook**: Run `node scripts/install-git-hooks.mjs` once to enable the `pre-push` hook. It re-syncs and blocks push if vendor copies are stale (`--enforce-clean`).
|
**Git hook**: Run `node scripts/install-git-hooks.mjs` once to enable the `pre-push` hook. It blocks pushes when a skill uses a local `file:` dependency or a vendored workspace package.
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@ This repository has many scripts, but they do not share a single runtime or depe
|
|||||||
- Coverage command: `npm run test:coverage`
|
- Coverage command: `npm run test:coverage`
|
||||||
- CI trigger: GitHub Actions on `push`, `pull_request`, and manual dispatch
|
- CI trigger: GitHub Actions on `push`, `pull_request`, and manual dispatch
|
||||||
|
|
||||||
This avoids introducing Jest/Vitest across a repo that already mixes plain Node scripts, Bun-based skill packages, vendored code, and browser automation.
|
This avoids introducing Jest/Vitest across a repo that already mixes plain Node scripts, Bun-based skill packages, npm-published shared packages, and browser automation.
|
||||||
|
|
||||||
## Rollout Plan
|
## Rollout Plan
|
||||||
|
|
||||||
@@ -18,19 +18,19 @@ This avoids introducing Jest/Vitest across a repo that already mixes plain Node
|
|||||||
Focus on pure functions under `scripts/lib/` first.
|
Focus on pure functions under `scripts/lib/` first.
|
||||||
|
|
||||||
- `scripts/lib/release-files.mjs`
|
- `scripts/lib/release-files.mjs`
|
||||||
- `scripts/lib/shared-skill-packages.mjs`
|
- `scripts/verify-shared-package-deps.mjs`
|
||||||
|
|
||||||
Goals:
|
Goals:
|
||||||
|
|
||||||
- Validate file filtering and release packaging rules
|
- Validate file filtering and release packaging rules
|
||||||
- Catch regressions in package vendoring and dependency rewriting
|
- Catch regressions that reintroduce local `file:` dependencies or vendored workspace packages
|
||||||
- Keep tests deterministic and free of network, Bun, or browser requirements
|
- Keep tests deterministic and free of network, Bun, or browser requirements
|
||||||
|
|
||||||
### Phase 2: Root CLI integration tests
|
### Phase 2: Root CLI integration tests
|
||||||
|
|
||||||
Add temp-directory integration tests for root CLIs that already support dry-run or local-only flows.
|
Add temp-directory integration tests for root CLIs that already support dry-run or local-only flows.
|
||||||
|
|
||||||
- `scripts/sync-shared-skill-packages.mjs`
|
- `scripts/verify-shared-package-deps.mjs`
|
||||||
- `scripts/publish-skill.mjs --dry-run`
|
- `scripts/publish-skill.mjs --dry-run`
|
||||||
- `scripts/sync-clawhub.mjs` argument handling and local skill discovery
|
- `scripts/sync-clawhub.mjs` argument handling and local skill discovery
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# User Input Tools
|
||||||
|
|
||||||
|
Skills in this repo are loaded by multiple agent runtimes (Claude Code, other agents, bare CLI). Each runtime exposes a different API for asking the user questions. This document defines the canonical **tool-selection rule** every skill follows so skills stay portable.
|
||||||
|
|
||||||
|
## Tool Selection (priority order)
|
||||||
|
|
||||||
|
1. **Prefer built-in user-input tools** if the current agent runtime exposes one — e.g., `AskUserQuestion`, `request_user_input`, `clarify`, `ask_user`, or any equivalent.
|
||||||
|
2. **Fallback to plain text**: if no such tool exists, emit a numbered plain-text message and ask the user to reply with the chosen number/answer for each question.
|
||||||
|
3. **Batching rule**:
|
||||||
|
- If the tool supports **multiple questions per call** (e.g., `AskUserQuestion`): **Combine all applicable questions into a single call. Do NOT split into separate calls.**
|
||||||
|
- If the tool supports **only one question per call** (e.g., single-prompt `clarify`): ask **one question per call, in priority order**.
|
||||||
|
|
||||||
|
## How Skills Declare This
|
||||||
|
|
||||||
|
Each `SKILL.md` that uses interactive user input includes **exactly one** `## User Input Tools` section (typically near the top, right after the intro) that **inlines** this rule. Do NOT link here from a SKILL.md — skills are self-contained (see [CLAUDE.md → Skill Self-Containment](../CLAUDE.md)). This document is the author-side canonical source; copy its body into each SKILL.md. The rule then governs every user-input interaction in that skill and its `references/` files.
|
||||||
|
|
||||||
|
Specific mentions of a concrete tool (e.g., `AskUserQuestion`) elsewhere in a skill are **concrete examples** — agents in other runtimes apply the rule above and substitute the local equivalent. Tool-specific parameters (e.g., `header:`, `multiSelect:`) are illustrative; runtimes without those knobs can omit them.
|
||||||
Generated
+594
-5
@@ -10,7 +10,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pptxgenjs": "^4.0.1"
|
"pptxgenjs": "^4.0.1",
|
||||||
|
"sharp": "^0.34.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mozilla/readability": "^0.6.0",
|
"@mozilla/readability": "^0.6.0",
|
||||||
@@ -415,6 +416,23 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.4",
|
"version": "0.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
|
||||||
@@ -857,6 +875,519 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@img/colour": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-darwin-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-ppc64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-riscv64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-s390x": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linux-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-wasm32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/runtime": "^1.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-arm64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-ia32": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@img/sharp-win32-x64": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@inquirer/external-editor": {
|
"node_modules/@inquirer/external-editor": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
|
||||||
@@ -1479,6 +2010,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/devlop": {
|
"node_modules/devlop": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
||||||
@@ -3749,7 +4289,6 @@
|
|||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -3764,6 +4303,50 @@
|
|||||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/sharp": {
|
||||||
|
"version": "0.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||||
|
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@img/colour": "^1.0.0",
|
||||||
|
"detect-libc": "^2.1.2",
|
||||||
|
"semver": "^7.7.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/libvips"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@img/sharp-darwin-arm64": "0.34.5",
|
||||||
|
"@img/sharp-darwin-x64": "0.34.5",
|
||||||
|
"@img/sharp-libvips-darwin-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-darwin-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-ppc64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-riscv64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-s390x": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linux-x64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
|
||||||
|
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
|
||||||
|
"@img/sharp-linux-arm": "0.34.5",
|
||||||
|
"@img/sharp-linux-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linux-ppc64": "0.34.5",
|
||||||
|
"@img/sharp-linux-riscv64": "0.34.5",
|
||||||
|
"@img/sharp-linux-s390x": "0.34.5",
|
||||||
|
"@img/sharp-linux-x64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-arm64": "0.34.5",
|
||||||
|
"@img/sharp-linuxmusl-x64": "0.34.5",
|
||||||
|
"@img/sharp-wasm32": "0.34.5",
|
||||||
|
"@img/sharp-win32-arm64": "0.34.5",
|
||||||
|
"@img/sharp-win32-ia32": "0.34.5",
|
||||||
|
"@img/sharp-win32-x64": "0.34.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -4301,10 +4884,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/baoyu-chrome-cdp": {
|
"packages/baoyu-chrome-cdp": {
|
||||||
"version": "0.1.0"
|
"version": "0.1.0",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.2.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"packages/baoyu-fetch": {
|
"packages/baoyu-fetch": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mozilla/readability": "^0.6.0",
|
"@mozilla/readability": "^0.6.0",
|
||||||
"chrome-launcher": "^1.2.1",
|
"chrome-launcher": "^1.2.1",
|
||||||
@@ -4318,7 +4904,7 @@
|
|||||||
"ws": "^8.18.3"
|
"ws": "^8.18.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"baoyu-fetch": "src/cli.ts"
|
"baoyu-fetch": "dist/cli.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.30.0",
|
"@changesets/cli": "^2.30.0",
|
||||||
@@ -4344,6 +4930,9 @@
|
|||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-stringify": "^11.0.0",
|
"remark-stringify": "^11.0.0",
|
||||||
"unified": "^11.0.5"
|
"unified": "^11.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+479
@@ -0,0 +1,479 @@
|
|||||||
|
var __create = Object.create;
|
||||||
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
|
var __defProp = Object.defineProperty;
|
||||||
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||||
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
|
function __accessProp(key) {
|
||||||
|
return this[key];
|
||||||
|
}
|
||||||
|
var __toESMCache_node;
|
||||||
|
var __toESMCache_esm;
|
||||||
|
var __toESM = (mod, isNodeMode, target) => {
|
||||||
|
var canCache = mod != null && typeof mod === "object";
|
||||||
|
if (canCache) {
|
||||||
|
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
||||||
|
var cached = cache.get(mod);
|
||||||
|
if (cached)
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
||||||
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
||||||
|
for (let key of __getOwnPropNames(mod))
|
||||||
|
if (!__hasOwnProp.call(to, key))
|
||||||
|
__defProp(to, key, {
|
||||||
|
get: __accessProp.bind(mod, key),
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
if (canCache)
|
||||||
|
cache.set(mod, to);
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
var __toCommonJS = (from) => {
|
||||||
|
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
||||||
|
if (entry)
|
||||||
|
return entry;
|
||||||
|
entry = __defProp({}, "__esModule", { value: true });
|
||||||
|
if (from && typeof from === "object" || typeof from === "function") {
|
||||||
|
for (var key of __getOwnPropNames(from))
|
||||||
|
if (!__hasOwnProp.call(entry, key))
|
||||||
|
__defProp(entry, key, {
|
||||||
|
get: __accessProp.bind(from, key),
|
||||||
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
__moduleCache.set(from, entry);
|
||||||
|
return entry;
|
||||||
|
};
|
||||||
|
var __moduleCache;
|
||||||
|
var __returnValue = (v) => v;
|
||||||
|
function __exportSetter(name, newValue) {
|
||||||
|
this[name] = __returnValue.bind(null, newValue);
|
||||||
|
}
|
||||||
|
var __export = (target, all) => {
|
||||||
|
for (var name in all)
|
||||||
|
__defProp(target, name, {
|
||||||
|
get: all[name],
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
set: __exportSetter.bind(all, name)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/index.ts
|
||||||
|
var exports_src = {};
|
||||||
|
__export(exports_src, {
|
||||||
|
waitForChromeDebugPort: () => waitForChromeDebugPort,
|
||||||
|
sleep: () => sleep,
|
||||||
|
resolveSharedChromeProfileDir: () => resolveSharedChromeProfileDir,
|
||||||
|
openPageSession: () => openPageSession,
|
||||||
|
launchChrome: () => launchChrome,
|
||||||
|
killChrome: () => killChrome,
|
||||||
|
gracefulKillChrome: () => gracefulKillChrome,
|
||||||
|
getFreePort: () => getFreePort,
|
||||||
|
getDefaultChromeUserDataDirs: () => getDefaultChromeUserDataDirs,
|
||||||
|
findExistingChromeDebugPort: () => findExistingChromeDebugPort,
|
||||||
|
findChromeExecutable: () => findChromeExecutable,
|
||||||
|
discoverRunningChromeDebugPort: () => discoverRunningChromeDebugPort,
|
||||||
|
CdpConnection: () => CdpConnection
|
||||||
|
});
|
||||||
|
module.exports = __toCommonJS(exports_src);
|
||||||
|
var import_node_child_process = require("node:child_process");
|
||||||
|
var import_node_fs = __toESM(require("node:fs"));
|
||||||
|
var import_node_net = __toESM(require("node:net"));
|
||||||
|
var import_node_os = __toESM(require("node:os"));
|
||||||
|
var import_node_path = __toESM(require("node:path"));
|
||||||
|
var import_node_process = __toESM(require("node:process"));
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
async function getFreePort(fixedEnvName) {
|
||||||
|
const fixed = fixedEnvName ? Number.parseInt(import_node_process.default.env[fixedEnvName] ?? "", 10) : NaN;
|
||||||
|
if (Number.isInteger(fixed) && fixed > 0)
|
||||||
|
return fixed;
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const server = import_node_net.default.createServer();
|
||||||
|
server.unref();
|
||||||
|
server.on("error", reject);
|
||||||
|
server.listen(0, "127.0.0.1", () => {
|
||||||
|
const address = server.address();
|
||||||
|
if (!address || typeof address === "string") {
|
||||||
|
server.close(() => reject(new Error("Unable to allocate a free TCP port.")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const port = address.port;
|
||||||
|
server.close((err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function findChromeExecutable(options) {
|
||||||
|
for (const envName of options.envNames ?? []) {
|
||||||
|
const override = import_node_process.default.env[envName]?.trim();
|
||||||
|
if (override && import_node_fs.default.existsSync(override))
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
const candidates = import_node_process.default.platform === "darwin" ? options.candidates.darwin ?? options.candidates.default : import_node_process.default.platform === "win32" ? options.candidates.win32 ?? options.candidates.default : options.candidates.default;
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (import_node_fs.default.existsSync(candidate))
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function resolveSharedChromeProfileDir(options = {}) {
|
||||||
|
for (const envName of options.envNames ?? []) {
|
||||||
|
const override = import_node_process.default.env[envName]?.trim();
|
||||||
|
if (override)
|
||||||
|
return import_node_path.default.resolve(override);
|
||||||
|
}
|
||||||
|
const appDataDirName = options.appDataDirName ?? "baoyu-skills";
|
||||||
|
const profileDirName = options.profileDirName ?? "chrome-profile";
|
||||||
|
if (options.wslWindowsHome) {
|
||||||
|
return import_node_path.default.join(options.wslWindowsHome, ".local", "share", appDataDirName, profileDirName);
|
||||||
|
}
|
||||||
|
const base = import_node_process.default.platform === "darwin" ? import_node_path.default.join(import_node_os.default.homedir(), "Library", "Application Support") : import_node_process.default.platform === "win32" ? import_node_process.default.env.APPDATA ?? import_node_path.default.join(import_node_os.default.homedir(), "AppData", "Roaming") : import_node_process.default.env.XDG_DATA_HOME ?? import_node_path.default.join(import_node_os.default.homedir(), ".local", "share");
|
||||||
|
return import_node_path.default.join(base, appDataDirName, profileDirName);
|
||||||
|
}
|
||||||
|
async function fetchWithTimeout(url, timeoutMs) {
|
||||||
|
if (!timeoutMs || timeoutMs <= 0)
|
||||||
|
return await fetch(url, { redirect: "follow" });
|
||||||
|
const ctl = new AbortController;
|
||||||
|
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
||||||
|
try {
|
||||||
|
return await fetch(url, { redirect: "follow", signal: ctl.signal });
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function fetchJson(url, options = {}) {
|
||||||
|
const response = await fetchWithTimeout(url, options.timeoutMs);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
async function isDebugPortReady(port, timeoutMs = 3000) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
return !!version.webSocketDebuggerUrl;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isPortListening(port, timeoutMs = 3000) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = new import_node_net.default.Socket;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
socket.destroy();
|
||||||
|
resolve(false);
|
||||||
|
}, timeoutMs);
|
||||||
|
socket.once("connect", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
socket.destroy();
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
socket.once("error", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
socket.connect(port, "127.0.0.1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function parseDevToolsActivePort(filePath) {
|
||||||
|
try {
|
||||||
|
const content = import_node_fs.default.readFileSync(filePath, "utf-8");
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
const port = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
||||||
|
const wsPath = lines[1]?.trim();
|
||||||
|
if (port > 0 && wsPath)
|
||||||
|
return { port, wsPath };
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function findExistingChromeDebugPort(options) {
|
||||||
|
const timeoutMs = options.timeoutMs ?? 3000;
|
||||||
|
const parsed = parseDevToolsActivePort(import_node_path.default.join(options.profileDir, "DevToolsActivePort"));
|
||||||
|
if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs))
|
||||||
|
return parsed.port;
|
||||||
|
if (import_node_process.default.platform === "win32")
|
||||||
|
return null;
|
||||||
|
try {
|
||||||
|
const result = import_node_child_process.spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5000 });
|
||||||
|
if (result.status !== 0 || !result.stdout)
|
||||||
|
return null;
|
||||||
|
const lines = result.stdout.split(`
|
||||||
|
`).filter((line) => line.includes(options.profileDir) && line.includes("--remote-debugging-port="));
|
||||||
|
for (const line of lines) {
|
||||||
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
|
if (port > 0 && await isDebugPortReady(port, timeoutMs))
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function getDefaultChromeUserDataDirs(channels = ["stable"]) {
|
||||||
|
const home = import_node_os.default.homedir();
|
||||||
|
const dirs = [];
|
||||||
|
const channelDirs = {
|
||||||
|
stable: {
|
||||||
|
darwin: import_node_path.default.join(home, "Library", "Application Support", "Google", "Chrome"),
|
||||||
|
linux: import_node_path.default.join(home, ".config", "google-chrome"),
|
||||||
|
win32: import_node_path.default.join(import_node_process.default.env.LOCALAPPDATA ?? import_node_path.default.join(home, "AppData", "Local"), "Google", "Chrome", "User Data")
|
||||||
|
},
|
||||||
|
beta: {
|
||||||
|
darwin: import_node_path.default.join(home, "Library", "Application Support", "Google", "Chrome Beta"),
|
||||||
|
linux: import_node_path.default.join(home, ".config", "google-chrome-beta"),
|
||||||
|
win32: import_node_path.default.join(import_node_process.default.env.LOCALAPPDATA ?? import_node_path.default.join(home, "AppData", "Local"), "Google", "Chrome Beta", "User Data")
|
||||||
|
},
|
||||||
|
canary: {
|
||||||
|
darwin: import_node_path.default.join(home, "Library", "Application Support", "Google", "Chrome Canary"),
|
||||||
|
linux: import_node_path.default.join(home, ".config", "google-chrome-canary"),
|
||||||
|
win32: import_node_path.default.join(import_node_process.default.env.LOCALAPPDATA ?? import_node_path.default.join(home, "AppData", "Local"), "Google", "Chrome SxS", "User Data")
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
darwin: import_node_path.default.join(home, "Library", "Application Support", "Google", "Chrome Dev"),
|
||||||
|
linux: import_node_path.default.join(home, ".config", "google-chrome-dev"),
|
||||||
|
win32: import_node_path.default.join(import_node_process.default.env.LOCALAPPDATA ?? import_node_path.default.join(home, "AppData", "Local"), "Google", "Chrome Dev", "User Data")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const platform = import_node_process.default.platform === "darwin" ? "darwin" : import_node_process.default.platform === "win32" ? "win32" : "linux";
|
||||||
|
for (const ch of channels) {
|
||||||
|
const entry = channelDirs[ch];
|
||||||
|
if (entry)
|
||||||
|
dirs.push(entry[platform]);
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
async function discoverRunningChromeDebugPort(options = {}) {
|
||||||
|
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
||||||
|
const timeoutMs = options.timeoutMs ?? 3000;
|
||||||
|
const userDataDirs = (options.userDataDirs ?? getDefaultChromeUserDataDirs(channels)).map((dir) => import_node_path.default.resolve(dir));
|
||||||
|
for (const dir of userDataDirs) {
|
||||||
|
const parsed = parseDevToolsActivePort(import_node_path.default.join(dir, "DevToolsActivePort"));
|
||||||
|
if (!parsed)
|
||||||
|
continue;
|
||||||
|
if (await isPortListening(parsed.port, timeoutMs)) {
|
||||||
|
return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (import_node_process.default.platform !== "win32") {
|
||||||
|
try {
|
||||||
|
const result = import_node_child_process.spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5000 });
|
||||||
|
if (result.status === 0 && result.stdout) {
|
||||||
|
const lines = result.stdout.split(`
|
||||||
|
`).filter((line) => line.includes("--remote-debugging-port=") && userDataDirs.some((dir) => line.includes(dir)));
|
||||||
|
for (const line of lines) {
|
||||||
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
|
if (port > 0 && await isDebugPortReady(port, timeoutMs)) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
if (version.webSocketDebuggerUrl)
|
||||||
|
return { port, wsUrl: version.webSocketDebuggerUrl };
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function waitForChromeDebugPort(port, timeoutMs, options) {
|
||||||
|
const start = Date.now();
|
||||||
|
let lastError = null;
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs: 5000 });
|
||||||
|
if (version.webSocketDebuggerUrl)
|
||||||
|
return version.webSocketDebuggerUrl;
|
||||||
|
lastError = new Error("Missing webSocketDebuggerUrl");
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
if (options?.includeLastError && lastError) {
|
||||||
|
throw new Error(`Chrome debug port not ready: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
||||||
|
}
|
||||||
|
throw new Error("Chrome debug port not ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
class CdpConnection {
|
||||||
|
ws;
|
||||||
|
nextId = 0;
|
||||||
|
pending = new Map;
|
||||||
|
eventHandlers = new Map;
|
||||||
|
defaultTimeoutMs;
|
||||||
|
constructor(ws, defaultTimeoutMs = 15000) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.defaultTimeoutMs = defaultTimeoutMs;
|
||||||
|
this.ws.addEventListener("message", (event) => {
|
||||||
|
try {
|
||||||
|
const data = typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data);
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
if (msg.method) {
|
||||||
|
const handlers = this.eventHandlers.get(msg.method);
|
||||||
|
if (handlers) {
|
||||||
|
handlers.forEach((handler) => handler(msg.params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.id) {
|
||||||
|
const pending = this.pending.get(msg.id);
|
||||||
|
if (pending) {
|
||||||
|
this.pending.delete(msg.id);
|
||||||
|
if (pending.timer)
|
||||||
|
clearTimeout(pending.timer);
|
||||||
|
if (msg.error?.message)
|
||||||
|
pending.reject(new Error(msg.error.message));
|
||||||
|
else
|
||||||
|
pending.resolve(msg.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
this.ws.addEventListener("close", () => {
|
||||||
|
for (const [id, pending] of this.pending.entries()) {
|
||||||
|
this.pending.delete(id);
|
||||||
|
if (pending.timer)
|
||||||
|
clearTimeout(pending.timer);
|
||||||
|
pending.reject(new Error("CDP connection closed."));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
static async connect(url, timeoutMs, options) {
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => reject(new Error("CDP connection timeout.")), timeoutMs);
|
||||||
|
ws.addEventListener("open", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
ws.addEventListener("error", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(new Error("CDP connection failed."));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return new CdpConnection(ws, options?.defaultTimeoutMs ?? 15000);
|
||||||
|
}
|
||||||
|
on(method, handler) {
|
||||||
|
if (!this.eventHandlers.has(method)) {
|
||||||
|
this.eventHandlers.set(method, new Set);
|
||||||
|
}
|
||||||
|
this.eventHandlers.get(method)?.add(handler);
|
||||||
|
}
|
||||||
|
off(method, handler) {
|
||||||
|
this.eventHandlers.get(method)?.delete(handler);
|
||||||
|
}
|
||||||
|
async send(method, params, options) {
|
||||||
|
const id = ++this.nextId;
|
||||||
|
const message = { id, method };
|
||||||
|
if (params)
|
||||||
|
message.params = params;
|
||||||
|
if (options?.sessionId)
|
||||||
|
message.sessionId = options.sessionId;
|
||||||
|
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
const timer = timeoutMs > 0 ? setTimeout(() => {
|
||||||
|
this.pending.delete(id);
|
||||||
|
reject(new Error(`CDP timeout: ${method}`));
|
||||||
|
}, timeoutMs) : null;
|
||||||
|
this.pending.set(id, { resolve, reject, timer });
|
||||||
|
this.ws.send(JSON.stringify(message));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
try {
|
||||||
|
this.ws.close();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function launchChrome(options) {
|
||||||
|
await import_node_fs.default.promises.mkdir(options.profileDir, { recursive: true });
|
||||||
|
const args = [
|
||||||
|
`--remote-debugging-port=${options.port}`,
|
||||||
|
`--user-data-dir=${options.profileDir}`,
|
||||||
|
"--no-first-run",
|
||||||
|
"--no-default-browser-check",
|
||||||
|
...options.extraArgs ?? []
|
||||||
|
];
|
||||||
|
if (options.headless)
|
||||||
|
args.push("--headless=new");
|
||||||
|
if (options.url)
|
||||||
|
args.push(options.url);
|
||||||
|
return import_node_child_process.spawn(options.chromePath, args, { stdio: "ignore" });
|
||||||
|
}
|
||||||
|
function killChrome(chrome) {
|
||||||
|
try {
|
||||||
|
chrome.kill("SIGTERM");
|
||||||
|
} catch {}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||||
|
try {
|
||||||
|
chrome.kill("SIGKILL");
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}, 2000).unref?.();
|
||||||
|
}
|
||||||
|
async function gracefulKillChrome(chrome, port, timeoutMs = 6000) {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null)
|
||||||
|
return;
|
||||||
|
const exitPromise = new Promise((resolve) => {
|
||||||
|
chrome.once("exit", () => resolve());
|
||||||
|
});
|
||||||
|
killChrome(chrome);
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null)
|
||||||
|
return;
|
||||||
|
if (port !== undefined && !await isPortListening(port, 250))
|
||||||
|
return;
|
||||||
|
const exited = await Promise.race([
|
||||||
|
exitPromise.then(() => true),
|
||||||
|
sleep(100).then(() => false)
|
||||||
|
]);
|
||||||
|
if (exited)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Promise.race([
|
||||||
|
exitPromise,
|
||||||
|
sleep(250)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
async function openPageSession(options) {
|
||||||
|
let targetId;
|
||||||
|
let createdTarget = false;
|
||||||
|
if (options.reusing) {
|
||||||
|
const created = await options.cdp.send("Target.createTarget", { url: options.url });
|
||||||
|
targetId = created.targetId;
|
||||||
|
createdTarget = true;
|
||||||
|
} else {
|
||||||
|
const targets = await options.cdp.send("Target.getTargets");
|
||||||
|
const existing = targets.targetInfos.find(options.matchTarget);
|
||||||
|
if (existing) {
|
||||||
|
targetId = existing.targetId;
|
||||||
|
} else {
|
||||||
|
const created = await options.cdp.send("Target.createTarget", { url: options.url });
|
||||||
|
targetId = created.targetId;
|
||||||
|
createdTarget = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { sessionId } = await options.cdp.send("Target.attachToTarget", { targetId, flatten: true });
|
||||||
|
if (options.activateTarget ?? true) {
|
||||||
|
await options.cdp.send("Target.activateTarget", { targetId });
|
||||||
|
}
|
||||||
|
if (options.enablePage)
|
||||||
|
await options.cdp.send("Page.enable", {}, { sessionId });
|
||||||
|
if (options.enableRuntime)
|
||||||
|
await options.cdp.send("Runtime.enable", {}, { sessionId });
|
||||||
|
if (options.enableDom)
|
||||||
|
await options.cdp.send("DOM.enable", {}, { sessionId });
|
||||||
|
if (options.enableNetwork)
|
||||||
|
await options.cdp.send("Network.enable", {}, { sessionId });
|
||||||
|
return { sessionId, targetId, createdTarget };
|
||||||
|
}
|
||||||
+415
@@ -0,0 +1,415 @@
|
|||||||
|
// src/index.ts
|
||||||
|
import { spawn, spawnSync } from "node:child_process";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import net from "node:net";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import process from "node:process";
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
async function getFreePort(fixedEnvName) {
|
||||||
|
const fixed = fixedEnvName ? Number.parseInt(process.env[fixedEnvName] ?? "", 10) : NaN;
|
||||||
|
if (Number.isInteger(fixed) && fixed > 0)
|
||||||
|
return fixed;
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const server = net.createServer();
|
||||||
|
server.unref();
|
||||||
|
server.on("error", reject);
|
||||||
|
server.listen(0, "127.0.0.1", () => {
|
||||||
|
const address = server.address();
|
||||||
|
if (!address || typeof address === "string") {
|
||||||
|
server.close(() => reject(new Error("Unable to allocate a free TCP port.")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const port = address.port;
|
||||||
|
server.close((err) => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve(port);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function findChromeExecutable(options) {
|
||||||
|
for (const envName of options.envNames ?? []) {
|
||||||
|
const override = process.env[envName]?.trim();
|
||||||
|
if (override && fs.existsSync(override))
|
||||||
|
return override;
|
||||||
|
}
|
||||||
|
const candidates = process.platform === "darwin" ? options.candidates.darwin ?? options.candidates.default : process.platform === "win32" ? options.candidates.win32 ?? options.candidates.default : options.candidates.default;
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (fs.existsSync(candidate))
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function resolveSharedChromeProfileDir(options = {}) {
|
||||||
|
for (const envName of options.envNames ?? []) {
|
||||||
|
const override = process.env[envName]?.trim();
|
||||||
|
if (override)
|
||||||
|
return path.resolve(override);
|
||||||
|
}
|
||||||
|
const appDataDirName = options.appDataDirName ?? "baoyu-skills";
|
||||||
|
const profileDirName = options.profileDirName ?? "chrome-profile";
|
||||||
|
if (options.wslWindowsHome) {
|
||||||
|
return path.join(options.wslWindowsHome, ".local", "share", appDataDirName, profileDirName);
|
||||||
|
}
|
||||||
|
const base = process.platform === "darwin" ? path.join(os.homedir(), "Library", "Application Support") : process.platform === "win32" ? process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming") : process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
|
||||||
|
return path.join(base, appDataDirName, profileDirName);
|
||||||
|
}
|
||||||
|
async function fetchWithTimeout(url, timeoutMs) {
|
||||||
|
if (!timeoutMs || timeoutMs <= 0)
|
||||||
|
return await fetch(url, { redirect: "follow" });
|
||||||
|
const ctl = new AbortController;
|
||||||
|
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
||||||
|
try {
|
||||||
|
return await fetch(url, { redirect: "follow", signal: ctl.signal });
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function fetchJson(url, options = {}) {
|
||||||
|
const response = await fetchWithTimeout(url, options.timeoutMs);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
async function isDebugPortReady(port, timeoutMs = 3000) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
return !!version.webSocketDebuggerUrl;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isPortListening(port, timeoutMs = 3000) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = new net.Socket;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
socket.destroy();
|
||||||
|
resolve(false);
|
||||||
|
}, timeoutMs);
|
||||||
|
socket.once("connect", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
socket.destroy();
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
socket.once("error", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
socket.connect(port, "127.0.0.1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function parseDevToolsActivePort(filePath) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
const port = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
||||||
|
const wsPath = lines[1]?.trim();
|
||||||
|
if (port > 0 && wsPath)
|
||||||
|
return { port, wsPath };
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function findExistingChromeDebugPort(options) {
|
||||||
|
const timeoutMs = options.timeoutMs ?? 3000;
|
||||||
|
const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort"));
|
||||||
|
if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs))
|
||||||
|
return parsed.port;
|
||||||
|
if (process.platform === "win32")
|
||||||
|
return null;
|
||||||
|
try {
|
||||||
|
const result = spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5000 });
|
||||||
|
if (result.status !== 0 || !result.stdout)
|
||||||
|
return null;
|
||||||
|
const lines = result.stdout.split(`
|
||||||
|
`).filter((line) => line.includes(options.profileDir) && line.includes("--remote-debugging-port="));
|
||||||
|
for (const line of lines) {
|
||||||
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
|
if (port > 0 && await isDebugPortReady(port, timeoutMs))
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function getDefaultChromeUserDataDirs(channels = ["stable"]) {
|
||||||
|
const home = os.homedir();
|
||||||
|
const dirs = [];
|
||||||
|
const channelDirs = {
|
||||||
|
stable: {
|
||||||
|
darwin: path.join(home, "Library", "Application Support", "Google", "Chrome"),
|
||||||
|
linux: path.join(home, ".config", "google-chrome"),
|
||||||
|
win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome", "User Data")
|
||||||
|
},
|
||||||
|
beta: {
|
||||||
|
darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Beta"),
|
||||||
|
linux: path.join(home, ".config", "google-chrome-beta"),
|
||||||
|
win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Beta", "User Data")
|
||||||
|
},
|
||||||
|
canary: {
|
||||||
|
darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Canary"),
|
||||||
|
linux: path.join(home, ".config", "google-chrome-canary"),
|
||||||
|
win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome SxS", "User Data")
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
darwin: path.join(home, "Library", "Application Support", "Google", "Chrome Dev"),
|
||||||
|
linux: path.join(home, ".config", "google-chrome-dev"),
|
||||||
|
win32: path.join(process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local"), "Google", "Chrome Dev", "User Data")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const platform = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "win32" : "linux";
|
||||||
|
for (const ch of channels) {
|
||||||
|
const entry = channelDirs[ch];
|
||||||
|
if (entry)
|
||||||
|
dirs.push(entry[platform]);
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
async function discoverRunningChromeDebugPort(options = {}) {
|
||||||
|
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
||||||
|
const timeoutMs = options.timeoutMs ?? 3000;
|
||||||
|
const userDataDirs = (options.userDataDirs ?? getDefaultChromeUserDataDirs(channels)).map((dir) => path.resolve(dir));
|
||||||
|
for (const dir of userDataDirs) {
|
||||||
|
const parsed = parseDevToolsActivePort(path.join(dir, "DevToolsActivePort"));
|
||||||
|
if (!parsed)
|
||||||
|
continue;
|
||||||
|
if (await isPortListening(parsed.port, timeoutMs)) {
|
||||||
|
return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
try {
|
||||||
|
const result = spawnSync("ps", ["aux"], { encoding: "utf-8", timeout: 5000 });
|
||||||
|
if (result.status === 0 && result.stdout) {
|
||||||
|
const lines = result.stdout.split(`
|
||||||
|
`).filter((line) => line.includes("--remote-debugging-port=") && userDataDirs.some((dir) => line.includes(dir)));
|
||||||
|
for (const line of lines) {
|
||||||
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
|
if (port > 0 && await isDebugPortReady(port, timeoutMs)) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
if (version.webSocketDebuggerUrl)
|
||||||
|
return { port, wsUrl: version.webSocketDebuggerUrl };
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function waitForChromeDebugPort(port, timeoutMs, options) {
|
||||||
|
const start = Date.now();
|
||||||
|
let lastError = null;
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson(`http://127.0.0.1:${port}/json/version`, { timeoutMs: 5000 });
|
||||||
|
if (version.webSocketDebuggerUrl)
|
||||||
|
return version.webSocketDebuggerUrl;
|
||||||
|
lastError = new Error("Missing webSocketDebuggerUrl");
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
await sleep(200);
|
||||||
|
}
|
||||||
|
if (options?.includeLastError && lastError) {
|
||||||
|
throw new Error(`Chrome debug port not ready: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
||||||
|
}
|
||||||
|
throw new Error("Chrome debug port not ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
class CdpConnection {
|
||||||
|
ws;
|
||||||
|
nextId = 0;
|
||||||
|
pending = new Map;
|
||||||
|
eventHandlers = new Map;
|
||||||
|
defaultTimeoutMs;
|
||||||
|
constructor(ws, defaultTimeoutMs = 15000) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.defaultTimeoutMs = defaultTimeoutMs;
|
||||||
|
this.ws.addEventListener("message", (event) => {
|
||||||
|
try {
|
||||||
|
const data = typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data);
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
if (msg.method) {
|
||||||
|
const handlers = this.eventHandlers.get(msg.method);
|
||||||
|
if (handlers) {
|
||||||
|
handlers.forEach((handler) => handler(msg.params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.id) {
|
||||||
|
const pending = this.pending.get(msg.id);
|
||||||
|
if (pending) {
|
||||||
|
this.pending.delete(msg.id);
|
||||||
|
if (pending.timer)
|
||||||
|
clearTimeout(pending.timer);
|
||||||
|
if (msg.error?.message)
|
||||||
|
pending.reject(new Error(msg.error.message));
|
||||||
|
else
|
||||||
|
pending.resolve(msg.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
this.ws.addEventListener("close", () => {
|
||||||
|
for (const [id, pending] of this.pending.entries()) {
|
||||||
|
this.pending.delete(id);
|
||||||
|
if (pending.timer)
|
||||||
|
clearTimeout(pending.timer);
|
||||||
|
pending.reject(new Error("CDP connection closed."));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
static async connect(url, timeoutMs, options) {
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => reject(new Error("CDP connection timeout.")), timeoutMs);
|
||||||
|
ws.addEventListener("open", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
ws.addEventListener("error", () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(new Error("CDP connection failed."));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return new CdpConnection(ws, options?.defaultTimeoutMs ?? 15000);
|
||||||
|
}
|
||||||
|
on(method, handler) {
|
||||||
|
if (!this.eventHandlers.has(method)) {
|
||||||
|
this.eventHandlers.set(method, new Set);
|
||||||
|
}
|
||||||
|
this.eventHandlers.get(method)?.add(handler);
|
||||||
|
}
|
||||||
|
off(method, handler) {
|
||||||
|
this.eventHandlers.get(method)?.delete(handler);
|
||||||
|
}
|
||||||
|
async send(method, params, options) {
|
||||||
|
const id = ++this.nextId;
|
||||||
|
const message = { id, method };
|
||||||
|
if (params)
|
||||||
|
message.params = params;
|
||||||
|
if (options?.sessionId)
|
||||||
|
message.sessionId = options.sessionId;
|
||||||
|
const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
const timer = timeoutMs > 0 ? setTimeout(() => {
|
||||||
|
this.pending.delete(id);
|
||||||
|
reject(new Error(`CDP timeout: ${method}`));
|
||||||
|
}, timeoutMs) : null;
|
||||||
|
this.pending.set(id, { resolve, reject, timer });
|
||||||
|
this.ws.send(JSON.stringify(message));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
try {
|
||||||
|
this.ws.close();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function launchChrome(options) {
|
||||||
|
await fs.promises.mkdir(options.profileDir, { recursive: true });
|
||||||
|
const args = [
|
||||||
|
`--remote-debugging-port=${options.port}`,
|
||||||
|
`--user-data-dir=${options.profileDir}`,
|
||||||
|
"--no-first-run",
|
||||||
|
"--no-default-browser-check",
|
||||||
|
...options.extraArgs ?? []
|
||||||
|
];
|
||||||
|
if (options.headless)
|
||||||
|
args.push("--headless=new");
|
||||||
|
if (options.url)
|
||||||
|
args.push(options.url);
|
||||||
|
return spawn(options.chromePath, args, { stdio: "ignore" });
|
||||||
|
}
|
||||||
|
function killChrome(chrome) {
|
||||||
|
try {
|
||||||
|
chrome.kill("SIGTERM");
|
||||||
|
} catch {}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||||
|
try {
|
||||||
|
chrome.kill("SIGKILL");
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}, 2000).unref?.();
|
||||||
|
}
|
||||||
|
async function gracefulKillChrome(chrome, port, timeoutMs = 6000) {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null)
|
||||||
|
return;
|
||||||
|
const exitPromise = new Promise((resolve) => {
|
||||||
|
chrome.once("exit", () => resolve());
|
||||||
|
});
|
||||||
|
killChrome(chrome);
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (chrome.exitCode !== null || chrome.signalCode !== null)
|
||||||
|
return;
|
||||||
|
if (port !== undefined && !await isPortListening(port, 250))
|
||||||
|
return;
|
||||||
|
const exited = await Promise.race([
|
||||||
|
exitPromise.then(() => true),
|
||||||
|
sleep(100).then(() => false)
|
||||||
|
]);
|
||||||
|
if (exited)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Promise.race([
|
||||||
|
exitPromise,
|
||||||
|
sleep(250)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
async function openPageSession(options) {
|
||||||
|
let targetId;
|
||||||
|
let createdTarget = false;
|
||||||
|
if (options.reusing) {
|
||||||
|
const created = await options.cdp.send("Target.createTarget", { url: options.url });
|
||||||
|
targetId = created.targetId;
|
||||||
|
createdTarget = true;
|
||||||
|
} else {
|
||||||
|
const targets = await options.cdp.send("Target.getTargets");
|
||||||
|
const existing = targets.targetInfos.find(options.matchTarget);
|
||||||
|
if (existing) {
|
||||||
|
targetId = existing.targetId;
|
||||||
|
} else {
|
||||||
|
const created = await options.cdp.send("Target.createTarget", { url: options.url });
|
||||||
|
targetId = created.targetId;
|
||||||
|
createdTarget = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { sessionId } = await options.cdp.send("Target.attachToTarget", { targetId, flatten: true });
|
||||||
|
if (options.activateTarget ?? true) {
|
||||||
|
await options.cdp.send("Target.activateTarget", { targetId });
|
||||||
|
}
|
||||||
|
if (options.enablePage)
|
||||||
|
await options.cdp.send("Page.enable", {}, { sessionId });
|
||||||
|
if (options.enableRuntime)
|
||||||
|
await options.cdp.send("Runtime.enable", {}, { sessionId });
|
||||||
|
if (options.enableDom)
|
||||||
|
await options.cdp.send("DOM.enable", {}, { sessionId });
|
||||||
|
if (options.enableNetwork)
|
||||||
|
await options.cdp.send("Network.enable", {}, { sessionId });
|
||||||
|
return { sessionId, targetId, createdTarget };
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
waitForChromeDebugPort,
|
||||||
|
sleep,
|
||||||
|
resolveSharedChromeProfileDir,
|
||||||
|
openPageSession,
|
||||||
|
launchChrome,
|
||||||
|
killChrome,
|
||||||
|
gracefulKillChrome,
|
||||||
|
getFreePort,
|
||||||
|
getDefaultChromeUserDataDirs,
|
||||||
|
findExistingChromeDebugPort,
|
||||||
|
findChromeExecutable,
|
||||||
|
discoverRunningChromeDebugPort,
|
||||||
|
CdpConnection
|
||||||
|
};
|
||||||
@@ -1,12 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-chrome-cdp",
|
"name": "baoyu-chrome-cdp",
|
||||||
"private": true,
|
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"src"
|
"dist",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"!src/**/*.test.ts"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": {
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./src/*": "./src/*"
|
||||||
|
},
|
||||||
|
"description": "Shared Chrome DevTools Protocol utilities for baoyu skills.",
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/JimLiu/baoyu-skills.git",
|
||||||
|
"directory": "packages/baoyu-chrome-cdp"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/JimLiu/baoyu-skills/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/JimLiu/baoyu-skills/tree/main/packages/baoyu-chrome-cdp#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node ../../scripts/build-shared-package.mjs",
|
||||||
|
"prepack": "bun run build"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-fetch",
|
"name": "baoyu-fetch",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"description": "Read URLs into high-quality Markdown or JSON with Chrome CDP and site adapters.",
|
"description": "Read URLs into high-quality Markdown or JSON with Chrome CDP and site adapters.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"baoyu-fetch": "./src/cli.ts"
|
"baoyu-fetch": "dist/cli.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"dist",
|
||||||
"README.zh-CN.md",
|
"README.zh-CN.md",
|
||||||
"src/adapters",
|
"src/adapters",
|
||||||
"src/browser",
|
"src/browser",
|
||||||
@@ -36,7 +37,8 @@
|
|||||||
"dev": "bun run ./src/cli.ts",
|
"dev": "bun run ./src/cli.ts",
|
||||||
"release": "changeset publish",
|
"release": "changeset publish",
|
||||||
"test": "bun test",
|
"test": "bun test",
|
||||||
"version-packages": "changeset version"
|
"version-packages": "changeset version",
|
||||||
|
"prepack": "bun run build"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"bun": ">=1.2.0"
|
"bun": ">=1.2.0"
|
||||||
|
|||||||
Vendored
+77838
File diff suppressed because one or more lines are too long
Vendored
+77818
File diff suppressed because one or more lines are too long
@@ -1,13 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-md",
|
"name": "baoyu-md",
|
||||||
"private": true,
|
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "./dist/index.cjs",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./src/index.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"src"
|
"dist",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.css",
|
||||||
|
"src/LICENSE",
|
||||||
|
"!src/**/*.test.ts"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": {
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./src/*": "./src/*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node ../../scripts/build-shared-package.mjs --external mermaid --external @antv/infographic --asset src/themes:themes --asset src/code-themes:code-themes",
|
||||||
|
"prepack": "bun run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
@@ -20,5 +36,21 @@
|
|||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-stringify": "^11.0.0",
|
"remark-stringify": "^11.0.0",
|
||||||
"unified": "^11.0.5"
|
"unified": "^11.0.5"
|
||||||
|
},
|
||||||
|
"description": "Shared Markdown rendering and WeChat-friendly HTML utilities for baoyu skills.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/JimLiu/baoyu-skills.git",
|
||||||
|
"directory": "packages/baoyu-md"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/JimLiu/baoyu-skills/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/JimLiu/baoyu-skills/tree/main/packages/baoyu-md#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user