abelworkflow 0.9.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +54 -182
  2. package/bin/abelworkflow.mjs +5 -3
  3. package/extensions/pi-gpt-responses-compat/index.ts +1 -1
  4. package/lib/cli/args.mjs +76 -0
  5. package/lib/cli/main.mjs +385 -0
  6. package/lib/cli/prompts.mjs +106 -0
  7. package/lib/config/dotenv.mjs +102 -0
  8. package/lib/config/jsonc.mjs +80 -0
  9. package/lib/config/store.mjs +310 -0
  10. package/lib/config/toml.mjs +638 -0
  11. package/lib/installer/assets.mjs +324 -0
  12. package/lib/installer/install.mjs +153 -0
  13. package/lib/installer/links.mjs +427 -0
  14. package/lib/installer/state.mjs +163 -0
  15. package/lib/paths.mjs +99 -0
  16. package/lib/providers/claude.mjs +197 -0
  17. package/lib/providers/codex.mjs +433 -0
  18. package/lib/providers/pi.mjs +577 -0
  19. package/lib/providers/skills.mjs +105 -0
  20. package/lib/templates/codex/agents/default.toml +7 -0
  21. package/lib/templates/codex/agents/explorer.toml +7 -0
  22. package/lib/templates/codex/agents/planner.toml +7 -0
  23. package/lib/templates/codex/agents/reviewer.toml +7 -0
  24. package/lib/templates/codex/agents/worker.toml +7 -0
  25. package/lib/templates/codex/config-base.toml +11 -3
  26. package/lib/templates/workflow/AGENTS.md +50 -0
  27. package/lib/templates/workflow/commands/abel-design.md +175 -0
  28. package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
  29. package/lib/templates/workflow/commands/abel-implement.md +170 -0
  30. package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
  31. package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
  32. package/lib/tools/cli-installer.mjs +277 -0
  33. package/package.json +37 -10
  34. package/skills/context7-auto-research/SKILL.md +5 -5
  35. package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
  36. package/skills/dev-browser/SKILL.md +53 -33
  37. package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
  38. package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
  39. package/skills/dev-browser/dist/scripts/start.js +118 -0
  40. package/skills/dev-browser/dist/scripts/start.js.map +1 -0
  41. package/skills/dev-browser/dist/src/client.d.ts +93 -0
  42. package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
  43. package/skills/dev-browser/dist/src/client.js +310 -0
  44. package/skills/dev-browser/dist/src/client.js.map +1 -0
  45. package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
  46. package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
  47. package/skills/dev-browser/dist/src/entrypoint.js +106 -0
  48. package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
  49. package/skills/dev-browser/dist/src/index.d.ts +4 -0
  50. package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
  51. package/skills/dev-browser/dist/src/index.js +2 -0
  52. package/skills/dev-browser/dist/src/index.js.map +1 -0
  53. package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
  54. package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
  55. package/skills/dev-browser/dist/src/page-api.js +104 -0
  56. package/skills/dev-browser/dist/src/page-api.js.map +1 -0
  57. package/skills/dev-browser/dist/src/relay.d.ts +27 -0
  58. package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
  59. package/skills/dev-browser/dist/src/relay.js +521 -0
  60. package/skills/dev-browser/dist/src/relay.js.map +1 -0
  61. package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
  62. package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
  63. package/skills/dev-browser/dist/src/runtime.js +54 -0
  64. package/skills/dev-browser/dist/src/runtime.js.map +1 -0
  65. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
  66. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
  67. package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
  68. package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
  69. package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
  70. package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
  71. package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
  72. package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
  73. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
  74. package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
  75. package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
  76. package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
  77. package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
  78. package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
  79. package/skills/dev-browser/dist/src/standalone.js +174 -0
  80. package/skills/dev-browser/dist/src/standalone.js.map +1 -0
  81. package/skills/dev-browser/dist/src/startup.d.ts +55 -0
  82. package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
  83. package/skills/dev-browser/dist/src/startup.js +81 -0
  84. package/skills/dev-browser/dist/src/startup.js.map +1 -0
  85. package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
  86. package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
  87. package/skills/dev-browser/dist/src/target-registry.js +135 -0
  88. package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
  89. package/skills/dev-browser/dist/src/types.d.ts +27 -0
  90. package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
  91. package/skills/dev-browser/dist/src/types.js +2 -0
  92. package/skills/dev-browser/dist/src/types.js.map +1 -0
  93. package/skills/dev-browser/package-lock.json +67 -1510
  94. package/skills/dev-browser/package.json +18 -14
  95. package/skills/dev-browser/references/scraping.md +94 -105
  96. package/skills/grok-search/.env.example +3 -3
  97. package/skills/grok-search/SKILL.md +1 -1
  98. package/skills/grok-search/defaults.json +3 -0
  99. package/skills/grok-search/gitignore.template +4 -0
  100. package/skills/grok-search/scripts/_dotenv.py +5 -1
  101. package/skills/grok-search/scripts/groksearch_cli.py +15 -33
  102. package/skills/prompt-enhancer/SKILL.md +2 -27
  103. package/skills/prompt-enhancer/TEMPLATE.md +0 -4
  104. package/.skill-lock.json +0 -29
  105. package/AGENTS.md +0 -45
  106. package/commands/abel-implement.md +0 -157
  107. package/commands/abel-plan.md +0 -88
  108. package/commands/abel-research.md +0 -126
  109. package/lib/cli/logic.mjs +0 -213
  110. package/lib/cli.mjs +0 -3016
  111. package/skills/confidence-check/SKILL.md +0 -34
  112. package/skills/confidence-check/confidence.ts +0 -276
  113. package/skills/dev-browser/bun.lock +0 -460
  114. package/skills/dev-browser/scripts/start.ts +0 -280
  115. package/skills/dev-browser/src/client.ts +0 -474
  116. package/skills/dev-browser/src/entrypoint.ts +0 -157
  117. package/skills/dev-browser/src/index.ts +0 -289
  118. package/skills/dev-browser/src/relay.ts +0 -731
  119. package/skills/dev-browser/src/runtime.test.ts +0 -60
  120. package/skills/dev-browser/src/runtime.ts +0 -171
  121. package/skills/dev-browser/src/startup.test.ts +0 -95
  122. package/skills/dev-browser/src/startup.ts +0 -153
  123. package/skills/dev-browser/src/types.ts +0 -35
  124. package/skills/dev-browser/tsconfig.json +0 -36
  125. package/skills/dev-browser/vitest.config.ts +0 -12
  126. package/skills/prompt-enhancer/.env.example +0 -12
  127. package/skills/prompt-enhancer/ADVANCED.md +0 -103
  128. package/skills/prompt-enhancer/requirements.txt +0 -2
  129. package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
  130. package/skills/prompt-enhancer/scripts/enhance.py +0 -191
  131. package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
  132. package/skills/sequential-think/SKILL.md +0 -198
  133. package/skills/sequential-think/scripts/.env.example +0 -5
  134. package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
