@constraint/cli 0.4.2 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constraint/cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Install, publish, and report Agent Skills with Constraint",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,8 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "bin",
11
- "src",
12
- "skills"
11
+ "src"
13
12
  ],
14
13
  "scripts": {
15
14
  "check": "node --check bin/constraint.js && node --check src/cli.js",
package/src/cli.js CHANGED
@@ -8,22 +8,16 @@ import { rejectUnknown, takeFlag, takeOption } from './arguments.js';
8
8
  import { login, logout } from './auth.js';
9
9
  import { loadConfig } from './config.js';
10
10
  import { mediaType } from './files.js';
11
- import { AGENTS, detectAgents, findProjectRoot, normalizeAgent } from './paths.js';
11
+ import { AGENTS, agentPaths, detectAgents, findProjectRoot, normalizeAgent } from './paths.js';
12
12
  import {
13
13
  begin,
14
14
  chooseAgents,
15
- chooseMode,
16
15
  chooseScope,
17
16
  confirmPlan,
18
17
  finish,
19
18
  note,
20
19
  taskSpinner,
21
20
  } from './prompts.js';
22
- import {
23
- installCompanionSkill,
24
- setupPlan,
25
- setupStatus,
26
- } from './setup.js';
27
21
  import {
28
22
  configuredAgents,
29
23
  identifyLocalSkill,
@@ -36,16 +30,14 @@ import {
36
30
  uploadSkill,
37
31
  } from './skills.js';
38
32
 
39
- const VERSION = '0.4.2';
33
+ const VERSION = '0.4.3';
40
34
 
41
35
  const HELP = `Constraint Skills CLI
42
36
 
43
37
  Usage:
44
38
  constraint login | logout | whoami
45
- constraint setup [--agent codex|claude-code]... [--dry-run] [--yes]
46
39
  constraint doctor [--agent codex|claude-code]...
47
40
  constraint skills list [--search <text>]
48
- constraint skills list --installed [--project|--global] [--agent <agent>]...
49
41
  constraint skills domains
50
42
  constraint skills inspect <skill> [--version <version>] [--files]
51
43
  constraint skills install <skill> [--version <version>] [--agent <agent>]...
@@ -53,10 +45,10 @@ Usage:
53
45
  constraint skills update <skill> [--agent <agent>]... [--project|--global]
54
46
  [--copy] [--yes]
55
47
  constraint skills update --all [--agent <agent>]... [--project|--global] [--yes]
56
- constraint skills upload <path> --domain <domain>
48
+ constraint skills upload <path> [--domain <domain>]
57
49
  constraint report <transcript-path> --skill <skill> [--client <client>] [--session <id>]
58
- constraint skills run list [--team <team>]... [--user <user>]... [--skill <skill>]...
59
- constraint skills run get <run-id>... [--raw] [--output <path>] (always writes a file, prints summary + path)
50
+ constraint skills run list [--team <team>]... [--skill <skill>]...
51
+ constraint skills run get <run-id> [--raw] [--output <path>] (writes a file, prints summary + path)
60
52
 
61
53
  Global options: --json --quiet --no-color --help --version`;
62
54
 
@@ -89,14 +81,8 @@ function humanize(value) {
89
81
  return String(value || '').replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
90
82
  }
91
83
 
92
- export function skillListPresentation(items, { installed = false, search = null, scope = 'project' } = {}) {
84
+ export function skillListPresentation(items, { search = null } = {}) {
93
85
  if (items.length === 0) {
94
- if (installed) {
95
- return {
96
- title: 'No installed skills',
97
- message: `Nothing is installed in ${scope} scope yet.\n\nBrowse available skills:\nconstraint skills list`,
98
- };
99
- }
100
86
  if (search) {
101
87
  return {
102
88
  title: 'No matching skills',
@@ -109,20 +95,10 @@ export function skillListPresentation(items, { installed = false, search = null,
109
95
  };
110
96
  }
111
97
 
112
- if (installed) {
113
- return {
114
- title: `${items.length} installed skill${items.length === 1 ? '' : 's'}`,
115
- message: items.map((item) => [
116
- `${item.slug} · v${item.version} · ${item.agent}`,
117
- `${humanize(item.scope)} · ${humanize(item.mode)} · ${item.path}`,
118
- ].join('\n')).join('\n\n'),
119
- };
120
- }
121
-
122
98
  return {
123
99
  title: `${items.length} available skill${items.length === 1 ? '' : 's'}`,
124
100
  message: items.map((item) => [
125
- `${item.name} (${item.slug}) · v${item.latest_version} · ${humanize(item.domain)}`,
101
+ `${item.name} · v${item.latest_version} · ${humanize(item.domain)}`,
126
102
  item.description,
127
103
  ].join('\n')).join('\n\n'),
128
104
  };
@@ -194,7 +170,7 @@ async function resolveAgents(provided, out, { prompt = true } = {}) {
194
170
  async function chooseDomain(domains) {
195
171
  if (domains.length === 0) throw new Error('You do not have permission to upload skills.');
196
172
  if (domains.length === 1) return domains[0];
197
- if (!process.stdin.isTTY) throw new Error('Upload has more than one permitted domain and requires an interactive terminal.');
173
+ if (!process.stdin.isTTY) throw new Error(`Choose a team with --domain. You can upload to: ${domains.join(', ')}`);
198
174
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
199
175
  domains.forEach((domain, index) => process.stdout.write(`${index + 1}. ${domain}\n`));
200
176
  const answer = await rl.question('Choose the skill domain: ');
@@ -223,74 +199,14 @@ async function authCommand(command, args, config, out) {
223
199
  await logout(config);
224
200
  out.line('Logged out.');
225
201
  } else {
226
- out.data(await apiRequest(config, '/me'));
227
- }
228
- }
229
-
230
- async function setupCommand(args, out) {
231
- let agents = takeAgents(args);
232
- const dryRun = takeFlag(args, '--dry-run');
233
- const yes = takeFlag(args, '--yes');
234
- rejectUnknown(args);
235
- if (args.length) throw new Error('setup does not accept positional arguments');
236
- const useUi = interactive(out) && !dryRun;
237
- if (useUi) begin('Constraint Skills setup');
238
- if (!agents.length) {
239
- const configured = await configuredAgents();
240
- const detected = configured.length ? configured : await detectAgents();
241
- agents = useUi && !yes ? await chooseAgents(detected) : detected;
242
- }
243
- if (!agents.length) throw new Error('Choose at least one agent with --agent.');
244
- const plans = await Promise.all(agents.map(setupPlan));
245
- const conflicts = plans.filter((plan) => plan.skill.action === 'conflict');
246
- if (conflicts.length) {
247
- throw new Error(`Local Constraint skill has uncommitted changes and was not replaced: ${conflicts.map((plan) => plan.skill.path).join(', ')}`);
248
- }
249
- if (dryRun) {
250
- out.data(plans.map((plan) => ({
251
- agent: plan.agent,
252
- skill: { path: plan.skill.path, scope: 'global', action: plan.skill.action },
253
- })));
254
- return;
255
- }
256
- const hasSkillChanges = plans.some((plan) => plan.skill.action !== 'current');
257
- if (!useUi && !yes && hasSkillChanges) {
258
- throw new Error('Non-interactive setup has pending changes. Pass --yes or use --dry-run.');
259
- }
260
-
261
- let installSkill = true;
262
- if (useUi) {
263
- note(
264
- [
265
- 'Skill: constraint-skills',
266
- 'Scope: Global',
267
- '',
268
- ...plans.flatMap((plan) => [
269
- `${humanize(plan.agent)} destination:`,
270
- `${plan.skill.path} · ${plan.skill.action === 'current' ? 'already current' : plan.skill.action}`,
271
- '',
272
- ]),
273
- ].join('\n').trim(),
274
- 'Install Constraint Skills',
275
- );
276
- installSkill = !hasSkillChanges || yes || await confirmPlan(`Install globally for ${agents.map(humanize).join(' and ')}?`);
277
- }
278
-
279
- const result = { skill: [] };
280
- if (installSkill) {
281
- const spinner = taskSpinner(useUi && hasSkillChanges);
282
- for (const plan of plans) {
283
- spinner.message(`Installing Constraint Skills for ${humanize(plan.agent)}`);
284
- result.skill.push(await installCompanionSkill(plan.agent));
202
+ const me = await apiRequest(config, '/me');
203
+ if (out.json) {
204
+ out.data(me);
205
+ return;
285
206
  }
286
- spinner.stop('Constraint Skills installed globally.');
207
+ const teams = (me.domains || []).map((domain) => domain.name || domain.slug);
208
+ out.line(`Signed in as ${me.workos_user_id} · ${me.roles?.join(', ') || 'member'} · ${teams.length} team${teams.length === 1 ? '' : 's'}${teams.length ? ` (${teams.join(', ')})` : ''}`);
287
209
  }
288
- if (useUi) {
289
- finish([
290
- 'Setup complete.',
291
- `Skill: ${installSkill ? `installed for ${agents.map(humanize).join(' and ')}` : 'skipped'}`,
292
- ].join('\n'));
293
- } else out.data(result);
294
210
  }
295
211
 
296
212
  async function doctorCommand(args, config, out) {
@@ -305,30 +221,35 @@ async function doctorCommand(args, config, out) {
305
221
  } catch (error) {
306
222
  authError = error.message;
307
223
  }
308
- out.data({
309
- api_url: config.apiUrl,
310
- authenticated: Boolean(identity),
311
- identity,
312
- auth_error: authError,
313
- agents: await Promise.all(agents.map(setupStatus)),
314
- });
224
+ const statuses = await Promise.all(agents.map(async (agent) => {
225
+ const selected = normalizeAgent(agent);
226
+ try {
227
+ await fs.stat(path.join(agentPaths(selected).skills, 'constraint-skills', 'SKILL.md'));
228
+ return { agent: selected, skill: true };
229
+ } catch {
230
+ return { agent: selected, skill: false };
231
+ }
232
+ }));
233
+ if (out.json) {
234
+ out.data({
235
+ api_url: config.apiUrl,
236
+ authenticated: Boolean(identity),
237
+ identity,
238
+ auth_error: authError,
239
+ agents: statuses,
240
+ });
241
+ return;
242
+ }
243
+ out.line(identity ? `Signed in against ${config.apiUrl}.` : `Not signed in (${authError || 'run constraint login'}).`);
244
+ for (const status of statuses) {
245
+ out.line(`${humanize(status.agent)}: companion skill ${status.skill ? 'installed' : 'missing — run constraint skills install constraint-skills --global'}.`);
246
+ }
315
247
  }
316
248
 
317
249
  async function listSkills(args, config, out) {
318
250
  const search = takeOption(args, '--search');
319
- const installed = takeFlag(args, '--installed');
320
- const scopeFlag = takeScope(args);
321
- const agents = takeAgents(args);
322
251
  rejectUnknown(args);
323
252
  if (args.length) throw new Error('skills list does not accept positional arguments');
324
- if (installed) {
325
- if (search) throw new Error('--search cannot be combined with --installed.');
326
- const scope = scopeFlag || 'project';
327
- const skills = await installedSkills({ scope, agents });
328
- printSkillList(skills, { installed: true, scope }, out);
329
- return;
330
- }
331
- if (scopeFlag || agents.length) throw new Error('--project, --global, and --agent require --installed.');
332
253
  const query = search ? `?search=${encodeURIComponent(search)}` : '';
333
254
  const skills = await apiRequest(config, `/skills${query}`);
334
255
  printSkillList(skills, { search }, out);
@@ -365,13 +286,11 @@ async function installCommand(args, config, out, update = false) {
365
286
  .map((item) => item.agent))];
366
287
  }
367
288
  agents = await resolveAgents(agents, out, { prompt: !yes && !update });
368
- if (!copyRequested && useUi && !yes) copy = (await chooseMode()) === 'copy';
369
289
  if (useUi) {
370
290
  note([
371
- `${update ? 'Update' : 'Install'}: ${args[0]}${version ? ` v${version}` : ' (latest)'}`,
372
- `Agents: ${agents.join(', ')}`,
373
- `Scope: ${scope}${root ? ` (${root})` : ''}`,
374
- `Mode: ${copy === undefined ? 'preserve installed mode' : copy ? 'copy' : 'shared package'}`,
291
+ `${update ? 'Update' : 'Install'} ${args[0]}${version ? ` v${version}` : ''}`,
292
+ `For: ${agents.map(humanize).join(' and ')}`,
293
+ scope === 'project' ? `Available only in this project${root ? ` (${root})` : ''}` : 'Available everywhere on this computer',
375
294
  ].join('\n'));
376
295
  if (!yes && !(await confirmPlan(`${update ? 'Update' : 'Install'} this skill?`))) {
377
296
  finish('No changes made.');
@@ -530,7 +449,6 @@ async function reportCommand(args, config, out) {
530
449
  async function runList(args, config, out) {
531
450
  const query = new URLSearchParams();
532
451
  for (const value of takeOption(args, '--team', { multiple: true })) query.append('team', value);
533
- for (const value of takeOption(args, '--user', { multiple: true })) query.append('user', value);
534
452
  for (const value of takeOption(args, '--skill', { multiple: true })) query.append('skill', value);
535
453
  for (const name of ['--after', '--before', '--limit', '--cursor']) {
536
454
  const value = takeOption(args, name);
@@ -555,9 +473,7 @@ async function runGet(args, config, out) {
555
473
  const raw = takeFlag(args, '--raw');
556
474
  const output = takeOption(args, '--output');
557
475
  rejectUnknown(args);
558
- if (!args.length) throw new Error('Usage: constraint skills run get <run-id>...');
559
- if (output && args.length !== 1) throw new Error('--output accepts exactly one run ID');
560
- if (raw && args.length !== 1) throw new Error('--raw accepts exactly one run ID');
476
+ if (args.length !== 1) throw new Error('Usage: constraint skills run get <run-id> [--raw] [--output <path>]');
561
477
  if (raw) {
562
478
  const response = await apiRequest(config, `/skill-runs/${encodeURIComponent(args[0])}/transcript`, { rawResponse: true });
563
479
  const content = Buffer.from(await response.arrayBuffer());
@@ -566,25 +482,21 @@ async function runGet(args, config, out) {
566
482
  out.data({ run_id: args[0], bytes: content.length, path: destination });
567
483
  return;
568
484
  }
569
- const summaries = [];
570
- for (const runId of args) {
571
- const run = await apiRequest(config, `/skill-runs/${encodeURIComponent(runId)}`);
572
- const destination = await runGetDestination(output, runId, '.json');
573
- await fs.writeFile(destination, `${JSON.stringify(run, null, 2)}\n`);
574
- const events = Array.isArray(run.normalized_events) ? run.normalized_events : [];
575
- summaries.push({
576
- run_id: run.run_id,
577
- skill: run.skill_slug,
578
- version: run.skill_version,
579
- state: run.state,
580
- client: run.client,
581
- completed_at: run.completed_at,
582
- events: events.length,
583
- events_with_text: events.filter((event) => event.content).length,
584
- path: destination,
585
- });
586
- }
587
- out.data(summaries.length === 1 ? summaries[0] : summaries);
485
+ const run = await apiRequest(config, `/skill-runs/${encodeURIComponent(args[0])}`);
486
+ const destination = await runGetDestination(output, args[0], '.json');
487
+ await fs.writeFile(destination, `${JSON.stringify(run, null, 2)}\n`);
488
+ const events = Array.isArray(run.normalized_events) ? run.normalized_events : [];
489
+ out.data({
490
+ run_id: run.run_id,
491
+ skill: run.skill_slug,
492
+ version: run.skill_version,
493
+ state: run.state,
494
+ client: run.client,
495
+ completed_at: run.completed_at,
496
+ events: events.length,
497
+ events_with_text: events.filter((event) => event.content).length,
498
+ path: destination,
499
+ });
588
500
  }
589
501
 
590
502
  async function runCommand(args, config, out) {
@@ -622,7 +534,6 @@ export async function main(argv) {
622
534
  const command = args.shift();
623
535
  const config = await loadConfig();
624
536
  if (['login', 'logout', 'whoami'].includes(command)) return authCommand(command, args, config, out);
625
- if (command === 'setup') return setupCommand(args, out);
626
537
  if (command === 'doctor') return doctorCommand(args, config, out);
627
538
  if (command === 'report') return reportCommand(args, config, out);
628
539
  if (command === 'skills') return skillsCommand(args, config, out);
package/src/config.js CHANGED
@@ -28,9 +28,6 @@ export async function loadConfig() {
28
28
  return {
29
29
  apiUrl: (process.env.CONSTRAINT_API_URL || saved.apiUrl || DEFAULT_API_URL).replace(/\/$/, ''),
30
30
  agents: Array.isArray(saved.agents) ? saved.agents : Array.isArray(saved.clients) ? saved.clients : [],
31
- companion_hashes: saved.companion_hashes && typeof saved.companion_hashes === 'object'
32
- ? saved.companion_hashes
33
- : {},
34
31
  };
35
32
  }
36
33
 
package/src/prompts.js CHANGED
@@ -31,20 +31,10 @@ export async function chooseAgents(initial = []) {
31
31
 
32
32
  export async function chooseScope() {
33
33
  return cancelled(await p.select({
34
- message: 'Where should the skill be installed?',
34
+ message: 'Where should this skill be available?',
35
35
  options: [
36
- { value: 'project', label: 'This project', hint: 'portable lockfile, repository-local skill' },
37
- { value: 'global', label: 'Globally', hint: 'available in every project for this user' },
38
- ],
39
- }));
40
- }
41
-
42
- export async function chooseMode() {
43
- return cancelled(await p.select({
44
- message: 'How should agent directories reference the package?',
45
- options: [
46
- { value: 'symlink', label: 'Shared package', hint: 'recommended; one managed copy' },
47
- { value: 'copy', label: 'Copy files', hint: 'use when links are unavailable' },
36
+ { value: 'project', label: 'Only in this project' },
37
+ { value: 'global', label: 'Everywhere on this computer' },
48
38
  ],
49
39
  }));
50
40
  }
@@ -1,71 +0,0 @@
1
- ---
2
- name: constraint-skills
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
- ---
5
-
6
- # Constraint Skills
7
-
8
- Use the `constraint` command for organization-authorized skills. Do not edit its
9
- config, lockfiles, installation receipts, or authorization tokens directly.
10
-
11
- ## Choose the operation
12
-
13
- - Authenticate or diagnose: `constraint login`, `constraint whoami`, `constraint doctor`.
14
- - Discover or inspect skills: `constraint skills list`, `constraint skills inspect`.
15
- - Install or update: `constraint skills install`, `constraint skills update`.
16
- - Discover upload destinations: `constraint skills domains --json`.
17
- - Upload a local skill folder: `constraint skills upload`.
18
- - Report a session: `constraint report`.
19
- - Review runs: `constraint skills run list`, `constraint skills run get`.
20
-
21
- Read [references/commands.md](references/commands.md) before constructing flags
22
- or handling a less common operation.
23
-
24
- ## Upload skills
25
-
26
- Upload the skill directory, not an individual `SKILL.md`. Before every upload, run:
27
-
28
- ```bash
29
- constraint skills domains --json
30
- ```
31
-
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.
35
-
36
- ```bash
37
- constraint skills upload <skill-directory> --domain <returned-slug>
38
- ```
39
-
40
- ## Install skills
41
-
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.
46
-
47
- ## Report a run
48
-
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.
52
-
53
- When asked, locate the current session's real JSON or JSONL transcript file and run:
54
-
55
- ```bash
56
- constraint report <transcript-path> --skill <skill-name>
57
- ```
58
-
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
-
66
- ## Work safely
67
-
68
- - Use `--json` when parsing output.
69
- - Respect WorkOS authorization failures; never bypass domain permissions.
70
- - Do not invent an uninstall workflow.
71
- - After mutations, report the affected skill, version, agent, scope, domain, or run ID as applicable.
@@ -1,4 +0,0 @@
1
- interface:
2
- display_name: "Constraint Skills"
3
- short_description: "Operate and report governed agent skills"
4
- default_prompt: "Use $constraint-skills to manage a governed skill and report its run."
@@ -1,76 +0,0 @@
1
- # Constraint CLI command reference
2
-
3
- ## Install, authenticate, and configure
4
-
5
- ```bash
6
- npm install --global @constraint/cli
7
- constraint --version
8
- constraint login
9
- constraint whoami
10
- constraint logout
11
- constraint setup
12
- constraint doctor
13
- ```
14
-
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
-
17
- ## Discover, inspect, install, and update
18
-
19
- ```bash
20
- constraint skills list
21
- constraint skills list --search <text>
22
- constraint skills inspect <skill> [--version <number>] [--files]
23
-
24
- constraint skills install <skill> --agent codex --project
25
- constraint skills install <skill> --agent claude-code --global
26
- constraint skills list --installed --project
27
- constraint skills update <skill> --project
28
- constraint skills update --all --global
29
- ```
30
-
31
- The catalog is already authorization-filtered. Project installs create `skills-lock.json`. Global receipts live in Constraint's per-user configuration directory.
32
-
33
- ## Discover upload domains and upload
34
-
35
- ```bash
36
- constraint skills domains
37
- constraint skills domains --json
38
- constraint skills upload <skill-directory> --domain <returned-slug>
39
- ```
40
-
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
-
43
- ## Report and review runs
44
-
45
- ```bash
46
- constraint report <actual-transcript-path> --skill <skill-name>
47
- constraint report <actual-transcript-path> --skill <skill-name> --client codex --session <id>
48
-
49
- constraint skills run list
50
- constraint skills run list --team <domain> --user <workos-user-id> --skill <skill>
51
- constraint skills run get <run-id>
52
- constraint skills run get <run-id> --raw
53
- ```
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
-
61
- Repeat `--team`, `--user`, or `--skill` to request subsets. Review visibility is permission-controlled.
62
-
63
- `run get` never prints a transcript to stdout: it writes the run JSON (or the
64
- original transcript file with `--raw`) under the system temp directory and
65
- prints a one-object summary containing the file `path`. Read the file from
66
- that path. Pass `--output <path>` to choose the destination (single run only).
67
-
68
- ## Automation and recovery
69
-
70
- Global flags: `--json`, `--quiet`, `--no-color`, `--help`, and `--version`. Installer workflows also accept `--yes`.
71
-
72
- - Authentication failure: run `constraint login`, then `constraint whoami`.
73
- - Setup uncertainty: run `constraint doctor`.
74
- - Upload denied: rerun `constraint skills domains --json` and respect the returned scope.
75
- - Install conflict: preserve local edits.
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/setup.js DELETED
@@ -1,97 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- import { loadConfig, saveConfig } from './config.js';
6
- import { packageDigest, readSkillDirectory, writeSkillDirectory } from './files.js';
7
- import { agentPaths, normalizeAgent } from './paths.js';
8
-
9
- async function companionFiles() {
10
- const here = path.dirname(fileURLToPath(import.meta.url));
11
- return readSkillDirectory(path.resolve(here, '..', 'skills', 'constraint-skills'));
12
- }
13
-
14
- export function expectedCompanionHash(config, selected, currentHash) {
15
- const direct = config.companion_hashes?.[selected] || null;
16
- if (direct) return direct;
17
- const recorded = Object.values(config.companion_hashes || {});
18
- return currentHash && recorded.includes(currentHash) ? currentHash : null;
19
- }
20
-
21
- async function skillPlan(selected, paths) {
22
- const files = await companionFiles();
23
- const bundledHash = packageDigest(files);
24
- const config = await loadConfig();
25
- const target = path.join(paths.skills, 'constraint-skills');
26
- let currentHash = null;
27
- try {
28
- currentHash = packageDigest(await readSkillDirectory(target));
29
- } catch (error) {
30
- if (error && error.code !== 'ENOENT') throw error;
31
- }
32
- const expectedHash = expectedCompanionHash(config, selected, currentHash);
33
- const action = currentHash === bundledHash
34
- ? 'current'
35
- : currentHash === null
36
- ? 'install'
37
- : expectedHash && currentHash === expectedHash
38
- ? 'update'
39
- : 'conflict';
40
- return { path: target, action, bundledHash, expectedHash };
41
- }
42
-
43
- export async function setupPlan(agent) {
44
- const selected = normalizeAgent(agent);
45
- const paths = agentPaths(selected);
46
- return {
47
- agent: selected,
48
- skill: await skillPlan(selected, paths),
49
- };
50
- }
51
-
52
- async function recordConfiguredAgent(selected, config = null) {
53
- const next = config || await loadConfig();
54
- next.agents = [...new Set([...next.agents.map(normalizeAgent), selected])];
55
- await saveConfig(next);
56
- }
57
-
58
- export async function installCompanionSkill(agent) {
59
- const selected = normalizeAgent(agent);
60
- const paths = agentPaths(selected);
61
- const files = await companionFiles();
62
- const config = await loadConfig();
63
- config.companion_hashes ||= {};
64
- const target = path.join(paths.skills, 'constraint-skills');
65
- const plan = await skillPlan(selected, paths);
66
- const result = await writeSkillDirectory(target, files, {
67
- expectedHash: plan.expectedHash,
68
- });
69
- config.companion_hashes[selected] = result.hash;
70
- await recordConfiguredAgent(selected, config);
71
- return { agent: selected, path: target, changed: result.changed, applied: true };
72
- }
73
-
74
- export async function setupAgent(agent, {
75
- dryRun = false,
76
- output = console.log,
77
- } = {}) {
78
- const selected = normalizeAgent(agent);
79
- const plan = await setupPlan(selected);
80
- if (dryRun) {
81
- output(`Install constraint-skills globally at ${plan.skill.path} (${plan.skill.action}).`);
82
- return { agent: selected, skill: plan.skill, applied: false };
83
- }
84
- const skillResult = await installCompanionSkill(selected);
85
- return { agent: selected, skill: skillResult, applied: true };
86
- }
87
-
88
- export async function setupStatus(agent) {
89
- const selected = normalizeAgent(agent);
90
- const paths = agentPaths(selected);
91
- try {
92
- const skill = await fs.stat(path.join(paths.skills, 'constraint-skills', 'SKILL.md'));
93
- return { agent: selected, skill: skill.isFile() };
94
- } catch {
95
- return { agent: selected, skill: false };
96
- }
97
- }