@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 +2 -3
- package/src/cli.js +56 -145
- package/src/config.js +0 -3
- package/src/prompts.js +3 -13
- package/skills/constraint-skills/SKILL.md +0 -71
- package/skills/constraint-skills/agents/openai.yaml +0 -4
- package/skills/constraint-skills/references/commands.md +0 -76
- package/src/setup.js +0 -97
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constraint/cli",
|
|
3
|
-
"version": "0.4.
|
|
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.
|
|
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>]... [--
|
|
59
|
-
constraint skills run get <run-id
|
|
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, {
|
|
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}
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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'}
|
|
372
|
-
`
|
|
373
|
-
`
|
|
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 (
|
|
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
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
|
34
|
+
message: 'Where should this skill be available?',
|
|
35
35
|
options: [
|
|
36
|
-
{ value: 'project', label: '
|
|
37
|
-
{ value: 'global', label: '
|
|
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,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
|
-
}
|