@@ -0,0 +1,277 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import c from "picocolors";
5
+ import { defaultPaths, getPlatform, isWindows } from "../paths.mjs";
6
+
7
+ const { homeDir: home } = defaultPaths;
8
+
9
+ function getRunCommandSpawnOptions(platform = getPlatform()) {
10
+ return { stdio: "inherit", shell: platform === "win32" };
11
+ }
12
+
13
+ function getCommandPath(command) {
14
+ const checker = isWindows() ? "where" : "which";
15
+ const result = spawnSync(checker, [command], { encoding: "utf8" });
16
+ if (result.status !== 0) {
17
+ return undefined;
18
+ }
19
+ return result.stdout
20
+ .split(/\r?\n/u)
21
+ .map((line) => line.trim())
22
+ .find(Boolean);
23
+ }
24
+
25
+ function commandExists(command) {
26
+ return Boolean(getCommandPath(command));
27
+ }
28
+
29
+ function readCommandOutput(command, args) {
30
+ const result = spawnSync(command, args, {
31
+ encoding: "utf8",
32
+ shell: isWindows(),
33
+ stdio: ["ignore", "pipe", "ignore"]
34
+ });
35
+ if (result.status !== 0) {
36
+ return undefined;
37
+ }
38
+ const outputValue = result.stdout.trim();
39
+ return outputValue || undefined;
40
+ }
41
+
42
+ async function runCommand(command, args) {
43
+ await new Promise((resolvePromise, rejectPromise) => {
44
+ const child = spawn(command, args, getRunCommandSpawnOptions(getPlatform()));
45
+ child.on("error", rejectPromise);
46
+ child.on("close", (code) => {
47
+ if (code === 0) {
48
+ resolvePromise();
49
+ return;
50
+ }
51
+ rejectPromise(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
52
+ });
53
+ });
54
+ }
55
+
56
+ function normalizeInstallPath(value) {
57
+ if (!value) {
58
+ return "";
59
+ }
60
+ return value.replace(/\\/gu, "/").replace(/\/+$/u, "").toLowerCase();
61
+ }
62
+
63
+ function installPathIsWithin(path, parentPath) {
64
+ const normalizedPath = normalizeInstallPath(path);
65
+ const normalizedParent = normalizeInstallPath(parentPath);
66
+ return Boolean(normalizedPath && normalizedParent)
67
+ && (normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`));
68
+ }
69
+
70
+ function getNpmGlobalBinDirs(npmGlobalPrefix, platform = getPlatform()) {
71
+ if (!npmGlobalPrefix) {
72
+ return [];
73
+ }
74
+ return platform === "win32" ? [npmGlobalPrefix] : [join(npmGlobalPrefix, "bin")];
75
+ }
76
+
77
+ function inferPackageManagerFromCommandPath(commandPath, {
78
+ bunGlobalBinDir,
79
+ npmGlobalPrefix,
80
+ platform = getPlatform()
81
+ } = {}) {
82
+ if (installPathIsWithin(commandPath, bunGlobalBinDir)) {
83
+ return "bun";
84
+ }
85
+ if (getNpmGlobalBinDirs(npmGlobalPrefix, platform)
86
+ .some((binDir) => installPathIsWithin(commandPath, binDir))) {
87
+ return "npm";
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function chooseCliInstallPackageManager({
93
+ commandPath,
94
+ availablePackageManagers = [],
95
+ bunGlobalBinDir,
96
+ npmGlobalPrefix,
97
+ platform = getPlatform(),
98
+ nodeAvailable = true,
99
+ supportedPackageManagers = ["bun", "npm"]
100
+ } = {}) {
101
+ const supported = new Set(supportedPackageManagers);
102
+ const available = new Set(availablePackageManagers.filter((packageManager) => {
103
+ if (!supported.has(packageManager)) {
104
+ return false;
105
+ }
106
+ return packageManager !== "bun" || nodeAvailable;
107
+ }));
108
+ const bunBlockedByMissingNode = !nodeAvailable
109
+ && supported.has("bun")
110
+ && availablePackageManagers.includes("bun");
111
+ const existingPackageManager = inferPackageManagerFromCommandPath(commandPath, {
112
+ bunGlobalBinDir,
113
+ npmGlobalPrefix,
114
+ platform
115
+ });
116
+
117
+ if (existingPackageManager && available.has(existingPackageManager)) {
118
+ return { packageManager: existingPackageManager, source: "existing" };
119
+ }
120
+ for (const packageManager of ["bun", "npm"]) {
121
+ if (available.has(packageManager)) {
122
+ return { packageManager, source: "available" };
123
+ }
124
+ }
125
+ return {
126
+ packageManager: null,
127
+ source: bunBlockedByMissingNode ? "missing-node" : (availablePackageManagers.length > 0 ? "unsupported" : "missing")
128
+ };
129
+ }
130
+
131
+ function buildCliToolInstallCommand(packageManager, { packageName, skipScripts = false }) {
132
+ if (packageManager === "npm") {
133
+ return {
134
+ command: "npm",
135
+ args: [
136
+ "install",
137
+ "-g",
138
+ ...(skipScripts ? ["--ignore-scripts"] : []),
139
+ packageName,
140
+ "--force"
141
+ ]
142
+ };
143
+ }
144
+ if (packageManager === "bun") {
145
+ return {
146
+ command: "bun",
147
+ args: [
148
+ "install",
149
+ "-g",
150
+ ...(skipScripts ? ["--ignore-scripts"] : []),
151
+ packageName
152
+ ]
153
+ };
154
+ }
155
+ throw new Error(`Unsupported package manager: ${packageManager}`);
156
+ }
157
+
158
+ function getPackageManagerInstallHelp(platform = getPlatform()) {
159
+ const platformLabel = {
160
+ darwin: "macOS",
161
+ win32: "Windows",
162
+ linux: "Linux"
163
+ }[platform] || platform;
164
+ return {
165
+ platformLabel,
166
+ mainlandUrl: "https://npmmirror.com/mirrors/node/",
167
+ officialUrl: "https://nodejs.org/en/download/"
168
+ };
169
+ }
170
+
171
+ function getAvailablePackageManagers() {
172
+ return ["bun", "npm"].filter((packageManager) => commandExists(packageManager));
173
+ }
174
+
175
+ function getBunGlobalBinDir(availablePackageManagers = getAvailablePackageManagers()) {
176
+ if (!availablePackageManagers.includes("bun")) {
177
+ return undefined;
178
+ }
179
+ return readCommandOutput("bun", ["pm", "bin", "-g"]) || join(home, ".bun", "bin");
180
+ }
181
+
182
+ function getNpmGlobalPrefix(availablePackageManagers = getAvailablePackageManagers()) {
183
+ if (!availablePackageManagers.includes("npm")) {
184
+ return undefined;
185
+ }
186
+ return readCommandOutput("npm", ["prefix", "-g"]);
187
+ }
188
+
189
+ async function installCliTool(tool, { confirmOrCancel }) {
190
+ const toolConfig = {
191
+ claude: {
192
+ label: "Claude Code",
193
+ command: "claude",
194
+ packageName: "@anthropic-ai/claude-code",
195
+ supportedPackageManagers: ["npm"],
196
+ installRequirement: "Claude Code 安装需要执行 postinstall;Bun 默认会阻止该脚本,因此需使用 npm。"
197
+ },
198
+ codex: {
199
+ label: "Codex",
200
+ command: "codex",
201
+ packageName: "@openai/codex"
202
+ },
203
+ pi: {
204
+ label: "Pi",
205
+ command: "pi",
206
+ packageName: "@earendil-works/pi-coding-agent",
207
+ skipScripts: true
208
+ }
209
+ }[tool];
210
+
211
+ if (!toolConfig) {
212
+ throw new Error(`Unsupported tool: ${tool}`);
213
+ }
214
+
215
+ const commandPath = getCommandPath(toolConfig.command);
216
+ const installed = Boolean(commandPath);
217
+ const availablePackageManagers = getAvailablePackageManagers();
218
+ const packageManagerChoice = chooseCliInstallPackageManager({
219
+ commandPath,
220
+ availablePackageManagers,
221
+ bunGlobalBinDir: getBunGlobalBinDir(availablePackageManagers),
222
+ npmGlobalPrefix: getNpmGlobalPrefix(availablePackageManagers),
223
+ platform: getPlatform(),
224
+ nodeAvailable: commandExists("node"),
225
+ supportedPackageManagers: toolConfig.supportedPackageManagers
226
+ });
227
+
228
+ if (!packageManagerChoice.packageManager) {
229
+ const help = getPackageManagerInstallHelp();
230
+ p.log.warn(`未检测到可用于安装 ${toolConfig.label} 的包管理器。`);
231
+ if (packageManagerChoice.source === "missing-node") {
232
+ p.log.message(`${toolConfig.label} 是 Node CLI;使用 Bun 安装前也需要先安装 Node.js。`);
233
+ }
234
+ if (toolConfig.installRequirement) {
235
+ p.log.message(toolConfig.installRequirement);
236
+ }
237
+ p.log.message(`${help.platformLabel} 可先安装 Node.js/npm,再重新运行安装。`);
238
+ p.log.message(`中国大陆镜像: ${help.mainlandUrl}`);
239
+ p.log.message(`官方下载页: ${help.officialUrl}`);
240
+ return;
241
+ }
242
+
243
+ if (installed) {
244
+ const managerMessage = packageManagerChoice.source === "existing"
245
+ ? `检测到原安装方式为 ${packageManagerChoice.packageManager}`
246
+ : `未识别原安装方式,将使用 ${packageManagerChoice.packageManager}`;
247
+ const shouldUpdate = await confirmOrCancel({
248
+ message: `${toolConfig.label} 已检测到(${managerMessage}),是否继续安装/更新?`,
249
+ initialValue: false
250
+ });
251
+ if (!shouldUpdate) {
252
+ p.log.message(`跳过 ${toolConfig.label} 安装。`);
253
+ return;
254
+ }
255
+ }
256
+
257
+ const installCommand = buildCliToolInstallCommand(packageManagerChoice.packageManager, toolConfig);
258
+ const s = p.spinner();
259
+ s.start(`正在使用 ${packageManagerChoice.packageManager} 安装 ${toolConfig.label}...`);
260
+ try {
261
+ await runCommand(installCommand.command, installCommand.args);
262
+ s.stop(`${toolConfig.label} 安装完成`);
263
+ } catch (e) {
264
+ s.cancel(c.red(`${toolConfig.label} 安装失败: ${e.message}`));
265
+ throw e;
266
+ }
267
+ }
268
+
269
+ export {
270
+ buildCliToolInstallCommand,
271
+ chooseCliInstallPackageManager,
272
+ getPackageManagerInstallHelp,
273
+ getRunCommandSpawnOptions,
274
+ inferPackageManagerFromCommandPath,
275
+ installCliTool,
276
+ commandExists
277
+ };
package/package.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "name": "abelworkflow",
3
- "version": "0.9.2",
3
+ "version": "1.0.0",
4
4
  "description": "Install AbelWorkflow into ~/.agents and create Claude/Codex symlinks.",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "test": "node --test test/runtime-doc-contracts.test.mjs test/cli-contracts.test.mjs test/codex-config.test.mjs test/pi-auth-precedence.test.mjs test/pi-auth-write.test.mjs test/pi-provider-tls.test.mjs",
8
- "test:contracts": "node --test test/runtime-doc-contracts.test.mjs test/cli-contracts.test.mjs"
7
+ "test": "npm run test:node",
8
+ "test:node": "node --test test/*.test.mjs",
9
+ "test:python": "python -m unittest discover -s test",
10
+ "test:dev-browser": "npm test --prefix skills/dev-browser",
11
+ "test:contracts": "node --test test/runtime-doc-contracts.test.mjs test/cli-args.test.mjs",
12
+ "typecheck": "npm run typecheck --prefix skills/dev-browser",
13
+ "build": "npm run build --prefix skills/dev-browser",
14
+ "prepack": "npm run build",
15
+ "check:docs": "node --test test/docs-contracts.test.mjs test/runtime-doc-contracts.test.mjs",
16
+ "check:package": "node --test test/package-contents.test.mjs",
17
+ "check": "npm run build && npm run test:node && npm run test:python && npm run test:dev-browser && npm run typecheck && npm run check:docs && npm run check:package"
9
18
  },
10
19
  "bin": {
11
20
  "abelworkflow": "bin/abelworkflow.mjs"
@@ -13,21 +22,39 @@
13
22
  "files": [
14
23
  "bin",
15
24
  "lib",
16
- "AGENTS.md",
17
25
  "README.md",
18
- "commands",
19
- "skills",
20
26
  "extensions",
21
- ".skill-lock.json",
22
- ".gitignore"
27
+ "skills/context7-auto-research/.env.example",
28
+ "skills/context7-auto-research/SKILL.md",
29
+ "skills/context7-auto-research/context7-api.cjs",
30
+ "skills/dev-browser/SKILL.md",
31
+ "skills/dev-browser/dist",
32
+ "skills/dev-browser/package-lock.json",
33
+ "skills/dev-browser/package.json",
34
+ "skills/dev-browser/references",
35
+ "skills/git-commit/SKILL.md",
36
+ "skills/grok-search/.env.example",
37
+ "skills/grok-search/SKILL.md",
38
+ "skills/grok-search/defaults.json",
39
+ "skills/grok-search/gitignore.template",
40
+ "skills/grok-search/requirements.txt",
41
+ "skills/grok-search/scripts/_dotenv.py",
42
+ "skills/grok-search/scripts/groksearch_cli.py",
43
+ "skills/grok-search/scripts/groksearch_entry.py",
44
+ "skills/prompt-enhancer/SKILL.md",
45
+ "skills/prompt-enhancer/TEMPLATE.md",
46
+ "skills/time/SKILL.md",
47
+ "skills/time/scripts/time_cli.py"
23
48
  ],
24
49
  "engines": {
25
- "node": ">=18"
50
+ "node": ">=22"
26
51
  },
52
+ "packageManager": "npm@10.9.2",
27
53
  "license": "MIT",
28
54
  "dependencies": {
29
55
  "@clack/prompts": "^1.2.0",
30
56
  "picocolors": "^1.1.1",
31
- "proper-lockfile": "4.1.2"
57
+ "proper-lockfile": "4.1.2",
58
+ "smol-toml": "1.7.0"
32
59
  }
33
60
  }
@@ -30,13 +30,13 @@ Common cues:
30
30
 
31
31
  ## Workflow
32
32
 
33
- 1. Run from this skill directory and use `./context7-api.js`.
33
+ 1. Run from this skill directory and use `./context7-api.cjs`.
34
34
  2. Search with the full user query:
35
35
 
36
36
  ```bash
37
37
  cd skills/context7-auto-research
38
- node ./context7-api.js --help
39
- node ./context7-api.js search '<library-name>' - <<'__C7_QUERY__'
38
+ node ./context7-api.cjs --help
39
+ node ./context7-api.cjs search '<library-name>' - <<'__C7_QUERY__'
40
40
  <full user query>
41
41
  __C7_QUERY__
42
42
  ```
@@ -50,7 +50,7 @@ __C7_QUERY__
50
50
  4. Fetch focused documentation:
51
51
 
52
52
  ```bash
53
- node ./context7-api.js context '<library-id>' - <<'__C7_QUERY__'
53
+ node ./context7-api.cjs context '<library-id>' - <<'__C7_QUERY__'
54
54
  <feature name or focused question>
55
55
  __C7_QUERY__
56
56
  ```
@@ -72,7 +72,7 @@ __C7_QUERY__
72
72
 
73
73
  - Preferred: `CONTEXT7_API_KEY` environment variable
74
74
  - Also supported: `<skill-dir>/.env`
75
- - CLI help: `node ./context7-api.js --help`
75
+ - CLI help: `node ./context7-api.cjs --help`
76
76
 
77
77
  ## Notes
78
78
 
@@ -44,12 +44,12 @@ function printErrorResult(commandName, error, exitCode = 1) {
44
44
 
45
45
  function printUsage() {
46
46
  console.error(`Usage:
47
- context7-api.js search <libraryName> <query|->
48
- context7-api.js context <libraryId> <query|->
47
+ context7-api.cjs search <libraryName> <query|->
48
+ context7-api.cjs context <libraryId> <query|->
49
49
 
50
50
  Examples:
51
- node context7-api.js search react "useEffect cleanup"
52
- node context7-api.js context /facebook/react -
51
+ node context7-api.cjs search react "useEffect cleanup"
52
+ node context7-api.cjs context /facebook/react -
53
53
 
54
54
  Config:
55
55
  CONTEXT7_API_KEY from environment, or .env in the script directory`);
@@ -83,10 +83,7 @@ function loadApiKey() {
83
83
  if (key !== 'CONTEXT7_API_KEY') {
84
84
  continue;
85
85
  }
86
- const value = trimmed.slice(separatorIndex + 1).trim();
87
- if (value) {
88
- return value.replace(/^["']|["']$/g, '');
89
- }
86
+ return trimmed.slice(separatorIndex + 1).trim().replace(/^["']|["']$/g, '');
90
87
  }
91
88
 
92
89
  return null;
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: dev-browser
3
- description: Browser automation with persistent page state. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request.
3
+ description: Browser automation with page state shared while the server process runs. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request.
4
4
  ---
5
5
 
6
6
  # Dev Browser Skill
7
7
 
8
- Browser automation that maintains page state across script executions. Write small, focused scripts to accomplish tasks incrementally. Once you've proven out part of a workflow and there is repeated work to be done, you can write a script to do the repeated work in a single execution.
8
+ Browser automation that maintains page state across script executions while the server process runs. Write small, focused scripts to accomplish tasks incrementally.
9
9
 
10
10
  ## Choosing Your Approach
11
11
 
@@ -22,13 +22,13 @@ Two supported startup modes are available on **Linux** and **native Windows**. R
22
22
  Launches a new Chromium browser for fresh automation sessions.
23
23
 
24
24
  ```text
25
- npx tsx scripts/start.ts standalone
25
+ node dist/scripts/start.js standalone
26
26
  ```
27
27
 
28
28
  Add `--headless` if needed:
29
29
 
30
30
  ```text
31
- npx tsx scripts/start.ts standalone --headless
31
+ node dist/scripts/start.js standalone --headless
32
32
  ```
33
33
 
34
34
  Wait for the stable readiness line `Ready` before running scripts.
@@ -43,7 +43,7 @@ Connects to the user's existing Chrome browser. Use this when:
43
43
  Start the relay server with:
44
44
 
45
45
  ```text
46
- npx tsx scripts/start.ts extension
46
+ node dist/scripts/start.js extension
47
47
  ```
48
48
 
49
49
  Wait for `Waiting for extension to connect...`. Once the browser extension attaches, the relay logs `Extension connected`.
@@ -54,25 +54,32 @@ If the extension hasn't connected yet, tell the user to launch and activate it.
54
54
 
55
55
  | Mode | Linux | Native Windows | Readiness signal | Notes |
56
56
  |------|-------|----------------|------------------|-------|
57
- | standalone mode | Supported | Supported | `Ready` | Launches a managed Chromium profile under `profiles/` |
57
+ | standalone mode | Supported | Supported | `Ready` | Uses a process-owned temporary Chromium profile and cleans it on exit |
58
58
  | extension mode | Supported | Supported | `Waiting for extension to connect...` then `Extension connected` | Requires the external browser extension to attach |
59
59
 
60
60
  ## Verification Checklist
61
61
 
62
- - Start `standalone mode` with `npx tsx scripts/start.ts standalone`
62
+ - Start `standalone mode` with `node dist/scripts/start.js standalone`
63
63
  - Observe the readiness line `Ready`
64
64
  - Connect with `connect()` and create a named page
65
- - Start `extension mode` with `npx tsx scripts/start.ts extension`
65
+ - Start `extension mode` with `node dist/scripts/start.js extension`
66
66
  - Observe `Waiting for extension to connect...`
67
67
  - Attach the browser extension and confirm `Extension connected`
68
68
 
69
69
  ## Known Differences
70
70
 
71
- - `standalone mode` launches and owns its own Chromium profile under this skill directory.
71
+ - `standalone mode` owns a temporary Chromium profile only for the server process lifetime and cleans it on exit.
72
72
  - `extension mode` depends on the external browser extension and the user's existing Chrome session.
73
73
  - In `extension mode`, relay readiness means the server is waiting for the extension; it does not imply browser control is available until `Extension connected` appears.
74
74
  - `standalone mode` is the default path for deterministic local automation; `extension mode` is for working inside an already-authenticated browser.
75
75
 
76
+ ## Relay Origin Boundary
77
+
78
+ - State-changing `/pages` requests accept only originless JSON clients; browser-origin and non-JSON mutations are rejected before backend calls.
79
+ - `/cdp` accepts only originless WebSocket clients, which is the normal behavior of the Node client. Browser pages always send an `Origin` and are rejected.
80
+ - `/extension` accepts originless protocol clients and valid `chrome-extension://` origins. HTTP(S), `null`, malformed, and other origins are rejected.
81
+ - Rejected sockets close with WebSocket policy code `1008` before they can own a client ID, replace the extension, or route buffered messages.
82
+
76
83
  ## Non-goal Environments
77
84
 
78
85
  - `WSL` and `Git Bash` are not part of the supported Windows path for this skill.
@@ -81,37 +88,45 @@ If the extension hasn't connected yet, tell the user to launch and activate it.
81
88
 
82
89
  ## Writing Scripts
83
90
 
84
- Use small TypeScript files under `tmp/` instead of shell heredocs. Run them from the same `dev-browser` skill directory so the `@/` import alias resolves correctly.
91
+ Treat the installed `dev-browser` skill directory as read-only. Store scripts and artifacts in the OS temporary directory or task workspace, never inside the skill directory, and delete files you created when the task ends.
85
92
 
86
- Example script (`tmp/example.ts`):
93
+ External scripts cannot use package self-resolution, so pass the installed skill directory as the first argument and import its compiled client by file URL. Example:
87
94
 
88
- ```typescript
89
- import { connect, waitForPageLoad } from "@/client.js";
95
+ ```javascript
96
+ import { tmpdir } from "node:os";
97
+ import { join } from "node:path";
98
+ import { pathToFileURL } from "node:url";
99
+
100
+ const skillDir = process.argv[2];
101
+ if (!skillDir) throw new Error("Pass the installed dev-browser skill directory");
102
+ const { connect, waitForPageLoad } = await import(
103
+ pathToFileURL(join(skillDir, "dist", "src", "client.js")).href
104
+ );
90
105
 
91
106
  const client = await connect();
92
107
  const page = await client.page("example", { viewport: { width: 1920, height: 1080 } });
93
108
 
94
109
  await page.goto("https://example.com");
95
110
  await waitForPageLoad(page);
111
+ const screenshotPath = join(tmpdir(), "dev-browser-screenshot.png");
112
+ await page.screenshot({ path: screenshotPath });
96
113
 
97
- console.log({ title: await page.title(), url: page.url() });
114
+ console.log({ title: await page.title(), url: page.url(), screenshotPath });
98
115
  await client.disconnect();
99
116
  ```
100
117
 
101
- Run it with:
118
+ This invocation works in Linux shells, PowerShell, and Command Prompt when the two paths are quoted:
102
119
 
103
120
  ```text
104
- npx tsx tmp/example.ts
121
+ node "PATH_TO_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
105
122
  ```
106
123
 
107
- **Write to `tmp/` files only when** the script needs reuse, is complex, or user explicitly requests it.
108
-
109
124
  ### Key Principles
110
125
 
111
126
  1. **Small scripts**: Each script does ONE thing (navigate, click, fill, check)
112
127
  2. **Evaluate state**: Log/return state at the end to decide next steps
113
128
  3. **Descriptive page names**: Use `"checkout"`, `"login"`, not `"main"`
114
- 4. **Disconnect to exit**: `await client.disconnect()` - pages persist on server
129
+ 4. **Disconnect to exit**: `await client.disconnect()` - pages persist while the server process runs
115
130
  5. **Plain JS in evaluate**: `page.evaluate()` runs in browser - no TypeScript syntax
116
131
 
117
132
  ## Workflow Loop
@@ -156,7 +171,7 @@ const pageWithSize = await client.page("name", { viewport: { width: 1920, height
156
171
 
157
172
  const pages = await client.list(); // List all page names
158
173
  await client.close("name"); // Close a page
159
- await client.disconnect(); // Disconnect (pages persist)
174
+ await client.disconnect(); // Disconnect (pages persist while the server runs)
160
175
 
161
176
  // ARIA Snapshot methods
162
177
  const snapshot = await client.getAISnapshot("name"); // Get accessibility tree
@@ -167,9 +182,7 @@ The `page` object is a standard Playwright Page.
167
182
 
168
183
  ## Waiting
169
184
 
170
- ```typescript
171
- import { waitForPageLoad } from "@/client.js";
172
-
185
+ ```javascript
173
186
  await waitForPageLoad(page); // After navigation
174
187
  await page.waitForSelector(".results"); // For specific elements
175
188
  await page.waitForURL("**/success"); // For specific URL
@@ -179,11 +192,18 @@ await page.waitForURL("**/success"); // For specific URL
179
192
 
180
193
  ### Screenshots
181
194
 
182
- ```typescript
183
- await page.screenshot({ path: "tmp/screenshot.png" });
184
- await page.screenshot({ path: "tmp/full.png", fullPage: true });
195
+ ```javascript
196
+ import { tmpdir } from "node:os";
197
+ import { join } from "node:path";
198
+
199
+ const screenshotPath = join(tmpdir(), "dev-browser-screenshot.png");
200
+ const fullScreenshotPath = join(tmpdir(), "dev-browser-full.png");
201
+ await page.screenshot({ path: screenshotPath });
202
+ await page.screenshot({ path: fullScreenshotPath, fullPage: true });
185
203
  ```
186
204
 
205
+ Inspect the files, then remove them no later than task completion.
206
+
187
207
  ### ARIA Snapshot (Element Discovery)
188
208
 
189
209
  Use `getAISnapshot()` to discover page elements. Returns YAML-formatted accessibility tree:
@@ -222,26 +242,26 @@ await element.click();
222
242
 
223
243
  ## Error Recovery
224
244
 
225
- Page state persists after failures. To inspect the current state, save a short script such as `tmp/debug.ts` and run it from the `dev-browser` skill directory:
226
-
227
- ```typescript
228
- import { connect } from "@/client.js";
245
+ Page state persists after failures while the server process runs. Reuse the external script pattern above from the OS temporary directory or task workspace:
229
246
 
247
+ ```javascript
230
248
  const client = await connect();
231
249
  const page = await client.page("hackernews");
232
250
 
233
- await page.screenshot({ path: "tmp/debug.png" });
251
+ const debugScreenshotPath = join(tmpdir(), "dev-browser-debug.png");
252
+ await page.screenshot({ path: debugScreenshotPath });
234
253
  console.log({
235
254
  url: page.url(),
236
255
  title: await page.title(),
237
256
  bodyText: await page.textContent("body").then((t) => t?.slice(0, 200)),
257
+ debugScreenshotPath,
238
258
  });
239
259
 
240
260
  await client.disconnect();
241
261
  ```
242
262
 
243
- Run it with:
263
+ Run it with the same cross-platform invocation and delete the debug script and screenshot when finished:
244
264
 
245
265
  ```text
246
- npx tsx tmp/debug.ts
266
+ node "PATH_TO_DEBUG_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
247
267
  ```
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../scripts/start.ts"],"names":[],"mappings":""}