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.
- package/README.md +54 -182
- package/bin/abelworkflow.mjs +5 -3
- package/extensions/pi-gpt-responses-compat/index.ts +1 -1
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +385 -0
- package/lib/cli/prompts.mjs +106 -0
- package/lib/config/dotenv.mjs +102 -0
- package/lib/config/jsonc.mjs +80 -0
- package/lib/config/store.mjs +310 -0
- package/lib/config/toml.mjs +638 -0
- package/lib/installer/assets.mjs +324 -0
- package/lib/installer/install.mjs +153 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/state.mjs +163 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +197 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +577 -0
- package/lib/providers/skills.mjs +105 -0
- package/lib/templates/codex/agents/default.toml +7 -0
- package/lib/templates/codex/agents/explorer.toml +7 -0
- package/lib/templates/codex/agents/planner.toml +7 -0
- package/lib/templates/codex/agents/reviewer.toml +7 -0
- package/lib/templates/codex/agents/worker.toml +7 -0
- package/lib/templates/codex/config-base.toml +11 -3
- package/lib/templates/workflow/AGENTS.md +50 -0
- package/lib/templates/workflow/commands/abel-design.md +175 -0
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/lib/templates/workflow/commands/abel-implement.md +170 -0
- package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +37 -10
- package/skills/context7-auto-research/SKILL.md +5 -5
- package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
- package/skills/dev-browser/SKILL.md +53 -33
- package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
- package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
- package/skills/dev-browser/dist/scripts/start.js +118 -0
- package/skills/dev-browser/dist/scripts/start.js.map +1 -0
- package/skills/dev-browser/dist/src/client.d.ts +93 -0
- package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/client.js +310 -0
- package/skills/dev-browser/dist/src/client.js.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.js +106 -0
- package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
- package/skills/dev-browser/dist/src/index.d.ts +4 -0
- package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/index.js +2 -0
- package/skills/dev-browser/dist/src/index.js.map +1 -0
- package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
- package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/page-api.js +104 -0
- package/skills/dev-browser/dist/src/page-api.js.map +1 -0
- package/skills/dev-browser/dist/src/relay.d.ts +27 -0
- package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/relay.js +521 -0
- package/skills/dev-browser/dist/src/relay.js.map +1 -0
- package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
- package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/runtime.js +54 -0
- package/skills/dev-browser/dist/src/runtime.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
- package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
- package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/standalone.js +174 -0
- package/skills/dev-browser/dist/src/standalone.js.map +1 -0
- package/skills/dev-browser/dist/src/startup.d.ts +55 -0
- package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/startup.js +81 -0
- package/skills/dev-browser/dist/src/startup.js.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.js +135 -0
- package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
- package/skills/dev-browser/dist/src/types.d.ts +27 -0
- package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/types.js +2 -0
- package/skills/dev-browser/dist/src/types.js.map +1 -0
- package/skills/dev-browser/package-lock.json +67 -1510
- package/skills/dev-browser/package.json +18 -14
- package/skills/dev-browser/references/scraping.md +94 -105
- package/skills/grok-search/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- package/skills/grok-search/defaults.json +3 -0
- package/skills/grok-search/gitignore.template +4 -0
- package/skills/grok-search/scripts/_dotenv.py +5 -1
- package/skills/grok-search/scripts/groksearch_cli.py +15 -33
- package/skills/prompt-enhancer/SKILL.md +2 -27
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/.skill-lock.json +0 -29
- package/AGENTS.md +0 -45
- package/commands/abel-implement.md +0 -157
- package/commands/abel-plan.md +0 -88
- package/commands/abel-research.md +0 -126
- package/lib/cli/logic.mjs +0 -213
- package/lib/cli.mjs +0 -3016
- package/skills/confidence-check/SKILL.md +0 -34
- package/skills/confidence-check/confidence.ts +0 -276
- package/skills/dev-browser/bun.lock +0 -460
- package/skills/dev-browser/scripts/start.ts +0 -280
- package/skills/dev-browser/src/client.ts +0 -474
- package/skills/dev-browser/src/entrypoint.ts +0 -157
- package/skills/dev-browser/src/index.ts +0 -289
- package/skills/dev-browser/src/relay.ts +0 -731
- package/skills/dev-browser/src/runtime.test.ts +0 -60
- package/skills/dev-browser/src/runtime.ts +0 -171
- package/skills/dev-browser/src/startup.test.ts +0 -95
- package/skills/dev-browser/src/startup.ts +0 -153
- package/skills/dev-browser/src/types.ts +0 -35
- package/skills/dev-browser/tsconfig.json +0 -36
- package/skills/dev-browser/vitest.config.ts +0 -12
- package/skills/prompt-enhancer/.env.example +0 -12
- package/skills/prompt-enhancer/ADVANCED.md +0 -103
- package/skills/prompt-enhancer/requirements.txt +0 -2
- package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
- package/skills/prompt-enhancer/scripts/enhance.py +0 -191
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
- package/skills/sequential-think/SKILL.md +0 -198
- package/skills/sequential-think/scripts/.env.example +0 -5
- 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.
|
|
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": "
|
|
8
|
-
"test:
|
|
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
|
-
"
|
|
22
|
-
".
|
|
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": ">=
|
|
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.
|
|
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.
|
|
39
|
-
node ./context7-api.
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
-
context7-api.
|
|
47
|
+
context7-api.cjs search <libraryName> <query|->
|
|
48
|
+
context7-api.cjs context <libraryId> <query|->
|
|
49
49
|
|
|
50
50
|
Examples:
|
|
51
|
-
node context7-api.
|
|
52
|
-
node context7-api.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
25
|
+
node dist/scripts/start.js standalone
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Add `--headless` if needed:
|
|
29
29
|
|
|
30
30
|
```text
|
|
31
|
-
|
|
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
|
-
|
|
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` |
|
|
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 `
|
|
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 `
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
89
|
-
import {
|
|
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
|
-
|
|
118
|
+
This invocation works in Linux shells, PowerShell, and Command Prompt when the two paths are quoted:
|
|
102
119
|
|
|
103
120
|
```text
|
|
104
|
-
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
266
|
+
node "PATH_TO_DEBUG_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
|
|
247
267
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../scripts/start.ts"],"names":[],"mappings":""}
|