abelworkflow 1.2.4 → 1.3.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 +26 -25
- package/extensions/gpt-responses-compat.ts +1 -1
- package/lib/cli/main.mjs +18 -86
- package/lib/cli/pi.mjs +43 -0
- package/lib/cli/prompts.mjs +3 -3
- package/lib/installer/assets.mjs +35 -31
- package/lib/installer/install.mjs +29 -9
- package/lib/installer/links.mjs +97 -84
- package/lib/paths.mjs +0 -2
- package/lib/providers/claude.mjs +14 -3
- package/lib/providers/codex.mjs +14 -9
- package/lib/providers/pi.mjs +61 -68
- package/lib/providers/skills.mjs +22 -22
- package/lib/providers/url.mjs +32 -1
- package/lib/templates/codex/agents/default.toml +16 -54
- package/lib/templates/codex/agents/explorer.toml +25 -52
- package/lib/templates/codex/agents/planner.toml +23 -77
- package/lib/templates/codex/agents/reviewer.toml +16 -62
- package/lib/templates/codex/agents/worker.toml +30 -64
- package/lib/templates/codex/config-base.toml +0 -6
- package/lib/templates/{workflow/gitignore.template → gitignore.template} +0 -3
- package/lib/tools/cli-installer.mjs +40 -12
- package/package.json +13 -18
- package/lib/templates/workflow/AGENTS.md +0 -50
- package/lib/templates/workflow/commands/abel-design.md +0 -175
- package/lib/templates/workflow/commands/abel-diagnose.md +0 -63
- package/lib/templates/workflow/commands/abel-implement.md +0 -170
- package/lib/templates/workflow/commands/abel-init.md +0 -25
- package/skills/dev-browser/SKILL.md +0 -281
- package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
- package/skills/dev-browser/dist/scripts/start.js +0 -90
- package/skills/dev-browser/dist/src/client.d.ts +0 -92
- package/skills/dev-browser/dist/src/client.js +0 -310
- package/skills/dev-browser/dist/src/entrypoint.d.ts +0 -29
- package/skills/dev-browser/dist/src/entrypoint.js +0 -113
- package/skills/dev-browser/dist/src/index.d.ts +0 -3
- package/skills/dev-browser/dist/src/index.js +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts +0 -24
- package/skills/dev-browser/dist/src/page-api.js +0 -103
- package/skills/dev-browser/dist/src/relay.d.ts +0 -26
- package/skills/dev-browser/dist/src/relay.js +0 -567
- package/skills/dev-browser/dist/src/runtime.d.ts +0 -34
- package/skills/dev-browser/dist/src/runtime.js +0 -44
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -22
- package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -868
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -13
- package/skills/dev-browser/dist/src/snapshot/index.js +0 -13
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -12
- package/skills/dev-browser/dist/src/snapshot/inject.js +0 -12
- package/skills/dev-browser/dist/src/standalone.d.ts +0 -31
- package/skills/dev-browser/dist/src/standalone.js +0 -173
- package/skills/dev-browser/dist/src/startup.d.ts +0 -46
- package/skills/dev-browser/dist/src/startup.js +0 -77
- package/skills/dev-browser/dist/src/target-registry.d.ts +0 -28
- package/skills/dev-browser/dist/src/target-registry.js +0 -134
- package/skills/dev-browser/dist/src/types.d.ts +0 -26
- package/skills/dev-browser/dist/src/types.js +0 -1
- package/skills/dev-browser/package-lock.json +0 -1545
- package/skills/dev-browser/package.json +0 -35
- package/skills/dev-browser/references/scraping.md +0 -144
- package/skills/git-commit/SKILL.md +0 -124
- package/skills/time/SKILL.md +0 -119
- package/skills/time/scripts/time_cli.py +0 -143
package/lib/installer/links.mjs
CHANGED
|
@@ -228,29 +228,6 @@ async function ensureProviderRootDirectory(homeDir, segments, options = {}) {
|
|
|
228
228
|
return directory;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
async function getDirectoryNames(root) {
|
|
232
|
-
if (!(await pathIsDirectory(root))) return [];
|
|
233
|
-
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
234
|
-
const names = await Promise.all(entries.map(async (entry) => {
|
|
235
|
-
if (entry.isDirectory()) return entry.name;
|
|
236
|
-
if (entry.isSymbolicLink() && await pathIsDirectory(join(root, entry.name))) return entry.name;
|
|
237
|
-
return null;
|
|
238
|
-
}));
|
|
239
|
-
return names.filter(Boolean);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function getCommandNames(commandsDir) {
|
|
243
|
-
if (!(await pathIsDirectory(commandsDir))) return [];
|
|
244
|
-
const entries = await fs.readdir(commandsDir, { withFileTypes: true });
|
|
245
|
-
const names = await Promise.all(entries.map(async (entry) => {
|
|
246
|
-
if (!entry.name.endsWith(".md")) return null;
|
|
247
|
-
if (entry.isFile()) return entry.name;
|
|
248
|
-
if (entry.isSymbolicLink() && await pathIsFile(join(commandsDir, entry.name))) return entry.name;
|
|
249
|
-
return null;
|
|
250
|
-
}));
|
|
251
|
-
return names.filter(Boolean);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
231
|
async function getPiExtensionNames(extensionsDir) {
|
|
255
232
|
if (!(await pathIsDirectory(extensionsDir))) return [];
|
|
256
233
|
const entries = await fs.readdir(extensionsDir, { withFileTypes: true });
|
|
@@ -289,10 +266,48 @@ async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames,
|
|
|
289
266
|
return results;
|
|
290
267
|
}
|
|
291
268
|
|
|
292
|
-
|
|
269
|
+
const cadenceOwnedSkills = new Set(["grok-search", "context7-auto-research"]);
|
|
270
|
+
|
|
271
|
+
function shouldLinkSkillToPi(skillName) {
|
|
272
|
+
return skillName !== ".system"
|
|
273
|
+
&& !skillName.startsWith("abel-")
|
|
274
|
+
&& !cadenceOwnedSkills.has(skillName);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function managedSkillNames(managedFiles = {}) {
|
|
278
|
+
const names = new Set();
|
|
279
|
+
for (const relativePath of Object.keys(managedFiles)) {
|
|
280
|
+
if (!relativePath.startsWith("skills/")) continue;
|
|
281
|
+
const skillName = relativePath.slice("skills/".length).split("/")[0];
|
|
282
|
+
if (shouldLinkSkillToPi(skillName)) names.add(skillName);
|
|
283
|
+
}
|
|
284
|
+
return names;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function selectedLinkedSkillNames({
|
|
288
|
+
previousLinkedTargets = {},
|
|
289
|
+
skillsRoot,
|
|
290
|
+
managedFiles,
|
|
291
|
+
extraName = null
|
|
292
|
+
} = {}) {
|
|
293
|
+
const names = managedFiles ? managedSkillNames(managedFiles) : new Set();
|
|
294
|
+
if (!managedFiles && skillsRoot) {
|
|
295
|
+
const resolvedSkillsRoot = resolve(skillsRoot);
|
|
296
|
+
for (const previousState of Object.values(previousLinkedTargets)) {
|
|
297
|
+
if (!previousState || previousState.kind !== "dir") continue;
|
|
298
|
+
const sourcePath = resolve(previousState.sourcePath);
|
|
299
|
+
if (dirname(sourcePath) !== resolvedSkillsRoot) continue;
|
|
300
|
+
const skillName = basename(sourcePath);
|
|
301
|
+
if (shouldLinkSkillToPi(skillName)) names.add(skillName);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (extraName && shouldLinkSkillToPi(extraName)) names.add(extraName);
|
|
305
|
+
return [...names];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, options, skillNames) {
|
|
293
309
|
const results = [];
|
|
294
310
|
const skillsRoot = join(paths.agentsDir, "skills");
|
|
295
|
-
const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
|
|
296
311
|
results.push(...await pruneManagedTargets(
|
|
297
312
|
join(baseDir, "skills"),
|
|
298
313
|
skillsRoot,
|
|
@@ -312,57 +327,54 @@ async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, optio
|
|
|
312
327
|
return results;
|
|
313
328
|
}
|
|
314
329
|
|
|
315
|
-
async function
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
330
|
+
async function linkPiSkill(paths, skillName, previousLinkedTargets = {}, options = {}) {
|
|
331
|
+
if (!shouldLinkSkillToPi(skillName)) return [];
|
|
332
|
+
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
|
|
333
|
+
await ensureManagedContainerDirectories([join(piAgentDir, "skills")], options);
|
|
334
|
+
const skillsRoot = join(paths.agentsDir, "skills");
|
|
335
|
+
return linkSkillDirectories(
|
|
336
|
+
piAgentDir,
|
|
337
|
+
paths,
|
|
338
|
+
previousLinkedTargets,
|
|
339
|
+
options,
|
|
340
|
+
selectedLinkedSkillNames({
|
|
324
341
|
previousLinkedTargets,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
342
|
+
skillsRoot,
|
|
343
|
+
managedFiles: options.managedFiles,
|
|
344
|
+
extraName: skillName
|
|
345
|
+
})
|
|
346
|
+
);
|
|
329
347
|
}
|
|
330
348
|
|
|
331
|
-
async function
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
join(paths.agentsDir, "AGENTS.md"),
|
|
341
|
-
"file",
|
|
342
|
-
previousLinkedTargets,
|
|
343
|
-
options
|
|
344
|
-
));
|
|
345
|
-
results.push(...await linkCommands(join(claudeDir, "commands"), paths, previousLinkedTargets, options));
|
|
346
|
-
results.push(...await linkSkillDirectories(claudeDir, paths, previousLinkedTargets, options));
|
|
347
|
-
return results;
|
|
349
|
+
async function pruneProviderInstructions(baseDir, commandsName, paths, previousLinkedTargets, options) {
|
|
350
|
+
const cleanupOptions = {
|
|
351
|
+
...options,
|
|
352
|
+
validateTargetDir: (directory) => ensureManagedContainerDirectories([directory], options)
|
|
353
|
+
};
|
|
354
|
+
return [
|
|
355
|
+
...await pruneManagedTargets(baseDir, paths.agentsDir, [], previousLinkedTargets, cleanupOptions),
|
|
356
|
+
...await pruneManagedTargets(join(baseDir, commandsName), join(paths.agentsDir, "commands"), [], previousLinkedTargets, cleanupOptions)
|
|
357
|
+
];
|
|
348
358
|
}
|
|
349
359
|
|
|
350
|
-
async function
|
|
351
|
-
const codexDir = await ensureProviderRootDirectory(paths.homeDir, [".codex"], options);
|
|
352
|
-
await ensureManagedContainerDirectories([
|
|
353
|
-
join(codexDir, "skills"),
|
|
354
|
-
join(codexDir, "prompts")
|
|
355
|
-
], options);
|
|
360
|
+
async function pruneRetiredProviderLinks(paths, previousLinkedTargets = {}, options = {}) {
|
|
356
361
|
const results = [];
|
|
357
|
-
|
|
358
|
-
join(
|
|
359
|
-
join(paths.
|
|
360
|
-
"
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
for (const targetDir of [
|
|
363
|
+
join(paths.homeDir, ".claude"),
|
|
364
|
+
join(paths.homeDir, ".claude", "commands"),
|
|
365
|
+
join(paths.homeDir, ".claude", "skills"),
|
|
366
|
+
join(paths.homeDir, ".codex"),
|
|
367
|
+
join(paths.homeDir, ".codex", "prompts"),
|
|
368
|
+
join(paths.homeDir, ".codex", "skills")
|
|
369
|
+
]) {
|
|
370
|
+
results.push(...await pruneManagedTargets(
|
|
371
|
+
targetDir,
|
|
372
|
+
paths.agentsDir,
|
|
373
|
+
[],
|
|
374
|
+
previousLinkedTargets,
|
|
375
|
+
options
|
|
376
|
+
));
|
|
377
|
+
}
|
|
366
378
|
return results;
|
|
367
379
|
}
|
|
368
380
|
|
|
@@ -404,19 +416,21 @@ async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
|
|
|
404
416
|
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, [".pi", "agent"], options);
|
|
405
417
|
await ensureManagedContainerDirectories([
|
|
406
418
|
join(piAgentDir, "extensions"),
|
|
407
|
-
join(piAgentDir, "skills")
|
|
408
|
-
join(piAgentDir, "prompts")
|
|
419
|
+
join(piAgentDir, "skills")
|
|
409
420
|
], options);
|
|
410
|
-
const results =
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
421
|
+
const results = await pruneRetiredProviderLinks(paths, previousLinkedTargets, options);
|
|
422
|
+
results.push(...await pruneProviderInstructions(piAgentDir, "prompts", paths, previousLinkedTargets, options));
|
|
423
|
+
results.push(...await linkSkillDirectories(
|
|
424
|
+
piAgentDir,
|
|
425
|
+
paths,
|
|
415
426
|
previousLinkedTargets,
|
|
416
|
-
options
|
|
427
|
+
options,
|
|
428
|
+
selectedLinkedSkillNames({
|
|
429
|
+
previousLinkedTargets,
|
|
430
|
+
skillsRoot: join(paths.agentsDir, "skills"),
|
|
431
|
+
managedFiles: options.managedFiles
|
|
432
|
+
})
|
|
417
433
|
));
|
|
418
|
-
results.push(...await linkSkillDirectories(piAgentDir, paths, previousLinkedTargets, options));
|
|
419
|
-
results.push(...await linkCommands(join(piAgentDir, "prompts"), paths, previousLinkedTargets, options));
|
|
420
434
|
results.push(...await linkPiExtensionsInDirectory(
|
|
421
435
|
join(piAgentDir, "extensions"),
|
|
422
436
|
join(paths.agentsDir, "extensions"),
|
|
@@ -429,11 +443,10 @@ async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
|
|
|
429
443
|
export {
|
|
430
444
|
ensureManagedLink,
|
|
431
445
|
ensureProviderRootDirectory,
|
|
432
|
-
getCommandNames,
|
|
433
446
|
isWithinManagedRoot,
|
|
434
|
-
linkClaude,
|
|
435
|
-
linkCodex,
|
|
436
447
|
linkPi,
|
|
437
448
|
linkPiExtensions,
|
|
438
|
-
|
|
449
|
+
linkPiSkill,
|
|
450
|
+
pruneManagedTargets,
|
|
451
|
+
shouldLinkSkillToPi
|
|
439
452
|
};
|
package/lib/paths.mjs
CHANGED
|
@@ -16,13 +16,11 @@ function createPaths({
|
|
|
16
16
|
assertSeparateSourceAndDeployment(absolutePackageRoot, absoluteAgentsDir);
|
|
17
17
|
|
|
18
18
|
const codexTemplateRoot = join(absolutePackageRoot, "lib", "templates", "codex");
|
|
19
|
-
const workflowTemplateRoot = join(absolutePackageRoot, "lib", "templates", "workflow");
|
|
20
19
|
const piAgentDir = join(absoluteHomeDir, ".pi", "agent");
|
|
21
20
|
return {
|
|
22
21
|
homeDir: absoluteHomeDir,
|
|
23
22
|
packageRoot: absolutePackageRoot,
|
|
24
23
|
agentsDir: absoluteAgentsDir,
|
|
25
|
-
workflowTemplateRoot,
|
|
26
24
|
installMetadataName: ".abelworkflow-install.json",
|
|
27
25
|
claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
|
|
28
26
|
codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
|
package/lib/providers/claude.mjs
CHANGED
|
@@ -50,8 +50,15 @@ const defaultClaudeSettings = {
|
|
|
50
50
|
language: "Chinese"
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
function assertClaudeApiKey(value, label) {
|
|
54
|
+
if (value !== undefined && typeof value !== "string") {
|
|
55
|
+
throw new TypeError(`${label} must be a string or undefined`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
function getExistingClaudeApiConfig(settings) {
|
|
54
60
|
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
61
|
+
assertClaudeApiKey(env.ANTHROPIC_API_KEY, "Claude settings env ANTHROPIC_API_KEY");
|
|
55
62
|
return {
|
|
56
63
|
baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
57
64
|
key: env.ANTHROPIC_API_KEY || "",
|
|
@@ -66,6 +73,8 @@ function buildClaudeApiSettings(settings, {
|
|
|
66
73
|
}) {
|
|
67
74
|
assertPlainObject(settings, "Claude settings");
|
|
68
75
|
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
76
|
+
assertClaudeApiKey(env.ANTHROPIC_API_KEY, "Claude settings env ANTHROPIC_API_KEY");
|
|
77
|
+
assertClaudeApiKey(key, "Claude API key");
|
|
69
78
|
const permissions = settings.permissions === undefined
|
|
70
79
|
? {}
|
|
71
80
|
: assertPlainObject(settings.permissions, "Claude settings permissions");
|
|
@@ -82,8 +91,7 @@ function buildClaudeApiSettings(settings, {
|
|
|
82
91
|
env: {
|
|
83
92
|
...defaultClaudeSettings.env,
|
|
84
93
|
...env,
|
|
85
|
-
ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl)
|
|
86
|
-
ANTHROPIC_API_KEY: key
|
|
94
|
+
ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl)
|
|
87
95
|
},
|
|
88
96
|
permissions: {
|
|
89
97
|
...defaultClaudeSettings.permissions,
|
|
@@ -92,6 +100,8 @@ function buildClaudeApiSettings(settings, {
|
|
|
92
100
|
deny: Array.isArray(permissions.deny) ? permissions.deny : [...defaultClaudeSettings.permissions.deny]
|
|
93
101
|
}
|
|
94
102
|
};
|
|
103
|
+
if (key === undefined) delete nextSettings.env.ANTHROPIC_API_KEY;
|
|
104
|
+
else nextSettings.env.ANTHROPIC_API_KEY = key;
|
|
95
105
|
delete nextSettings.includeCoAuthoredBy;
|
|
96
106
|
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
97
107
|
delete nextSettings.env.DISABLE_TELEMETRY;
|
|
@@ -144,7 +154,8 @@ async function configureClaudeApi(paths, promptApi, runtime = {}) {
|
|
|
144
154
|
|
|
145
155
|
const finalKey = await passwordOrExisting({
|
|
146
156
|
message: "Claude Code API Key",
|
|
147
|
-
existingValue: existing.key
|
|
157
|
+
existingValue: existing.key,
|
|
158
|
+
requiredMessage: existing.key ? null : "API Key 不能为空"
|
|
148
159
|
});
|
|
149
160
|
|
|
150
161
|
const model = await text({
|
package/lib/providers/codex.mjs
CHANGED
|
@@ -62,7 +62,10 @@ function mergeCodexTemplateDefaults(content, templateContent) {
|
|
|
62
62
|
? current.developer_instructions
|
|
63
63
|
: "";
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
const instructionHash = hashBytes(currentInstructions);
|
|
66
|
+
const usesLegacyDefaults = publishedCodexDeveloperInstructionHashes.has(instructionHash);
|
|
67
|
+
if (usesLegacyDefaults
|
|
68
|
+
|| instructionHash === "c84017d17fa425175f6dd7df9766b5de74e289420f63fea87054e2f41822a8bc") {
|
|
66
69
|
const legacyDefaults = {
|
|
67
70
|
approvals_reviewer: "guardian_subagent",
|
|
68
71
|
approval_policy: "on-request",
|
|
@@ -71,7 +74,7 @@ function mergeCodexTemplateDefaults(content, templateContent) {
|
|
|
71
74
|
};
|
|
72
75
|
const replacementFields = new Set(["developer_instructions"]);
|
|
73
76
|
for (const [field, value] of Object.entries(legacyDefaults)) {
|
|
74
|
-
if (current[field] === value) replacementFields.add(field);
|
|
77
|
+
if (usesLegacyDefaults && current[field] === value) replacementFields.add(field);
|
|
75
78
|
}
|
|
76
79
|
for (const field of replacementFields) {
|
|
77
80
|
nextContent = removeTopLevelTomlField(nextContent, field);
|
|
@@ -375,15 +378,17 @@ function buildCodexConfigContent(currentContent, {
|
|
|
375
378
|
|
|
376
379
|
function mergeCodexAuthData(auth, apiKey) {
|
|
377
380
|
assertPlainObject(auth, "Codex auth");
|
|
378
|
-
|
|
379
|
-
assertCodexAuthKeyAvailable(nextAuth, CODEX_ENV_KEY);
|
|
381
|
+
assertCodexAuthKeyAvailable(auth, CODEX_ENV_KEY);
|
|
380
382
|
if (apiKey) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
|
|
383
|
+
return {
|
|
384
|
+
auth_mode: "apikey",
|
|
385
|
+
[CODEX_ENV_KEY]: apiKey
|
|
386
|
+
};
|
|
386
387
|
}
|
|
388
|
+
|
|
389
|
+
const nextAuth = { ...auth };
|
|
390
|
+
delete nextAuth[CODEX_ENV_KEY];
|
|
391
|
+
if (nextAuth.auth_mode === "apikey") delete nextAuth.auth_mode;
|
|
387
392
|
return nextAuth;
|
|
388
393
|
}
|
|
389
394
|
|
package/lib/providers/pi.mjs
CHANGED
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
readJsoncFileSafe,
|
|
6
6
|
withFileTransaction
|
|
7
7
|
} from "../config/store.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
normalizeOpenAiBaseUrl,
|
|
10
|
+
promptBaseUrlOptions,
|
|
11
|
+
resolvePromptedBaseUrl
|
|
12
|
+
} from "./url.mjs";
|
|
9
13
|
import { stripJsonComments } from "../config/jsonc.mjs";
|
|
10
14
|
|
|
11
15
|
const piProviderId = "abelworkflow";
|
|
@@ -349,83 +353,72 @@ async function persistPiConfiguration(paths, intent, operations = {}) {
|
|
|
349
353
|
});
|
|
350
354
|
}
|
|
351
355
|
|
|
352
|
-
async function
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
selectOrCancel
|
|
363
|
-
} = promptApi;
|
|
364
|
-
const { auth, models, settings } = await readExistingPiConfiguration(paths);
|
|
365
|
-
const existing = resolveExistingPiApiConfig(models, settings, auth);
|
|
366
|
-
const providerLabel = `Pi ${piProviderId}`;
|
|
367
|
-
|
|
368
|
-
const baseUrlInput = await text({
|
|
369
|
-
message: `${providerLabel} Base URL`,
|
|
370
|
-
initialValue: existing.baseUrl || undefined,
|
|
371
|
-
validate: (value) => {
|
|
372
|
-
try {
|
|
373
|
-
normalizeOpenAiBaseUrl(value);
|
|
374
|
-
} catch (error) {
|
|
375
|
-
return error.message;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
assertNotCancelled(baseUrlInput);
|
|
380
|
-
const inferredApi = inferPiApiFromBaseUrl(baseUrlInput);
|
|
381
|
-
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
356
|
+
async function promptPiConfiguration(paths, promptApi, runtime = {}) {
|
|
357
|
+
const text = runtime.text ?? p.text;
|
|
358
|
+
const {
|
|
359
|
+
assertNotCancelled,
|
|
360
|
+
passwordOrExisting,
|
|
361
|
+
selectOrCancel
|
|
362
|
+
} = promptApi;
|
|
363
|
+
const { auth, models, settings } = await readExistingPiConfiguration(paths);
|
|
364
|
+
const existing = resolveExistingPiApiConfig(models, settings, auth);
|
|
365
|
+
const providerLabel = `Pi ${piProviderId}`;
|
|
382
366
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
message: `${providerLabel} API Key`,
|
|
394
|
-
existingValue: existing.apiKey
|
|
395
|
-
});
|
|
367
|
+
const baseUrlPrompt = {
|
|
368
|
+
message: `${providerLabel} Base URL`,
|
|
369
|
+
existingValue: existing.baseUrl,
|
|
370
|
+
placeholder: "https://api.openai.com/v1",
|
|
371
|
+
normalize: normalizeOpenAiBaseUrl
|
|
372
|
+
};
|
|
373
|
+
const baseUrlInput = await text(promptBaseUrlOptions(baseUrlPrompt));
|
|
374
|
+
assertNotCancelled(baseUrlInput);
|
|
375
|
+
const inferredApi = inferPiApiFromBaseUrl(baseUrlInput);
|
|
376
|
+
const baseUrl = resolvePromptedBaseUrl(baseUrlInput, baseUrlPrompt);
|
|
396
377
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
411
|
-
assertNotCancelled(defaultModel);
|
|
378
|
+
const apiOptions = getPiApiPromptOptions();
|
|
379
|
+
const initialApi = apiOptions.some((option) => option.value === existing.api)
|
|
380
|
+
? existing.api
|
|
381
|
+
: undefined;
|
|
382
|
+
const api = inferredApi || await selectOrCancel({
|
|
383
|
+
message: `${providerLabel} API 类型`,
|
|
384
|
+
options: apiOptions,
|
|
385
|
+
...(initialApi ? { initialValue: initialApi } : {})
|
|
386
|
+
});
|
|
387
|
+
const apiKey = await passwordOrExisting({
|
|
388
|
+
message: `${providerLabel} API Key`,
|
|
389
|
+
existingValue: existing.apiKey
|
|
390
|
+
});
|
|
412
391
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
392
|
+
const modelIdsText = await text({
|
|
393
|
+
message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
|
|
394
|
+
initialValue: existing.modelIds.join(",") || undefined,
|
|
395
|
+
validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
|
|
396
|
+
});
|
|
397
|
+
assertNotCancelled(modelIdsText);
|
|
398
|
+
const modelIds = parsePiModelIds(modelIdsText);
|
|
399
|
+
const defaultModel = await text({
|
|
400
|
+
message: "Pi 默认模型",
|
|
401
|
+
initialValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
|
|
402
|
+
validate: (value) => modelIds.includes(String(value || "").trim())
|
|
403
|
+
? undefined
|
|
404
|
+
: "默认模型必须在模型 ID 列表中"
|
|
421
405
|
});
|
|
406
|
+
assertNotCancelled(defaultModel);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
apiKey,
|
|
410
|
+
baseUrl,
|
|
411
|
+
api,
|
|
412
|
+
modelIds,
|
|
413
|
+
defaultModel: String(defaultModel).trim()
|
|
414
|
+
};
|
|
422
415
|
}
|
|
423
416
|
|
|
424
417
|
export {
|
|
425
418
|
buildPiAuthConfig,
|
|
426
419
|
buildPiModelsConfig,
|
|
427
420
|
buildPiSettingsConfig,
|
|
428
|
-
|
|
421
|
+
promptPiConfiguration,
|
|
429
422
|
getPiApiPromptOptions,
|
|
430
423
|
inferPiApiFromBaseUrl,
|
|
431
424
|
normalizeOpenAiBaseUrl,
|
package/lib/providers/skills.mjs
CHANGED
|
@@ -3,7 +3,11 @@ import { join } from "node:path";
|
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import { readDotenvFile, updateDotenvFile } from "../config/store.mjs";
|
|
5
5
|
import { pathToLabel } from "../paths.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
normalizeHttpBaseUrl,
|
|
8
|
+
promptBaseUrlOptions,
|
|
9
|
+
resolvePromptedBaseUrl
|
|
10
|
+
} from "./url.mjs";
|
|
7
11
|
|
|
8
12
|
const grokDefaults = Object.freeze(JSON.parse(readFileSync(
|
|
9
13
|
new URL("../../skills/grok-search/defaults.json", import.meta.url),
|
|
@@ -18,14 +22,6 @@ async function updateSkillEnvFile(path, updates, safeRoot) {
|
|
|
18
22
|
return updateDotenvFile(path, updates, { safeRoot, sensitive: true });
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
function validateHttpBaseUrl(value) {
|
|
22
|
-
try {
|
|
23
|
-
normalizeHttpBaseUrl(value);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
return error.message;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
async function configureGrokSearchEnv(paths, withSkillPresent, promptApi, runtime = {}) {
|
|
30
26
|
const {
|
|
31
27
|
assertNotCancelled,
|
|
@@ -39,13 +35,21 @@ async function configureGrokSearchEnv(paths, withSkillPresent, promptApi, runtim
|
|
|
39
35
|
await guard();
|
|
40
36
|
const envPath = join(paths.agentsDir, "skills", "grok-search", ".env");
|
|
41
37
|
const existing = await readSkillEnvFile(envPath, paths.agentsDir);
|
|
42
|
-
const
|
|
38
|
+
const grokApiUrlPrompt = {
|
|
43
39
|
message: "Grok API URL",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
existingValue: existing.GROK_API_URL,
|
|
41
|
+
placeholder: "https://api.x.ai/v1",
|
|
42
|
+
normalize: normalizeHttpBaseUrl
|
|
43
|
+
};
|
|
44
|
+
const tavilyApiUrlPrompt = {
|
|
45
|
+
message: "Tavily API URL",
|
|
46
|
+
existingValue: existing.TAVILY_API_URL,
|
|
47
|
+
placeholder: "https://api.tavily.com",
|
|
48
|
+
normalize: normalizeHttpBaseUrl
|
|
49
|
+
};
|
|
50
|
+
const baseUrlInput = await text(promptBaseUrlOptions(grokApiUrlPrompt));
|
|
47
51
|
assertNotCancelled(baseUrlInput);
|
|
48
|
-
const baseUrl =
|
|
52
|
+
const baseUrl = resolvePromptedBaseUrl(baseUrlInput, grokApiUrlPrompt);
|
|
49
53
|
|
|
50
54
|
const finalApiKey = await passwordOrExisting({
|
|
51
55
|
message: "Grok API Key",
|
|
@@ -65,15 +69,11 @@ async function configureGrokSearchEnv(paths, withSkillPresent, promptApi, runtim
|
|
|
65
69
|
initialValue: Boolean(existing.TAVILY_API_KEY)
|
|
66
70
|
});
|
|
67
71
|
|
|
68
|
-
const tavilyUrl = useTavily
|
|
69
|
-
? await text({
|
|
70
|
-
message: "Tavily API URL",
|
|
71
|
-
initialValue: existing.TAVILY_API_URL || "https://api.tavily.com",
|
|
72
|
-
validate: validateHttpBaseUrl
|
|
73
|
-
})
|
|
74
|
-
: null;
|
|
72
|
+
const tavilyUrl = useTavily ? await text(promptBaseUrlOptions(tavilyApiUrlPrompt)) : null;
|
|
75
73
|
if (useTavily) assertNotCancelled(tavilyUrl);
|
|
76
|
-
const normalizedTavilyUrl = useTavily
|
|
74
|
+
const normalizedTavilyUrl = useTavily
|
|
75
|
+
? resolvePromptedBaseUrl(tavilyUrl, tavilyApiUrlPrompt)
|
|
76
|
+
: null;
|
|
77
77
|
|
|
78
78
|
const finalTavilyKey = useTavily
|
|
79
79
|
? await passwordOrExisting({
|
package/lib/providers/url.mjs
CHANGED
|
@@ -32,4 +32,35 @@ function normalizeOpenAiBaseUrl(value) {
|
|
|
32
32
|
return url.toString();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
function resolvePromptedBaseUrl(value, { existingValue = "", placeholder, normalize }) {
|
|
36
|
+
const existing = typeof existingValue === "string" ? existingValue.trim() : "";
|
|
37
|
+
return normalize(String(value || "").trim() || existing || placeholder);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function promptBaseUrlOptions({
|
|
41
|
+
message,
|
|
42
|
+
existingValue,
|
|
43
|
+
placeholder,
|
|
44
|
+
normalize
|
|
45
|
+
}) {
|
|
46
|
+
const existing = typeof existingValue === "string" ? existingValue.trim() : "";
|
|
47
|
+
return {
|
|
48
|
+
message,
|
|
49
|
+
placeholder,
|
|
50
|
+
...(existing ? { initialValue: existing } : { defaultValue: placeholder }),
|
|
51
|
+
validate(value) {
|
|
52
|
+
try {
|
|
53
|
+
resolvePromptedBaseUrl(value, { existingValue: existing, placeholder, normalize });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return error.message;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
normalizeHttpBaseUrl,
|
|
63
|
+
normalizeOpenAiBaseUrl,
|
|
64
|
+
promptBaseUrlOptions,
|
|
65
|
+
resolvePromptedBaseUrl
|
|
66
|
+
};
|