@constraint/cli 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/package.json +1 -1
- package/skills/constraint-skills/references/commands.md +1 -1
- package/src/cli.js +78 -12
- package/src/setup.js +90 -24
package/README.md
CHANGED
|
@@ -55,9 +55,11 @@ Or target agents explicitly:
|
|
|
55
55
|
constraint setup --agent codex --agent claude-code
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Setup
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
Setup presents two separate stages: first it offers to add a marked Constraint
|
|
59
|
+
block to each agent's global instructions, then it shows the global destination
|
|
60
|
+
and installs the `constraint-skills` companion using the regular install-plan
|
|
61
|
+
flow. Existing `AGENTS.md` and `CLAUDE.md` content outside that block is
|
|
62
|
+
preserved.
|
|
61
63
|
|
|
62
64
|
Preview the instruction change without applying it:
|
|
63
65
|
|
package/package.json
CHANGED
|
@@ -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, then separately presents the managed global-instruction change and this skill's global installation destinations. It never replaces unrelated instructions.
|
|
16
16
|
|
|
17
17
|
## Discover, inspect, install, and update
|
|
18
18
|
|
package/src/cli.js
CHANGED
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
note,
|
|
19
19
|
taskSpinner,
|
|
20
20
|
} from './prompts.js';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
applyInstructionSetup,
|
|
23
|
+
installCompanionSkill,
|
|
24
|
+
setupPlan,
|
|
25
|
+
setupStatus,
|
|
26
|
+
} from './setup.js';
|
|
22
27
|
import {
|
|
23
28
|
configuredAgents,
|
|
24
29
|
installSkill,
|
|
@@ -30,7 +35,7 @@ import {
|
|
|
30
35
|
uploadSkill,
|
|
31
36
|
} from './skills.js';
|
|
32
37
|
|
|
33
|
-
const VERSION = '0.3.
|
|
38
|
+
const VERSION = '0.3.4';
|
|
34
39
|
|
|
35
40
|
const HELP = `Constraint Skills CLI
|
|
36
41
|
|
|
@@ -236,17 +241,78 @@ async function setupCommand(args, out) {
|
|
|
236
241
|
agents = useUi && !yes ? await chooseAgents(detected) : detected;
|
|
237
242
|
}
|
|
238
243
|
if (!agents.length) throw new Error('Choose at least one agent with --agent.');
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
instructions: {
|
|
253
|
+
path: plan.instructions.path,
|
|
254
|
+
action: plan.instructions.changed ? 'update' : 'current',
|
|
255
|
+
content: plan.instructions.content,
|
|
256
|
+
},
|
|
257
|
+
skill: { path: plan.skill.path, scope: 'global', action: plan.skill.action },
|
|
258
|
+
})));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const hasInstructionChanges = plans.some((plan) => plan.instructions.changed);
|
|
262
|
+
const hasSkillChanges = plans.some((plan) => plan.skill.action !== 'current');
|
|
263
|
+
if (!useUi && !yes && (hasInstructionChanges || hasSkillChanges)) {
|
|
264
|
+
throw new Error('Non-interactive setup has pending changes. Pass --yes or use --dry-run.');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let configureInstructions = true;
|
|
268
|
+
let installSkill = true;
|
|
269
|
+
if (useUi) {
|
|
270
|
+
note(
|
|
271
|
+
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'),
|
|
272
|
+
'1. Configure agent instructions',
|
|
273
|
+
);
|
|
274
|
+
configureInstructions = !hasInstructionChanges || yes || await confirmPlan('Apply these instruction changes?');
|
|
275
|
+
|
|
276
|
+
note(
|
|
277
|
+
[
|
|
278
|
+
'Skill: constraint-skills',
|
|
279
|
+
'Scope: Global',
|
|
280
|
+
'',
|
|
281
|
+
...plans.flatMap((plan) => [
|
|
282
|
+
`${humanize(plan.agent)} destination:`,
|
|
283
|
+
`${plan.skill.path} · ${plan.skill.action === 'current' ? 'already current' : plan.skill.action}`,
|
|
284
|
+
'',
|
|
285
|
+
]),
|
|
286
|
+
].join('\n').trim(),
|
|
287
|
+
'2. Install Constraint Skills',
|
|
288
|
+
);
|
|
289
|
+
installSkill = !hasSkillChanges || yes || await confirmPlan(`Install globally for ${agents.map(humanize).join(' and ')}?`);
|
|
247
290
|
}
|
|
248
|
-
|
|
249
|
-
|
|
291
|
+
|
|
292
|
+
const result = { instructions: [], skill: [] };
|
|
293
|
+
if (configureInstructions) {
|
|
294
|
+
const spinner = taskSpinner(useUi && hasInstructionChanges);
|
|
295
|
+
for (const plan of plans) {
|
|
296
|
+
spinner.message(`Configuring ${humanize(plan.agent)} instructions`);
|
|
297
|
+
result.instructions.push(await applyInstructionSetup(plan.agent));
|
|
298
|
+
}
|
|
299
|
+
spinner.stop('Agent instructions configured.');
|
|
300
|
+
}
|
|
301
|
+
if (installSkill) {
|
|
302
|
+
const spinner = taskSpinner(useUi && hasSkillChanges);
|
|
303
|
+
for (const plan of plans) {
|
|
304
|
+
spinner.message(`Installing Constraint Skills for ${humanize(plan.agent)}`);
|
|
305
|
+
result.skill.push(await installCompanionSkill(plan.agent));
|
|
306
|
+
}
|
|
307
|
+
spinner.stop('Constraint Skills installed globally.');
|
|
308
|
+
}
|
|
309
|
+
if (useUi) {
|
|
310
|
+
finish([
|
|
311
|
+
'Setup complete.',
|
|
312
|
+
`Instructions: ${configureInstructions ? `${agents.length} configured` : 'skipped'}`,
|
|
313
|
+
`Skill: ${installSkill ? `installed for ${agents.map(humanize).join(' and ')}` : 'skipped'}`,
|
|
314
|
+
].join('\n'));
|
|
315
|
+
} else out.data(result);
|
|
250
316
|
}
|
|
251
317
|
|
|
252
318
|
async function doctorCommand(args, config, out) {
|
package/src/setup.js
CHANGED
|
@@ -42,41 +42,107 @@ async function companionFiles() {
|
|
|
42
42
|
return readSkillDirectory(path.resolve(here, '..', 'skills', 'constraint-skills'));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
dryRun = false,
|
|
47
|
-
yes = false,
|
|
48
|
-
confirmChange = async () => false,
|
|
49
|
-
output = console.log,
|
|
50
|
-
} = {}) {
|
|
51
|
-
const selected = normalizeAgent(agent);
|
|
52
|
-
const paths = agentPaths(selected);
|
|
45
|
+
async function instructionPlan(selected, paths) {
|
|
53
46
|
let existing = '';
|
|
54
|
-
let
|
|
47
|
+
let mode = 0o644;
|
|
55
48
|
try {
|
|
56
49
|
existing = await fs.readFile(paths.instructions, 'utf8');
|
|
57
|
-
|
|
50
|
+
mode = (await fs.stat(paths.instructions)).mode & 0o777;
|
|
58
51
|
} catch (error) {
|
|
59
52
|
if (error && error.code !== 'ENOENT') throw error;
|
|
60
53
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
const content = mergeInstructionBlock(existing);
|
|
55
|
+
return { path: paths.instructions, changed: content !== existing, content, mode };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function skillPlan(selected, paths) {
|
|
59
|
+
const files = await companionFiles();
|
|
60
|
+
const bundledHash = packageDigest(files);
|
|
61
|
+
const config = await loadConfig();
|
|
62
|
+
const expectedHash = config.companion_hashes?.[selected] || null;
|
|
63
|
+
const target = path.join(paths.skills, 'constraint-skills');
|
|
64
|
+
let currentHash = null;
|
|
65
|
+
try {
|
|
66
|
+
currentHash = packageDigest(await readSkillDirectory(target));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error && error.code !== 'ENOENT') throw error;
|
|
69
69
|
}
|
|
70
|
+
const action = currentHash === bundledHash
|
|
71
|
+
? 'current'
|
|
72
|
+
: currentHash === null
|
|
73
|
+
? 'install'
|
|
74
|
+
: expectedHash && currentHash === expectedHash
|
|
75
|
+
? 'update'
|
|
76
|
+
: 'conflict';
|
|
77
|
+
return { path: target, action, bundledHash, expectedHash };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function setupPlan(agent) {
|
|
81
|
+
const selected = normalizeAgent(agent);
|
|
82
|
+
const paths = agentPaths(selected);
|
|
83
|
+
return {
|
|
84
|
+
agent: selected,
|
|
85
|
+
instructions: await instructionPlan(selected, paths),
|
|
86
|
+
skill: await skillPlan(selected, paths),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function recordConfiguredAgent(selected, config = null) {
|
|
91
|
+
const next = config || await loadConfig();
|
|
92
|
+
next.agents = [...new Set([...next.agents.map(normalizeAgent), selected])];
|
|
93
|
+
await saveConfig(next);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function applyInstructionSetup(agent) {
|
|
97
|
+
const selected = normalizeAgent(agent);
|
|
98
|
+
const paths = agentPaths(selected);
|
|
99
|
+
const plan = await instructionPlan(selected, paths);
|
|
100
|
+
if (plan.changed) await writeInstructions(plan.path, plan.content, plan.mode);
|
|
101
|
+
await recordConfiguredAgent(selected);
|
|
102
|
+
return { agent: selected, path: plan.path, changed: plan.changed, applied: true };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function installCompanionSkill(agent) {
|
|
106
|
+
const selected = normalizeAgent(agent);
|
|
107
|
+
const paths = agentPaths(selected);
|
|
70
108
|
const files = await companionFiles();
|
|
71
109
|
const config = await loadConfig();
|
|
72
110
|
config.companion_hashes ||= {};
|
|
73
111
|
const target = path.join(paths.skills, 'constraint-skills');
|
|
74
|
-
await writeSkillDirectory(target, files, {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
config.
|
|
78
|
-
await
|
|
79
|
-
return { agent: selected,
|
|
112
|
+
const result = await writeSkillDirectory(target, files, {
|
|
113
|
+
expectedHash: config.companion_hashes[selected] || null,
|
|
114
|
+
});
|
|
115
|
+
config.companion_hashes[selected] = result.hash;
|
|
116
|
+
await recordConfiguredAgent(selected, config);
|
|
117
|
+
return { agent: selected, path: target, changed: result.changed, applied: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function setupAgent(agent, {
|
|
121
|
+
dryRun = false,
|
|
122
|
+
yes = false,
|
|
123
|
+
confirmChange = async () => false,
|
|
124
|
+
output = console.log,
|
|
125
|
+
instructions = true,
|
|
126
|
+
skill = true,
|
|
127
|
+
} = {}) {
|
|
128
|
+
const selected = normalizeAgent(agent);
|
|
129
|
+
const plan = await setupPlan(selected);
|
|
130
|
+
output(`${selected}: ${plan.instructions.path}`);
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
if (instructions) output(plan.instructions.content);
|
|
133
|
+
if (skill) output(`Install constraint-skills globally at ${plan.skill.path} (${plan.skill.action}).`);
|
|
134
|
+
return { agent: selected, instructions: plan.instructions, skill: plan.skill, applied: false };
|
|
135
|
+
}
|
|
136
|
+
let instructionResult = null;
|
|
137
|
+
let skillResult = null;
|
|
138
|
+
if (instructions) {
|
|
139
|
+
const approved = !plan.instructions.changed || yes || await confirmChange(
|
|
140
|
+
`Add the Constraint Skills block to ${plan.instructions.path}?`,
|
|
141
|
+
);
|
|
142
|
+
if (approved) instructionResult = await applyInstructionSetup(selected);
|
|
143
|
+
}
|
|
144
|
+
if (skill) skillResult = await installCompanionSkill(selected);
|
|
145
|
+
return { agent: selected, instructions: instructionResult, skill: skillResult, applied: true };
|
|
80
146
|
}
|
|
81
147
|
|
|
82
148
|
export async function setupStatus(agent) {
|