@agent-native/skills 0.2.2 → 0.2.3
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/dist/connect.d.ts +1 -0
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +19 -5
- package/dist/connect.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +322 -161
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ Options:
|
|
|
25
25
|
--with-github-action Add .github/workflows/pr-visual-recap.yml when visual-recap is installed
|
|
26
26
|
--force Overwrite a different existing PR Visual Recap workflow
|
|
27
27
|
--no-mcp Install skill files only; skip registering the app's MCP server
|
|
28
|
+
--no-connect Register MCP where possible but skip inline browser/device authentication
|
|
28
29
|
-y, --yes Use defaults in non-interactive mode
|
|
29
30
|
--dry-run Print intended writes without changing files
|
|
30
31
|
--json Print the result as JSON
|
|
@@ -118,6 +119,10 @@ export function parseSkillsCliArgs(argv) {
|
|
|
118
119
|
out.mcp = false;
|
|
119
120
|
else if (arg === "--mcp")
|
|
120
121
|
out.mcp = true;
|
|
122
|
+
else if (arg === "--no-connect" || arg === "--skip-connect")
|
|
123
|
+
out.connect = false;
|
|
124
|
+
else if (arg === "--connect")
|
|
125
|
+
out.connect = true;
|
|
121
126
|
else if (arg.startsWith("-"))
|
|
122
127
|
throw new Error(`Unknown option: ${arg}`);
|
|
123
128
|
else if (!out.source)
|
|
@@ -166,6 +171,8 @@ function toCoreSkillsArgv(parsed) {
|
|
|
166
171
|
out.push("--force");
|
|
167
172
|
if (parsed.mcp === false)
|
|
168
173
|
out.push("--no-mcp");
|
|
174
|
+
if (parsed.connect === false)
|
|
175
|
+
out.push("--no-connect");
|
|
169
176
|
if (parsed.updateInstructions === true)
|
|
170
177
|
out.push("--update-instructions");
|
|
171
178
|
if (parsed.updateInstructions === false)
|
|
@@ -229,6 +236,9 @@ export async function runSkillsCli(argv, options = {}) {
|
|
|
229
236
|
source.cleanup?.();
|
|
230
237
|
}
|
|
231
238
|
}
|
|
239
|
+
const stdoutLog = parsed.printJson || options.log
|
|
240
|
+
? options.log
|
|
241
|
+
: (message) => process.stdout.write(`${message}\n`);
|
|
232
242
|
const result = await installSkills({
|
|
233
243
|
source: skillSource,
|
|
234
244
|
skillNames: parsed.skillNames,
|
|
@@ -243,7 +253,9 @@ export async function runSkillsCli(argv, options = {}) {
|
|
|
243
253
|
instructionFiles: parsed.instructionFiles,
|
|
244
254
|
withGithubAction: parsed.withGithubAction,
|
|
245
255
|
force: parsed.force,
|
|
246
|
-
|
|
256
|
+
connect: parsed.connect,
|
|
257
|
+
quiet: parsed.printJson,
|
|
258
|
+
log: parsed.printJson ? undefined : stdoutLog,
|
|
247
259
|
isInteractive: options.isInteractive,
|
|
248
260
|
promptSkills: options.promptSkills,
|
|
249
261
|
promptClients: options.promptClients,
|
|
@@ -264,28 +276,10 @@ export async function runSkillsCli(argv, options = {}) {
|
|
|
264
276
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
265
277
|
return;
|
|
266
278
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
? `Skill files: ${result.written.join(", ")}`
|
|
272
|
-
: "",
|
|
273
|
-
result.instructionFiles.length
|
|
274
|
-
? `Managed instructions: ${result.instructionFiles.join(", ")}`
|
|
275
|
-
: "",
|
|
276
|
-
result.githubActionPath
|
|
277
|
-
? `PR Visual Recap workflow: ${result.githubActionPath}`
|
|
278
|
-
: "",
|
|
279
|
-
...result.mcpServers.flatMap((server) => [
|
|
280
|
-
`MCP server "${server.serverName}" ${parsed.dryRun ? "would be registered" : "registered"} for ${server.clients.join(", ")}${server.files.length ? `:\n ${server.files.join("\n ")}` : ""}`,
|
|
281
|
-
...server.guidance.map((line) => ` ${line}`),
|
|
282
|
-
]),
|
|
283
|
-
parsed.dryRun
|
|
284
|
-
? ""
|
|
285
|
-
: "Restart or reload selected agent clients if needed.",
|
|
286
|
-
]
|
|
287
|
-
.filter(Boolean)
|
|
288
|
-
.join("\n") + "\n");
|
|
279
|
+
await printInstallResult(result, {
|
|
280
|
+
baseDir: parsed.baseDir ?? options.baseDir ?? process.cwd(),
|
|
281
|
+
dryRun: parsed.dryRun,
|
|
282
|
+
});
|
|
289
283
|
}
|
|
290
284
|
catch (error) {
|
|
291
285
|
telemetry.track("skills_cli failed", {
|
|
@@ -356,98 +350,126 @@ export async function installSkills(options) {
|
|
|
356
350
|
});
|
|
357
351
|
const scope = await resolveSelectedScope(options);
|
|
358
352
|
options.telemetry?.track("skills_cli scope selected", { scope });
|
|
353
|
+
const skillNames = selected.map((skill) => skill.name);
|
|
354
|
+
const instructionBlocks = managedInstructionBlocksForSkills(skillNames);
|
|
355
|
+
const shouldUpdateInstructions = await shouldUpdateManagedInstructions(instructionBlocks, options);
|
|
356
|
+
const shouldWriteGithubAction = selected.some((skill) => skill.name === "visual-recap") &&
|
|
357
|
+
(options.withGithubAction ||
|
|
358
|
+
(await shouldPromptGithubAction(options, baseDir)));
|
|
359
|
+
const mcpApps = options.mcp === false ? [] : mcpAppsForSkills(skillNames);
|
|
360
|
+
const progress = await createInstallProgress(options, 1 +
|
|
361
|
+
(shouldUpdateInstructions ? 1 : 0) +
|
|
362
|
+
(shouldWriteGithubAction ? 1 : 0) +
|
|
363
|
+
mcpApps.length);
|
|
359
364
|
const written = [];
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
365
|
+
let instructionFiles = [];
|
|
366
|
+
let githubActionPath;
|
|
367
|
+
const mcpServers = [];
|
|
368
|
+
try {
|
|
369
|
+
progress?.start("Installing skill files...");
|
|
370
|
+
for (const client of clients) {
|
|
371
|
+
const root = installRootForClient(client, scope, baseDir);
|
|
372
|
+
for (const skill of selected) {
|
|
373
|
+
const destination = path.join(root, skill.name);
|
|
374
|
+
written.push(destination);
|
|
375
|
+
if (!options.dryRun) {
|
|
376
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
377
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
378
|
+
fs.cpSync(skill.dir, destination, { recursive: true });
|
|
379
|
+
}
|
|
369
380
|
}
|
|
370
381
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
});
|
|
379
|
-
const instructionFiles = await maybeUpdateInstructions(selected.map((skill) => skill.name), baseDir, options);
|
|
380
|
-
if (instructionFiles.length) {
|
|
381
|
-
options.telemetry?.track("skills_cli instructions updated", {
|
|
382
|
-
fileCount: instructionFiles.length,
|
|
382
|
+
progress?.advance("Skill files installed");
|
|
383
|
+
options.telemetry?.track("skills_cli install completed", {
|
|
384
|
+
skills: skillNames.join(","),
|
|
385
|
+
clients: clients.join(","),
|
|
386
|
+
scope,
|
|
387
|
+
writtenCount: written.length,
|
|
388
|
+
dryRun: Boolean(options.dryRun),
|
|
383
389
|
});
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
options.telemetry?.track("skills_cli github action added");
|
|
392
|
-
}
|
|
393
|
-
// Register the hosted MCP server for app-backed skills (visual-plan /
|
|
394
|
-
// visual-recap → Agent-Native Plan, assets, design-exploration) so the
|
|
395
|
-
// agent can actually call them — not just read the SKILL.md. On by
|
|
396
|
-
// default; `--no-mcp` installs the skill files only. One registration per
|
|
397
|
-
// app, so visual-plan + visual-recap share a single "plan" server.
|
|
398
|
-
const mcpServers = [];
|
|
399
|
-
if (options.mcp !== false) {
|
|
400
|
-
const mcpClients = clients.map((client) => client === "claude-code" ? "claude-code" : "codex");
|
|
401
|
-
const seenApps = new Set();
|
|
402
|
-
for (const skill of selected) {
|
|
403
|
-
const app = resolveAppForSkill(skill.name);
|
|
404
|
-
if (!app || seenApps.has(app.appId))
|
|
405
|
-
continue;
|
|
406
|
-
seenApps.add(app.appId);
|
|
407
|
-
if (options.dryRun) {
|
|
408
|
-
mcpServers.push({
|
|
409
|
-
serverName: app.serverName,
|
|
410
|
-
mcpUrl: app.mcpUrl,
|
|
411
|
-
clients,
|
|
412
|
-
files: [],
|
|
413
|
-
authenticated: false,
|
|
414
|
-
guidance: [],
|
|
390
|
+
if (shouldUpdateInstructions) {
|
|
391
|
+
progress?.message("Updating managed instructions...");
|
|
392
|
+
instructionFiles = writeManagedInstructions(instructionBlocks, baseDir, clients, scope, options);
|
|
393
|
+
progress?.advance("Managed instructions updated");
|
|
394
|
+
if (instructionFiles.length) {
|
|
395
|
+
options.telemetry?.track("skills_cli instructions updated", {
|
|
396
|
+
fileCount: instructionFiles.length,
|
|
415
397
|
});
|
|
416
|
-
continue;
|
|
417
398
|
}
|
|
418
|
-
const registration = await registerMcpServer({
|
|
419
|
-
descriptor: {
|
|
420
|
-
serverName: app.serverName,
|
|
421
|
-
mcpUrl: app.mcpUrl,
|
|
422
|
-
aliases: app.aliases,
|
|
423
|
-
authMode: app.authMode,
|
|
424
|
-
hostedUrl: app.hostedUrl,
|
|
425
|
-
},
|
|
426
|
-
clients: mcpClients,
|
|
427
|
-
scope,
|
|
428
|
-
baseDir,
|
|
429
|
-
interactive: isInteractive(options),
|
|
430
|
-
log,
|
|
431
|
-
});
|
|
432
|
-
mcpServers.push({
|
|
433
|
-
serverName: app.serverName,
|
|
434
|
-
mcpUrl: app.mcpUrl,
|
|
435
|
-
clients,
|
|
436
|
-
files: [...new Set(registration.written.map((entry) => entry.file))],
|
|
437
|
-
authenticated: registration.authenticated,
|
|
438
|
-
guidance: registration.guidance,
|
|
439
|
-
});
|
|
440
|
-
options.telemetry?.track("skills_cli mcp registered", {
|
|
441
|
-
serverName: app.serverName,
|
|
442
|
-
clients: clients.join(","),
|
|
443
|
-
authenticated: registration.authenticated,
|
|
444
|
-
});
|
|
445
399
|
}
|
|
400
|
+
if (shouldWriteGithubAction) {
|
|
401
|
+
progress?.message("Writing PR Visual Recap workflow...");
|
|
402
|
+
githubActionPath = writePrVisualRecapWorkflow(baseDir, options);
|
|
403
|
+
progress?.advance("PR Visual Recap workflow ready");
|
|
404
|
+
if (githubActionPath) {
|
|
405
|
+
options.telemetry?.track("skills_cli github action added");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Register the hosted MCP server for app-backed skills (visual-plan /
|
|
409
|
+
// visual-recap → Agent-Native Plan, assets, design-exploration) so the
|
|
410
|
+
// agent can actually call them — not just read the SKILL.md. On by
|
|
411
|
+
// default; `--no-mcp` installs the skill files only. One registration per
|
|
412
|
+
// app, so visual-plan + visual-recap share a single "plan" server.
|
|
413
|
+
if (mcpApps.length > 0) {
|
|
414
|
+
const mcpClients = clients.map((client) => client === "claude-code" ? "claude-code" : "codex");
|
|
415
|
+
for (const app of mcpApps) {
|
|
416
|
+
progress?.message(`Registering ${app.displayName} MCP server...`);
|
|
417
|
+
if (!options.dryRun) {
|
|
418
|
+
const registration = await registerMcpServer({
|
|
419
|
+
descriptor: {
|
|
420
|
+
serverName: app.serverName,
|
|
421
|
+
mcpUrl: app.mcpUrl,
|
|
422
|
+
aliases: app.aliases,
|
|
423
|
+
authMode: app.authMode,
|
|
424
|
+
hostedUrl: app.hostedUrl,
|
|
425
|
+
},
|
|
426
|
+
clients: mcpClients,
|
|
427
|
+
scope,
|
|
428
|
+
baseDir,
|
|
429
|
+
interactive: options.connect !== false && isInteractive(options),
|
|
430
|
+
log,
|
|
431
|
+
deviceFlowTimeoutMs: options.deviceFlowTimeoutMs,
|
|
432
|
+
});
|
|
433
|
+
mcpServers.push({
|
|
434
|
+
serverName: app.serverName,
|
|
435
|
+
mcpUrl: app.mcpUrl,
|
|
436
|
+
clients,
|
|
437
|
+
registeredClients: unique(registration.written.map((entry) => entry.client)),
|
|
438
|
+
files: [
|
|
439
|
+
...new Set(registration.written.map((entry) => entry.file)),
|
|
440
|
+
],
|
|
441
|
+
authenticated: registration.authenticated,
|
|
442
|
+
guidance: registration.guidance,
|
|
443
|
+
});
|
|
444
|
+
options.telemetry?.track("skills_cli mcp registered", {
|
|
445
|
+
serverName: app.serverName,
|
|
446
|
+
clients: clients.join(","),
|
|
447
|
+
authenticated: registration.authenticated,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
mcpServers.push({
|
|
452
|
+
serverName: app.serverName,
|
|
453
|
+
mcpUrl: app.mcpUrl,
|
|
454
|
+
clients,
|
|
455
|
+
registeredClients: clients,
|
|
456
|
+
files: [],
|
|
457
|
+
authenticated: false,
|
|
458
|
+
guidance: [],
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
progress?.advance(`${app.displayName} MCP server ready`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
progress?.stop("Installation complete");
|
|
465
|
+
}
|
|
466
|
+
catch (err) {
|
|
467
|
+
progress?.error("Installation failed");
|
|
468
|
+
throw err;
|
|
446
469
|
}
|
|
447
|
-
log(`Resolved ${selected.length} skill${selected.length === 1 ? "" : "s"} from ${source.root}.`);
|
|
448
470
|
return {
|
|
449
471
|
source: source.root,
|
|
450
|
-
skills:
|
|
472
|
+
skills: skillNames,
|
|
451
473
|
clients,
|
|
452
474
|
scope,
|
|
453
475
|
written,
|
|
@@ -475,6 +497,7 @@ function defaultArgs(command) {
|
|
|
475
497
|
instructionFiles: [],
|
|
476
498
|
withGithubAction: false,
|
|
477
499
|
force: false,
|
|
500
|
+
connect: true,
|
|
478
501
|
mcp: true,
|
|
479
502
|
};
|
|
480
503
|
}
|
|
@@ -510,6 +533,125 @@ function normalizeSkillName(value) {
|
|
|
510
533
|
function unique(values) {
|
|
511
534
|
return [...new Set(values)];
|
|
512
535
|
}
|
|
536
|
+
function plural(count, singular, pluralForm = `${singular}s`) {
|
|
537
|
+
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
538
|
+
}
|
|
539
|
+
function shortenPathForOutput(file, baseDir) {
|
|
540
|
+
const resolved = path.resolve(file);
|
|
541
|
+
const home = process.env.HOME || os.homedir();
|
|
542
|
+
if (resolved === home || resolved.startsWith(`${home}${path.sep}`)) {
|
|
543
|
+
return `~${resolved.slice(home.length)}`;
|
|
544
|
+
}
|
|
545
|
+
const base = path.resolve(baseDir);
|
|
546
|
+
const relative = path.relative(base, resolved);
|
|
547
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
548
|
+
return `.${path.sep}${relative}`;
|
|
549
|
+
}
|
|
550
|
+
return file;
|
|
551
|
+
}
|
|
552
|
+
function summarizePaths(files, baseDir, max = 4) {
|
|
553
|
+
const shortened = unique(files).map((file) => shortenPathForOutput(file, baseDir));
|
|
554
|
+
if (shortened.length <= max)
|
|
555
|
+
return shortened.join(", ");
|
|
556
|
+
return `${shortened.slice(0, max).join(", ")} +${shortened.length - max} more`;
|
|
557
|
+
}
|
|
558
|
+
async function createInstallProgress(options, max) {
|
|
559
|
+
if (options.quiet || !isInteractive(options) || max <= 0)
|
|
560
|
+
return null;
|
|
561
|
+
const clack = await import("@clack/prompts");
|
|
562
|
+
const progress = clack.progress({ max, indicator: "timer" });
|
|
563
|
+
let active = false;
|
|
564
|
+
return {
|
|
565
|
+
start(message) {
|
|
566
|
+
active = true;
|
|
567
|
+
progress.start(message);
|
|
568
|
+
},
|
|
569
|
+
message(message) {
|
|
570
|
+
if (active)
|
|
571
|
+
progress.message(message);
|
|
572
|
+
},
|
|
573
|
+
advance(message) {
|
|
574
|
+
if (active)
|
|
575
|
+
progress.advance(1, message);
|
|
576
|
+
},
|
|
577
|
+
stop(message) {
|
|
578
|
+
if (!active)
|
|
579
|
+
return;
|
|
580
|
+
progress.stop(message);
|
|
581
|
+
active = false;
|
|
582
|
+
},
|
|
583
|
+
error(message) {
|
|
584
|
+
if (!active)
|
|
585
|
+
return;
|
|
586
|
+
progress.error(message);
|
|
587
|
+
active = false;
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function mcpAppsForSkills(skillNames) {
|
|
592
|
+
const apps = [];
|
|
593
|
+
const seen = new Set();
|
|
594
|
+
for (const skillName of skillNames) {
|
|
595
|
+
const app = resolveAppForSkill(skillName);
|
|
596
|
+
if (!app || seen.has(app.appId))
|
|
597
|
+
continue;
|
|
598
|
+
seen.add(app.appId);
|
|
599
|
+
apps.push(app);
|
|
600
|
+
}
|
|
601
|
+
return apps;
|
|
602
|
+
}
|
|
603
|
+
function mcpStatus(server, dryRun) {
|
|
604
|
+
if (dryRun)
|
|
605
|
+
return `would register for ${server.clients.join(", ")}`;
|
|
606
|
+
const registered = server.registeredClients;
|
|
607
|
+
const pending = server.clients.filter((client) => !registered.includes(client));
|
|
608
|
+
const parts = [];
|
|
609
|
+
if (registered.length > 0) {
|
|
610
|
+
parts.push(`${server.authenticated ? "registered and authenticated" : "registered"} for ${registered.join(", ")}`);
|
|
611
|
+
}
|
|
612
|
+
if (pending.length > 0) {
|
|
613
|
+
parts.push(`authentication pending for ${pending.join(", ")}`);
|
|
614
|
+
}
|
|
615
|
+
return (parts.join("; ") ||
|
|
616
|
+
`authentication pending for ${server.clients.join(", ")}`);
|
|
617
|
+
}
|
|
618
|
+
async function printInstallResult(result, options) {
|
|
619
|
+
const clack = await import("@clack/prompts");
|
|
620
|
+
const verb = options.dryRun ? "Would install" : "Installed";
|
|
621
|
+
const summary = [
|
|
622
|
+
`Skills ${result.skills.join(", ") || "none"}`,
|
|
623
|
+
`Agents ${result.clients.join(", ") || "none"}`,
|
|
624
|
+
`Scope ${result.scope}`,
|
|
625
|
+
result.written.length
|
|
626
|
+
? `Skill folders ${plural(result.written.length, "folder")} (${summarizePaths(result.written, options.baseDir)})`
|
|
627
|
+
: "",
|
|
628
|
+
].filter(Boolean);
|
|
629
|
+
clack.note(summary.join("\n"), verb);
|
|
630
|
+
if (result.instructionFiles.length) {
|
|
631
|
+
clack.note(summarizePaths(result.instructionFiles, options.baseDir), "Managed instructions");
|
|
632
|
+
}
|
|
633
|
+
if (result.githubActionPath) {
|
|
634
|
+
clack.note(shortenPathForOutput(result.githubActionPath, options.baseDir), "PR Visual Recap workflow");
|
|
635
|
+
}
|
|
636
|
+
if (result.mcpServers.length) {
|
|
637
|
+
const mcpLines = result.mcpServers.map((server) => {
|
|
638
|
+
const status = mcpStatus(server, options.dryRun);
|
|
639
|
+
const files = server.files.length
|
|
640
|
+
? ` (${summarizePaths(server.files, options.baseDir, 2)})`
|
|
641
|
+
: "";
|
|
642
|
+
return `${server.serverName}: ${status}${files}`;
|
|
643
|
+
});
|
|
644
|
+
clack.note(mcpLines.join("\n"), "MCP");
|
|
645
|
+
const guidance = result.mcpServers.flatMap((server) => server.guidance);
|
|
646
|
+
if (guidance.length) {
|
|
647
|
+
clack.note(guidance.join("\n"), "Next steps");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!options.dryRun) {
|
|
651
|
+
clack.note("Restart or reload selected agent clients if needed.", "Reload");
|
|
652
|
+
}
|
|
653
|
+
clack.outro(`${options.dryRun ? "Dry run complete" : "All set"} ✅`);
|
|
654
|
+
}
|
|
513
655
|
function compactPromptHint(value) {
|
|
514
656
|
const hint = value?.replace(/\s+/g, " ").trim() ?? "";
|
|
515
657
|
if (!hint)
|
|
@@ -716,14 +858,34 @@ function skillEntry(dir) {
|
|
|
716
858
|
return null;
|
|
717
859
|
const body = fs.readFileSync(skillFile, "utf-8");
|
|
718
860
|
const frontmatter = body.match(/^---\n([\s\S]*?)\n---/);
|
|
719
|
-
const name = frontmatter?.[1]
|
|
720
|
-
|
|
721
|
-
?.trim() ?? path.basename(dir);
|
|
722
|
-
const description = frontmatter?.[1]
|
|
723
|
-
?.match(/^description:\s*(?:>-\s*)?(.+)$/m)?.[1]
|
|
724
|
-
?.trim();
|
|
861
|
+
const name = frontmatterField(frontmatter?.[1], "name") ?? path.basename(dir);
|
|
862
|
+
const description = frontmatterField(frontmatter?.[1], "description");
|
|
725
863
|
return { name: normalizeSkillName(name), dir, description };
|
|
726
864
|
}
|
|
865
|
+
function frontmatterField(frontmatter, field) {
|
|
866
|
+
if (!frontmatter)
|
|
867
|
+
return undefined;
|
|
868
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
869
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
870
|
+
const match = lines[i].match(new RegExp(`^${field}:\\s*(.*)$`));
|
|
871
|
+
if (!match)
|
|
872
|
+
continue;
|
|
873
|
+
const raw = match[1].trim();
|
|
874
|
+
if (raw === ">-" || raw === ">" || raw === "|-" || raw === "|") {
|
|
875
|
+
const block = [];
|
|
876
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
877
|
+
const line = lines[j];
|
|
878
|
+
if (line.trim() && !/^\s/.test(line))
|
|
879
|
+
break;
|
|
880
|
+
block.push(line.replace(/^\s+/, ""));
|
|
881
|
+
}
|
|
882
|
+
const value = raw.startsWith("|") ? block.join("\n") : block.join(" ");
|
|
883
|
+
return value.replace(/\s+/g, " ").trim() || undefined;
|
|
884
|
+
}
|
|
885
|
+
return raw.replace(/^["']|["']$/g, "").trim() || undefined;
|
|
886
|
+
}
|
|
887
|
+
return undefined;
|
|
888
|
+
}
|
|
727
889
|
async function materializeSource(input) {
|
|
728
890
|
const local = path.resolve(input);
|
|
729
891
|
if (fs.existsSync(local))
|
|
@@ -798,70 +960,49 @@ function parseGitHubSource(input) {
|
|
|
798
960
|
}
|
|
799
961
|
function instructionContentForSkill(skillName) {
|
|
800
962
|
if (skillName === "quick-recap") {
|
|
801
|
-
return
|
|
802
|
-
|
|
803
|
-
Every response that completes a unit of work must end with one final status line:
|
|
804
|
-
|
|
805
|
-
\`\`\`md
|
|
806
|
-
🟢 Actual concise status sentence
|
|
807
|
-
\`\`\`
|
|
808
|
-
|
|
809
|
-
Use 🟢 when the requested work is finished. Use 🟡 when non-routine follow-up work or a manual step remains, and name that pending item. Use 🔴 only when blocked on user input. Keep the status line under 100 characters. Put the status line at the very end of the response. Do not add \`---\`, spacer lines, or any content after it.
|
|
810
|
-
|
|
811
|
-
Examples:
|
|
812
|
-
|
|
813
|
-
\`\`\`md
|
|
814
|
-
🟢 Updated quick recap docs with output examples
|
|
815
|
-
\`\`\`
|
|
816
|
-
|
|
817
|
-
\`\`\`md
|
|
818
|
-
🟡 Code updated, set PROVIDER_WEBHOOK_SECRET before testing webhooks
|
|
819
|
-
\`\`\`
|
|
820
|
-
|
|
821
|
-
\`\`\`md
|
|
822
|
-
🔴 Need the production API key to continue
|
|
823
|
-
\`\`\``;
|
|
963
|
+
return "When writing final response status indicators, use the /quick-recap skill always.";
|
|
824
964
|
}
|
|
825
965
|
if (skillName === "efficient-fable") {
|
|
826
|
-
return
|
|
827
|
-
|
|
828
|
-
When operating as Claude Fable or another explicitly Fable-class expensive model, preserve Fable for the judgment layer: decomposition, architecture and product tradeoffs, synthesis, risk calls, and final review. Delegate token-heavy research, coding, testing, file inventory, repetitive edits, and independent implementation slices to cheaper subagents when available. Write delegated prompts as self-contained handoff packets with objective, scope, out-of-scope areas, expected evidence, verification commands, and stop conditions. For testing, Fable should suggest the validation direction and important scripts or browser checks, then lighter agents can run them, reduce logs, collect screenshots, and report exact failures and likely causes. Treat delegated reports as leads: Fable should verify important cited files, failures, and high-risk diffs before relying on them. Do not make unsupported quality or speed guarantees; frame savings as workload-dependent.`;
|
|
966
|
+
return "When operating as Claude Fable, use the /efficient-fable skill always.";
|
|
829
967
|
}
|
|
830
968
|
if (skillName === "efficient-frontier") {
|
|
831
|
-
return
|
|
832
|
-
|
|
833
|
-
When running any high-cost frontier model on a codebase-heavy task, act as the orchestrator and reviewer. Split independent research, search, summarization, coding, and testing work into cheaper/faster subagents when the host supports them, then spend frontier-model tokens on the plan, tradeoffs, integration decisions, validation strategy, and final quality pass. Delegated prompts should be self-contained: objective, repo path, scope, out-of-scope areas, expected evidence, verification commands, and stop conditions. For testing-heavy work, the frontier model should choose the scripts or browser flows that matter while lighter agents run checks, reduce output, and return the concrete signal. Treat delegated findings as leads and verify important claims before presenting them as facts.`;
|
|
969
|
+
return "When using a high-cost frontier model for codebase-heavy work, use the /efficient-frontier skill always.";
|
|
834
970
|
}
|
|
835
971
|
if (skillName === "stay-within-limits") {
|
|
836
|
-
return
|
|
837
|
-
|
|
838
|
-
|
|
972
|
+
return "When long-running or parallel work needs usage-limit checks, use the /stay-within-limits skill always.";
|
|
973
|
+
}
|
|
974
|
+
if (skillName === "visual-plan") {
|
|
975
|
+
return "When work needs an interactive visual plan before implementation, use the /visual-plan skill always.";
|
|
976
|
+
}
|
|
977
|
+
if (skillName === "visual-recap") {
|
|
978
|
+
return "When a PR, branch, commit, or diff needs an interactive visual recap, use the /visual-recap skill always.";
|
|
839
979
|
}
|
|
840
980
|
return null;
|
|
841
981
|
}
|
|
842
|
-
|
|
843
|
-
|
|
982
|
+
function managedInstructionBlocksForSkills(skillNames) {
|
|
983
|
+
return skillNames
|
|
844
984
|
.map((name) => instructionContentForSkill(name))
|
|
845
985
|
.filter((block) => Boolean(block));
|
|
986
|
+
}
|
|
987
|
+
async function shouldUpdateManagedInstructions(blocks, options) {
|
|
846
988
|
if (blocks.length === 0)
|
|
847
|
-
return
|
|
989
|
+
return false;
|
|
848
990
|
let shouldUpdate = options.updateInstructions;
|
|
849
|
-
if (shouldUpdate
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
if (!shouldUpdate)
|
|
991
|
+
if (shouldUpdate !== undefined)
|
|
992
|
+
return shouldUpdate;
|
|
993
|
+
if (options.yes)
|
|
994
|
+
return true;
|
|
995
|
+
if (!isInteractive(options))
|
|
996
|
+
return false;
|
|
997
|
+
const prompt = options.promptUpdateInstructions ?? promptForUpdateInstructions;
|
|
998
|
+
return (await prompt()) === true;
|
|
999
|
+
}
|
|
1000
|
+
function writeManagedInstructions(blocks, baseDir, clients, scope, options) {
|
|
1001
|
+
if (blocks.length === 0)
|
|
861
1002
|
return [];
|
|
862
|
-
const files = resolveInstructionFiles(baseDir, options.instructionFiles);
|
|
1003
|
+
const files = resolveInstructionFiles(baseDir, options.instructionFiles, clients, scope);
|
|
863
1004
|
const content = `${MANAGED_INSTRUCTIONS_START}
|
|
864
|
-
${blocks.join("\n
|
|
1005
|
+
${blocks.join("\n")}
|
|
865
1006
|
${MANAGED_INSTRUCTIONS_END}`;
|
|
866
1007
|
for (const file of files) {
|
|
867
1008
|
if (options.dryRun)
|
|
@@ -870,10 +1011,30 @@ ${MANAGED_INSTRUCTIONS_END}`;
|
|
|
870
1011
|
}
|
|
871
1012
|
return files;
|
|
872
1013
|
}
|
|
873
|
-
function
|
|
1014
|
+
async function maybeUpdateInstructions(skillNames, baseDir, options) {
|
|
1015
|
+
const blocks = managedInstructionBlocksForSkills(skillNames);
|
|
1016
|
+
const clients = options.clients?.length ? options.clients : CLIENTS;
|
|
1017
|
+
const scope = options.scope ?? "user";
|
|
1018
|
+
if (!(await shouldUpdateManagedInstructions(blocks, options)))
|
|
1019
|
+
return [];
|
|
1020
|
+
return writeManagedInstructions(blocks, baseDir, clients, scope, options);
|
|
1021
|
+
}
|
|
1022
|
+
function resolveInstructionFiles(baseDir, explicit, clients, scope) {
|
|
874
1023
|
if (explicit && explicit.length > 0) {
|
|
875
1024
|
return explicit.map((file) => path.resolve(baseDir, file));
|
|
876
1025
|
}
|
|
1026
|
+
if (scope === "user") {
|
|
1027
|
+
const home = process.env.HOME || os.homedir();
|
|
1028
|
+
const files = [];
|
|
1029
|
+
if (clients.includes("codex")) {
|
|
1030
|
+
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
1031
|
+
files.push(path.join(codexHome, "AGENTS.md"));
|
|
1032
|
+
}
|
|
1033
|
+
if (clients.includes("claude-code")) {
|
|
1034
|
+
files.push(path.join(home, ".claude", "CLAUDE.md"));
|
|
1035
|
+
}
|
|
1036
|
+
return unique(files);
|
|
1037
|
+
}
|
|
877
1038
|
const candidates = ["AGENTS.md", "CLAUDE.md"].map((file) => path.join(baseDir, file));
|
|
878
1039
|
const existing = candidates.filter((file) => fs.existsSync(file));
|
|
879
1040
|
return existing.length > 0 ? existing : [path.join(baseDir, "AGENTS.md")];
|