@constraint/cli 0.3.7 → 0.4.1
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/package.json +1 -1
- package/skills/constraint-skills/SKILL.md +25 -25
- package/skills/constraint-skills/references/commands.md +11 -5
- package/src/cli.js +48 -49
- package/src/paths.js +4 -7
- package/src/setup.js +7 -78
package/package.json
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: constraint-skills
|
|
3
|
-
description: Operate governed Agent Skills with the Constraint terminal CLI
|
|
3
|
+
description: Operate governed Agent Skills with the Constraint terminal CLI. Use when an agent needs to authenticate or diagnose Constraint access; discover upload domains; inspect, install, update, or upload skills; review run history; or report a session transcript when the user asks to put skill work on the record.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Constraint Skills
|
|
7
7
|
|
|
8
|
-
Use the `constraint` command for organization-authorized skills. Do not edit its
|
|
8
|
+
Use the `constraint` command for organization-authorized skills. Do not edit its
|
|
9
|
+
config, lockfiles, installation receipts, or authorization tokens directly.
|
|
9
10
|
|
|
10
11
|
## Choose the operation
|
|
11
12
|
|
|
12
13
|
- Authenticate or diagnose: `constraint login`, `constraint whoami`, `constraint doctor`.
|
|
13
|
-
- Configure agents: `constraint setup`.
|
|
14
14
|
- Discover or inspect skills: `constraint skills list`, `constraint skills inspect`.
|
|
15
15
|
- Install or update: `constraint skills install`, `constraint skills update`.
|
|
16
16
|
- Discover upload destinations: `constraint skills domains --json`.
|
|
17
17
|
- Upload a local skill folder: `constraint skills upload`.
|
|
18
|
+
- Report a session: `constraint report`.
|
|
18
19
|
- Review runs: `constraint skills run list`, `constraint skills run get`.
|
|
19
20
|
|
|
20
|
-
Read [references/commands.md](references/commands.md) before constructing flags
|
|
21
|
+
Read [references/commands.md](references/commands.md) before constructing flags
|
|
22
|
+
or handling a less common operation.
|
|
21
23
|
|
|
22
24
|
## Upload skills
|
|
23
25
|
|
|
@@ -27,7 +29,9 @@ Upload the skill directory, not an individual `SKILL.md`. Before every upload, r
|
|
|
27
29
|
constraint skills domains --json
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
Use one returned `slug` exactly as `--domain`. Never guess a slug from a display
|
|
32
|
+
Use one returned `slug` exactly as `--domain`. Never guess a slug from a display
|
|
33
|
+
name or reuse a domain from another organization. If multiple destinations are
|
|
34
|
+
valid and ownership is unclear, ask the user.
|
|
31
35
|
|
|
32
36
|
```bash
|
|
33
37
|
constraint skills upload <skill-directory> --domain <returned-slug>
|
|
@@ -35,33 +39,29 @@ constraint skills upload <skill-directory> --domain <returned-slug>
|
|
|
35
39
|
|
|
36
40
|
## Install skills
|
|
37
41
|
|
|
38
|
-
Choose the target agent and scope explicitly. Prefer `--project`; use
|
|
42
|
+
Choose the target agent and scope explicitly. Prefer `--project`; use
|
|
43
|
+
`--global` only when the user wants the skill across projects. Prefer managed
|
|
44
|
+
links and use `--copy` only when independent files are required. Never
|
|
45
|
+
overwrite locally edited installed skills.
|
|
39
46
|
|
|
40
|
-
##
|
|
47
|
+
## Report a run
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
A run is the posting of a transcript in which a skill was invoked, and the user
|
|
50
|
+
drives it: report only when the user asks to put a session on the record. Never
|
|
51
|
+
report automatically or unprompted.
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
constraint skills run start <skill-name> --client <client> --session <session-id>
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Use the invoked skill's frontmatter `name`. Normalize the client to `codex` or `claude-code`. Use the current client session identifier. Retain the exact run ID printed by the command. If one session invokes multiple skills, start and retain a distinct run ID for each invocation.
|
|
49
|
-
|
|
50
|
-
If the command fails, report the failure briefly and continue the user's work. Do not fabricate a run ID.
|
|
51
|
-
|
|
52
|
-
## Complete a run
|
|
53
|
-
|
|
54
|
-
After finishing the governed skill's work and before ending the response, locate the current session's real JSON or JSONL transcript file and run:
|
|
53
|
+
When asked, locate the current session's real JSON or JSONL transcript file and run:
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
|
-
constraint
|
|
56
|
+
constraint report <transcript-path> --skill <skill-name>
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
Pass the explicit local path
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
Pass the explicit local path to the transcript as it exists at that moment.
|
|
60
|
+
Use the invoked skill's frontmatter `name`. Client and session id are read
|
|
61
|
+
from the transcript path; re-reporting the same session later supersedes the
|
|
62
|
+
earlier report. Never create a replacement transcript, summarize it, copy it
|
|
63
|
+
elsewhere first, or pass a temporary file. If the transcript path cannot be
|
|
64
|
+
determined, say so instead of guessing.
|
|
65
65
|
|
|
66
66
|
## Work safely
|
|
67
67
|
|
|
@@ -12,7 +12,7 @@ constraint setup
|
|
|
12
12
|
constraint doctor
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`login` uses WorkOS device authorization. Never request or embed an API key or client secret. `setup` detects Codex and Claude Code
|
|
15
|
+
`login` uses WorkOS device authorization. Never request or embed an API key or client secret. `setup` detects Codex and Claude Code and installs this skill globally. It never touches instruction files.
|
|
16
16
|
|
|
17
17
|
## Discover, inspect, install, and update
|
|
18
18
|
|
|
@@ -40,11 +40,11 @@ constraint skills upload <skill-directory> --domain <returned-slug>
|
|
|
40
40
|
|
|
41
41
|
`skills domains` returns only domains where the authenticated user can manage skills. JSON objects contain the exact `slug`, display `name`, and `description`. Discover first and pass one returned slug. Ask the user when multiple domains fit and ownership is unclear.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## Report and review runs
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
constraint
|
|
47
|
-
constraint
|
|
46
|
+
constraint report <actual-transcript-path> --skill <skill-name>
|
|
47
|
+
constraint report <actual-transcript-path> --skill <skill-name> --client codex --session <id>
|
|
48
48
|
|
|
49
49
|
constraint skills run list
|
|
50
50
|
constraint skills run list --team <domain> --user <workos-user-id> --skill <skill>
|
|
@@ -52,6 +52,12 @@ constraint skills run get <run-id>
|
|
|
52
52
|
constraint skills run get <run-id> --raw
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
`report` is user-driven: run it only when the user asks to put a session on the
|
|
56
|
+
record. Client and session id are read from the transcript path itself (Claude
|
|
57
|
+
Code project transcripts and Codex rollouts are named by session id);
|
|
58
|
+
`--client`/`--session` are only needed for files outside those locations.
|
|
59
|
+
Re-reporting the same session supersedes the earlier report.
|
|
60
|
+
|
|
55
61
|
Repeat `--team`, `--user`, or `--skill` to request subsets. Review visibility is permission-controlled.
|
|
56
62
|
|
|
57
63
|
`run get` never prints a transcript to stdout: it writes the run JSON (or the
|
|
@@ -67,4 +73,4 @@ Global flags: `--json`, `--quiet`, `--no-color`, `--help`, and `--version`. Inst
|
|
|
67
73
|
- Setup uncertainty: run `constraint doctor`.
|
|
68
74
|
- Upload denied: rerun `constraint skills domains --json` and respect the returned scope.
|
|
69
75
|
- Install conflict: preserve local edits.
|
|
70
|
-
- Missing transcript: locate the real client JSON or JSONL file before
|
|
76
|
+
- Missing transcript: locate the real client JSON or JSONL file before reporting; if it cannot be found, tell the user instead of guessing.
|
package/src/cli.js
CHANGED
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
taskSpinner,
|
|
21
21
|
} from './prompts.js';
|
|
22
22
|
import {
|
|
23
|
-
applyInstructionSetup,
|
|
24
23
|
installCompanionSkill,
|
|
25
24
|
setupPlan,
|
|
26
25
|
setupStatus,
|
|
@@ -36,7 +35,7 @@ import {
|
|
|
36
35
|
uploadSkill,
|
|
37
36
|
} from './skills.js';
|
|
38
37
|
|
|
39
|
-
const VERSION = '0.
|
|
38
|
+
const VERSION = '0.4.1';
|
|
40
39
|
|
|
41
40
|
const HELP = `Constraint Skills CLI
|
|
42
41
|
|
|
@@ -54,8 +53,7 @@ Usage:
|
|
|
54
53
|
[--copy] [--yes]
|
|
55
54
|
constraint skills update --all [--agent <agent>]... [--project|--global] [--yes]
|
|
56
55
|
constraint skills upload <path> --domain <domain>
|
|
57
|
-
constraint
|
|
58
|
-
constraint skills run complete <run-id> <transcript-path>
|
|
56
|
+
constraint report <transcript-path> --skill <skill> [--client <client>] [--session <id>]
|
|
59
57
|
constraint skills run list [--team <team>]... [--user <user>]... [--skill <skill>]...
|
|
60
58
|
constraint skills run get <run-id>... [--raw] [--output <path>] (always writes a file, prints summary + path)
|
|
61
59
|
|
|
@@ -250,30 +248,17 @@ async function setupCommand(args, out) {
|
|
|
250
248
|
if (dryRun) {
|
|
251
249
|
out.data(plans.map((plan) => ({
|
|
252
250
|
agent: plan.agent,
|
|
253
|
-
instructions: {
|
|
254
|
-
path: plan.instructions.path,
|
|
255
|
-
action: plan.instructions.changed ? 'update' : 'current',
|
|
256
|
-
content: plan.instructions.content,
|
|
257
|
-
},
|
|
258
251
|
skill: { path: plan.skill.path, scope: 'global', action: plan.skill.action },
|
|
259
252
|
})));
|
|
260
253
|
return;
|
|
261
254
|
}
|
|
262
|
-
const hasInstructionChanges = plans.some((plan) => plan.instructions.changed);
|
|
263
255
|
const hasSkillChanges = plans.some((plan) => plan.skill.action !== 'current');
|
|
264
|
-
if (!useUi && !yes &&
|
|
256
|
+
if (!useUi && !yes && hasSkillChanges) {
|
|
265
257
|
throw new Error('Non-interactive setup has pending changes. Pass --yes or use --dry-run.');
|
|
266
258
|
}
|
|
267
259
|
|
|
268
|
-
let configureInstructions = true;
|
|
269
260
|
let installSkill = true;
|
|
270
261
|
if (useUi) {
|
|
271
|
-
note(
|
|
272
|
-
plans.map((plan) => `${humanize(plan.agent)}\n${plan.instructions.path}\n${plan.instructions.changed ? 'Managed block will be added or updated' : 'Already configured'}`).join('\n\n'),
|
|
273
|
-
'1. Configure agent instructions',
|
|
274
|
-
);
|
|
275
|
-
configureInstructions = !hasInstructionChanges || yes || await confirmPlan('Apply these instruction changes?');
|
|
276
|
-
|
|
277
262
|
note(
|
|
278
263
|
[
|
|
279
264
|
'Skill: constraint-skills',
|
|
@@ -285,20 +270,12 @@ async function setupCommand(args, out) {
|
|
|
285
270
|
'',
|
|
286
271
|
]),
|
|
287
272
|
].join('\n').trim(),
|
|
288
|
-
'
|
|
273
|
+
'Install Constraint Skills',
|
|
289
274
|
);
|
|
290
275
|
installSkill = !hasSkillChanges || yes || await confirmPlan(`Install globally for ${agents.map(humanize).join(' and ')}?`);
|
|
291
276
|
}
|
|
292
277
|
|
|
293
|
-
const result = {
|
|
294
|
-
if (configureInstructions) {
|
|
295
|
-
const spinner = taskSpinner(useUi && hasInstructionChanges);
|
|
296
|
-
for (const plan of plans) {
|
|
297
|
-
spinner.message(`Configuring ${humanize(plan.agent)} instructions`);
|
|
298
|
-
result.instructions.push(await applyInstructionSetup(plan.agent));
|
|
299
|
-
}
|
|
300
|
-
spinner.stop('Agent instructions configured.');
|
|
301
|
-
}
|
|
278
|
+
const result = { skill: [] };
|
|
302
279
|
if (installSkill) {
|
|
303
280
|
const spinner = taskSpinner(useUi && hasSkillChanges);
|
|
304
281
|
for (const plan of plans) {
|
|
@@ -310,7 +287,6 @@ async function setupCommand(args, out) {
|
|
|
310
287
|
if (useUi) {
|
|
311
288
|
finish([
|
|
312
289
|
'Setup complete.',
|
|
313
|
-
`Instructions: ${configureInstructions ? `${agents.length} configured` : 'skipped'}`,
|
|
314
290
|
`Skill: ${installSkill ? `installed for ${agents.map(humanize).join(' and ')}` : 'skipped'}`,
|
|
315
291
|
].join('\n'));
|
|
316
292
|
} else out.data(result);
|
|
@@ -471,31 +447,55 @@ async function uploadCommand(args, config, out) {
|
|
|
471
447
|
out.data({ skill_id: detail.skill_id, slug: detail.slug, domain: detail.domain, version: detail.version.version });
|
|
472
448
|
}
|
|
473
449
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
450
|
+
// A run is the posting of a transcript in which a skill was invoked. The user
|
|
451
|
+
// drives reporting: one command, after the session, with the finished
|
|
452
|
+
// transcript. Client and session id come straight from the transcript path —
|
|
453
|
+
// agent clients keep sessions at well-known locations named by session id.
|
|
454
|
+
export function transcriptIdentity(absolute) {
|
|
455
|
+
const basename = path.basename(absolute);
|
|
456
|
+
const stem = basename.replace(/\.(jsonl?|json)$/i, '');
|
|
457
|
+
const uuid = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
458
|
+
if (absolute.includes(`${path.sep}.codex${path.sep}sessions${path.sep}`) || stem.startsWith('rollout-')) {
|
|
459
|
+
const match = stem.match(uuid);
|
|
460
|
+
return { client: 'codex', session: match ? match[0] : stem };
|
|
480
461
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
else out.line(run.run_id);
|
|
462
|
+
if (absolute.includes(`${path.sep}.claude${path.sep}projects${path.sep}`) || uuid.test(stem)) {
|
|
463
|
+
const match = stem.match(uuid);
|
|
464
|
+
return { client: 'claude-code', session: match ? match[0] : stem };
|
|
465
|
+
}
|
|
466
|
+
return { client: null, session: null };
|
|
487
467
|
}
|
|
488
468
|
|
|
489
|
-
async function
|
|
469
|
+
async function reportCommand(args, config, out) {
|
|
470
|
+
const skill = takeOption(args, '--skill');
|
|
471
|
+
const client = takeOption(args, '--client');
|
|
472
|
+
const session = takeOption(args, '--session');
|
|
490
473
|
rejectUnknown(args);
|
|
491
|
-
if (args.length !==
|
|
492
|
-
|
|
493
|
-
|
|
474
|
+
if (args.length !== 1 || !skill) {
|
|
475
|
+
throw new Error('Usage: constraint report <transcript-path> --skill <skill> [--client <client>] [--session <session-id>]');
|
|
476
|
+
}
|
|
477
|
+
const transcript = await readTranscript(args[0]);
|
|
478
|
+
const identity = transcriptIdentity(transcript.absolute);
|
|
479
|
+
const resolvedClient = client || identity.client;
|
|
480
|
+
const resolvedSession = session || identity.session;
|
|
481
|
+
if (!resolvedClient || !resolvedSession) {
|
|
482
|
+
throw new Error('Could not tell the client and session from this path. Pass --client and --session.');
|
|
483
|
+
}
|
|
484
|
+
const query = new URLSearchParams({ skill, client: resolvedClient, session: resolvedSession });
|
|
485
|
+
const run = await apiRequest(config, `/skill-reports?${query.toString()}`, {
|
|
494
486
|
method: 'POST',
|
|
495
487
|
headers: { 'Content-Type': mediaType(transcript.absolute), 'X-Transcript-Filename': transcript.filename },
|
|
496
488
|
body: transcript.content,
|
|
497
489
|
});
|
|
498
|
-
out.data(
|
|
490
|
+
out.data({
|
|
491
|
+
run_id: run.run_id,
|
|
492
|
+
skill: run.skill_slug,
|
|
493
|
+
version: run.skill_version,
|
|
494
|
+
state: run.state,
|
|
495
|
+
client: run.client,
|
|
496
|
+
session: run.client_session_id,
|
|
497
|
+
transcript_bytes: run.transcript_size,
|
|
498
|
+
});
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
async function runList(args, config, out) {
|
|
@@ -560,11 +560,9 @@ async function runGet(args, config, out) {
|
|
|
560
560
|
|
|
561
561
|
async function runCommand(args, config, out) {
|
|
562
562
|
const command = args.shift();
|
|
563
|
-
if (command === 'start') return runStart(args, config, out);
|
|
564
|
-
if (command === 'complete') return runComplete(args, config, out);
|
|
565
563
|
if (command === 'list') return runList(args, config, out);
|
|
566
564
|
if (command === 'get') return runGet(args, config, out);
|
|
567
|
-
throw new Error('Expected skills run
|
|
565
|
+
throw new Error('Expected skills run list or get. Post a transcript with constraint report.');
|
|
568
566
|
}
|
|
569
567
|
|
|
570
568
|
async function skillsCommand(args, config, out) {
|
|
@@ -597,6 +595,7 @@ export async function main(argv) {
|
|
|
597
595
|
if (['login', 'logout', 'whoami'].includes(command)) return authCommand(command, args, config, out);
|
|
598
596
|
if (command === 'setup') return setupCommand(args, out);
|
|
599
597
|
if (command === 'doctor') return doctorCommand(args, config, out);
|
|
598
|
+
if (command === 'report') return reportCommand(args, config, out);
|
|
600
599
|
if (command === 'skills') return skillsCommand(args, config, out);
|
|
601
600
|
throw new Error(`Unknown command: ${command}`);
|
|
602
601
|
}
|
package/src/paths.js
CHANGED
|
@@ -28,18 +28,15 @@ export function agentPaths(agent, { scope = 'global', projectRoot = process.cwd(
|
|
|
28
28
|
const selected = normalizeAgent(agent);
|
|
29
29
|
if (scope === 'project') {
|
|
30
30
|
if (selected === 'codex') {
|
|
31
|
-
return { skills: path.join(projectRoot, '.agents', 'skills')
|
|
31
|
+
return { skills: path.join(projectRoot, '.agents', 'skills') };
|
|
32
32
|
}
|
|
33
|
-
return { skills: path.join(projectRoot, '.claude', 'skills')
|
|
33
|
+
return { skills: path.join(projectRoot, '.claude', 'skills') };
|
|
34
34
|
}
|
|
35
35
|
if (selected === 'codex') {
|
|
36
36
|
const root = process.env.CODEX_HOME ? expandPath(process.env.CODEX_HOME) : path.join(os.homedir(), '.codex');
|
|
37
|
-
return {
|
|
37
|
+
return { skills: path.join(root, 'skills') };
|
|
38
38
|
}
|
|
39
|
-
return {
|
|
40
|
-
instructions: path.join(os.homedir(), '.claude', 'CLAUDE.md'),
|
|
41
|
-
skills: path.join(os.homedir(), '.claude', 'skills'),
|
|
42
|
-
};
|
|
39
|
+
return { skills: path.join(os.homedir(), '.claude', 'skills') };
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
async function exists(candidate) {
|
package/src/setup.js
CHANGED
|
@@ -6,37 +6,6 @@ import { loadConfig, saveConfig } from './config.js';
|
|
|
6
6
|
import { packageDigest, readSkillDirectory, writeSkillDirectory } from './files.js';
|
|
7
7
|
import { agentPaths, normalizeAgent } from './paths.js';
|
|
8
8
|
|
|
9
|
-
export const BLOCK_START = '<!-- constraint-skills:start -->';
|
|
10
|
-
export const BLOCK_END = '<!-- constraint-skills:end -->';
|
|
11
|
-
|
|
12
|
-
const BLOCK = `${BLOCK_START}
|
|
13
|
-
## Constraint Skills
|
|
14
|
-
|
|
15
|
-
When using the Constraint CLI or invoking any installed Agent Skill, load and
|
|
16
|
-
follow the global \`constraint-skills\` companion skill. It contains the CLI
|
|
17
|
-
workflow and records governed runs without modifying the invoked skill.
|
|
18
|
-
${BLOCK_END}`;
|
|
19
|
-
|
|
20
|
-
export function mergeInstructionBlock(existing) {
|
|
21
|
-
const start = existing.indexOf(BLOCK_START);
|
|
22
|
-
const end = existing.indexOf(BLOCK_END);
|
|
23
|
-
if ((start >= 0) !== (end >= 0)) {
|
|
24
|
-
throw new Error('The existing Constraint Skills instruction block is incomplete; fix it before running setup.');
|
|
25
|
-
}
|
|
26
|
-
if (start >= 0 && end >= start) {
|
|
27
|
-
return `${existing.slice(0, start)}${BLOCK}${existing.slice(end + BLOCK_END.length)}`;
|
|
28
|
-
}
|
|
29
|
-
const separator = existing.length === 0 ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
|
|
30
|
-
return `${existing}${separator}${BLOCK}\n`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function writeInstructions(file, content, mode) {
|
|
34
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
35
|
-
const staging = `${file}.constraint-${process.pid}`;
|
|
36
|
-
await fs.writeFile(staging, content, { mode });
|
|
37
|
-
await fs.rename(staging, file);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
9
|
async function companionFiles() {
|
|
41
10
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
42
11
|
return readSkillDirectory(path.resolve(here, '..', 'skills', 'constraint-skills'));
|
|
@@ -49,19 +18,6 @@ export function expectedCompanionHash(config, selected, currentHash) {
|
|
|
49
18
|
return currentHash && recorded.includes(currentHash) ? currentHash : null;
|
|
50
19
|
}
|
|
51
20
|
|
|
52
|
-
async function instructionPlan(selected, paths) {
|
|
53
|
-
let existing = '';
|
|
54
|
-
let mode = 0o644;
|
|
55
|
-
try {
|
|
56
|
-
existing = await fs.readFile(paths.instructions, 'utf8');
|
|
57
|
-
mode = (await fs.stat(paths.instructions)).mode & 0o777;
|
|
58
|
-
} catch (error) {
|
|
59
|
-
if (error && error.code !== 'ENOENT') throw error;
|
|
60
|
-
}
|
|
61
|
-
const content = mergeInstructionBlock(existing);
|
|
62
|
-
return { path: paths.instructions, changed: content !== existing, content, mode };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
21
|
async function skillPlan(selected, paths) {
|
|
66
22
|
const files = await companionFiles();
|
|
67
23
|
const bundledHash = packageDigest(files);
|
|
@@ -89,7 +45,6 @@ export async function setupPlan(agent) {
|
|
|
89
45
|
const paths = agentPaths(selected);
|
|
90
46
|
return {
|
|
91
47
|
agent: selected,
|
|
92
|
-
instructions: await instructionPlan(selected, paths),
|
|
93
48
|
skill: await skillPlan(selected, paths),
|
|
94
49
|
};
|
|
95
50
|
}
|
|
@@ -100,15 +55,6 @@ async function recordConfiguredAgent(selected, config = null) {
|
|
|
100
55
|
await saveConfig(next);
|
|
101
56
|
}
|
|
102
57
|
|
|
103
|
-
export async function applyInstructionSetup(agent) {
|
|
104
|
-
const selected = normalizeAgent(agent);
|
|
105
|
-
const paths = agentPaths(selected);
|
|
106
|
-
const plan = await instructionPlan(selected, paths);
|
|
107
|
-
if (plan.changed) await writeInstructions(plan.path, plan.content, plan.mode);
|
|
108
|
-
await recordConfiguredAgent(selected);
|
|
109
|
-
return { agent: selected, path: plan.path, changed: plan.changed, applied: true };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
58
|
export async function installCompanionSkill(agent) {
|
|
113
59
|
const selected = normalizeAgent(agent);
|
|
114
60
|
const paths = agentPaths(selected);
|
|
@@ -127,42 +73,25 @@ export async function installCompanionSkill(agent) {
|
|
|
127
73
|
|
|
128
74
|
export async function setupAgent(agent, {
|
|
129
75
|
dryRun = false,
|
|
130
|
-
yes = false,
|
|
131
|
-
confirmChange = async () => false,
|
|
132
76
|
output = console.log,
|
|
133
|
-
instructions = true,
|
|
134
|
-
skill = true,
|
|
135
77
|
} = {}) {
|
|
136
78
|
const selected = normalizeAgent(agent);
|
|
137
79
|
const plan = await setupPlan(selected);
|
|
138
|
-
output(`${selected}: ${plan.instructions.path}`);
|
|
139
80
|
if (dryRun) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return { agent: selected, instructions: plan.instructions, skill: plan.skill, applied: false };
|
|
143
|
-
}
|
|
144
|
-
let instructionResult = null;
|
|
145
|
-
let skillResult = null;
|
|
146
|
-
if (instructions) {
|
|
147
|
-
const approved = !plan.instructions.changed || yes || await confirmChange(
|
|
148
|
-
`Add the Constraint Skills block to ${plan.instructions.path}?`,
|
|
149
|
-
);
|
|
150
|
-
if (approved) instructionResult = await applyInstructionSetup(selected);
|
|
81
|
+
output(`Install constraint-skills globally at ${plan.skill.path} (${plan.skill.action}).`);
|
|
82
|
+
return { agent: selected, skill: plan.skill, applied: false };
|
|
151
83
|
}
|
|
152
|
-
|
|
153
|
-
return { agent: selected,
|
|
84
|
+
const skillResult = await installCompanionSkill(selected);
|
|
85
|
+
return { agent: selected, skill: skillResult, applied: true };
|
|
154
86
|
}
|
|
155
87
|
|
|
156
88
|
export async function setupStatus(agent) {
|
|
157
89
|
const selected = normalizeAgent(agent);
|
|
158
90
|
const paths = agentPaths(selected);
|
|
159
91
|
try {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
fs.stat(path.join(paths.skills, 'constraint-skills', 'SKILL.md')),
|
|
163
|
-
]);
|
|
164
|
-
return { agent: selected, instructions: instructions.includes(BLOCK_START), skill: skill.isFile() };
|
|
92
|
+
const skill = await fs.stat(path.join(paths.skills, 'constraint-skills', 'SKILL.md'));
|
|
93
|
+
return { agent: selected, skill: skill.isFile() };
|
|
165
94
|
} catch {
|
|
166
|
-
return { agent: selected,
|
|
95
|
+
return { agent: selected, skill: false };
|
|
167
96
|
}
|
|
168
97
|
}
|