@dujavi/ai-md 0.5.0 → 0.6.0
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 +47 -107
- package/bin/ai-md.js +344 -186
- package/lib/build.js +268 -0
- package/lib/commands.js +292 -99
- package/lib/config-paths.js +73 -0
- package/lib/config.js +85 -57
- package/lib/harnesses/index.js +309 -0
- package/lib/link.js +224 -0
- package/lib/rescue.js +95 -0
- package/lib/skeleton.js +117 -0
- package/lib/status.js +110 -35
- package/package.json +4 -3
- package/skeleton/README.md +3 -0
- package/skeleton/agents/agents/skills/.gitkeep +0 -0
- package/skeleton/agents/claude/rules/.gitkeep +0 -0
- package/skeleton/agents/claude/skills/.gitkeep +0 -0
- package/skeleton/agents/cursor/skills/.gitkeep +0 -0
- package/skeleton/agents/gemini/skills/.gitkeep +0 -0
- package/skeleton/agents/opencode/skills/.gitkeep +0 -0
- package/skeleton/projects/.gitkeep +0 -0
- package/skeleton/scripts/.gitkeep +0 -0
- package/skeleton/shared/rules/edit-source-not-dist.mdc +23 -0
- package/skeleton/shared/rules/personal-config.mdc +49 -0
- package/skeleton/shared/skills/ai-md-config/SKILL.md +51 -0
- package/skeleton/templates/base/rules/.gitkeep +0 -0
- package/skeleton/templates/base/rules/project-context.mdc +8 -0
- package/skeleton/templates/base/skills/.gitkeep +0 -0
package/bin/ai-md.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const { spawnSync } = require("child_process");
|
|
5
|
-
const path = require("path");
|
|
6
4
|
const {
|
|
7
5
|
resolveConfig,
|
|
8
6
|
applyEnvFromConfig,
|
|
@@ -17,12 +15,22 @@ const {
|
|
|
17
15
|
applyTemplate,
|
|
18
16
|
linkProject,
|
|
19
17
|
runDoctor,
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
runInstall,
|
|
19
|
+
runPull,
|
|
20
|
+
runPush,
|
|
21
|
+
buildAndLink,
|
|
22
|
+
runBuild,
|
|
23
|
+
runRescue,
|
|
24
|
+
seedSkeleton,
|
|
25
|
+
initRepo,
|
|
26
|
+
harnessList,
|
|
27
|
+
harnessShow,
|
|
28
|
+
harnessSet,
|
|
29
|
+
harnessUnset,
|
|
30
|
+
harnessEnable,
|
|
22
31
|
} = require("../lib/commands");
|
|
23
32
|
const { runScript, runScripts } = require("../lib/scripts");
|
|
24
|
-
|
|
25
|
-
const scriptsDir = path.join(__dirname, "..", "scripts");
|
|
33
|
+
const { defaultLinkMode } = require("../lib/config-paths");
|
|
26
34
|
|
|
27
35
|
function applyFlag(out, flag, args) {
|
|
28
36
|
switch (flag) {
|
|
@@ -42,6 +50,21 @@ function applyFlag(out, flag, args) {
|
|
|
42
50
|
out.fix = true;
|
|
43
51
|
out.force = true;
|
|
44
52
|
return true;
|
|
53
|
+
case "--force-link":
|
|
54
|
+
out.forceLink = true;
|
|
55
|
+
return true;
|
|
56
|
+
case "--init":
|
|
57
|
+
out.init = true;
|
|
58
|
+
return true;
|
|
59
|
+
case "--no-git":
|
|
60
|
+
out.noGit = true;
|
|
61
|
+
return true;
|
|
62
|
+
case "--verbose":
|
|
63
|
+
out.verbose = true;
|
|
64
|
+
return true;
|
|
65
|
+
case "--paths-only":
|
|
66
|
+
out.pathsOnly = true;
|
|
67
|
+
return true;
|
|
45
68
|
case "-m":
|
|
46
69
|
case "--message":
|
|
47
70
|
out.message = args.shift();
|
|
@@ -64,6 +87,18 @@ function applyFlag(out, flag, args) {
|
|
|
64
87
|
case "--dir":
|
|
65
88
|
out.dir = expandHome(args.shift());
|
|
66
89
|
return true;
|
|
90
|
+
case "--skills":
|
|
91
|
+
out.skills = args.shift();
|
|
92
|
+
return true;
|
|
93
|
+
case "--rules":
|
|
94
|
+
out.rules = args.shift();
|
|
95
|
+
return true;
|
|
96
|
+
case "--format":
|
|
97
|
+
out.format = args.shift();
|
|
98
|
+
return true;
|
|
99
|
+
case "--link-mode":
|
|
100
|
+
out.linkMode = args.shift();
|
|
101
|
+
return true;
|
|
67
102
|
case "--agents":
|
|
68
103
|
out.agents = String(args.shift() || "cursor")
|
|
69
104
|
.split(",")
|
|
@@ -100,6 +135,11 @@ function parseArgs(argv) {
|
|
|
100
135
|
force: false,
|
|
101
136
|
dryRun: false,
|
|
102
137
|
fix: false,
|
|
138
|
+
forceLink: false,
|
|
139
|
+
init: false,
|
|
140
|
+
noGit: false,
|
|
141
|
+
verbose: false,
|
|
142
|
+
pathsOnly: false,
|
|
103
143
|
message: null,
|
|
104
144
|
repo: null,
|
|
105
145
|
name: null,
|
|
@@ -107,7 +147,11 @@ function parseArgs(argv) {
|
|
|
107
147
|
from: "base",
|
|
108
148
|
remote: null,
|
|
109
149
|
dir: null,
|
|
110
|
-
|
|
150
|
+
skills: null,
|
|
151
|
+
rules: null,
|
|
152
|
+
format: null,
|
|
153
|
+
linkMode: null,
|
|
154
|
+
agents: null,
|
|
111
155
|
scripts: [],
|
|
112
156
|
scriptName: null,
|
|
113
157
|
scriptArgs: [],
|
|
@@ -129,7 +173,6 @@ function parseArgs(argv) {
|
|
|
129
173
|
out.cmd = args.shift();
|
|
130
174
|
}
|
|
131
175
|
|
|
132
|
-
// `script <name> [--] [args...]` — ai-md flags only before the name
|
|
133
176
|
if (out.cmd === "script" || out.cmd === "run-script") {
|
|
134
177
|
while (args.length) {
|
|
135
178
|
const a = args[0];
|
|
@@ -144,11 +187,7 @@ function parseArgs(argv) {
|
|
|
144
187
|
if (a.startsWith("-")) {
|
|
145
188
|
args.shift();
|
|
146
189
|
if (!applyFlag(out, a, args)) {
|
|
147
|
-
fail(`unknown flag: ${a}`, {
|
|
148
|
-
exitCode: 2,
|
|
149
|
-
json: out.json,
|
|
150
|
-
help: ["Run `ai-md --help`"],
|
|
151
|
-
});
|
|
190
|
+
fail(`unknown flag: ${a}`, { exitCode: 2, json: out.json });
|
|
152
191
|
process.exit(2);
|
|
153
192
|
}
|
|
154
193
|
continue;
|
|
@@ -157,11 +196,7 @@ function parseArgs(argv) {
|
|
|
157
196
|
break;
|
|
158
197
|
}
|
|
159
198
|
if (!out.scriptName) {
|
|
160
|
-
fail("script requires a name", {
|
|
161
|
-
exitCode: 2,
|
|
162
|
-
json: out.json,
|
|
163
|
-
help: ["ai-md script <name> [--] [args...]"],
|
|
164
|
-
});
|
|
199
|
+
fail("script requires a name", { exitCode: 2, json: out.json });
|
|
165
200
|
process.exit(2);
|
|
166
201
|
}
|
|
167
202
|
if (args[0] === "--") args.shift();
|
|
@@ -175,7 +210,14 @@ function parseArgs(argv) {
|
|
|
175
210
|
out.scriptArgs = args;
|
|
176
211
|
break;
|
|
177
212
|
}
|
|
178
|
-
if (
|
|
213
|
+
if (
|
|
214
|
+
a === "set" ||
|
|
215
|
+
a === "show" ||
|
|
216
|
+
a === "list" ||
|
|
217
|
+
a === "unset" ||
|
|
218
|
+
a === "enable" ||
|
|
219
|
+
a === "disable"
|
|
220
|
+
) {
|
|
179
221
|
out.rest.push(a);
|
|
180
222
|
continue;
|
|
181
223
|
}
|
|
@@ -196,110 +238,74 @@ function parseArgs(argv) {
|
|
|
196
238
|
}
|
|
197
239
|
|
|
198
240
|
function printHelp() {
|
|
199
|
-
process.stdout.write(`ai-md — private ~/.ai-md:
|
|
241
|
+
process.stdout.write(`ai-md — private ~/.ai-md: shared + agents → dist → live harnesses
|
|
200
242
|
|
|
201
243
|
Usage:
|
|
202
244
|
ai-md [command] [options]
|
|
203
|
-
npx -y @dujavi/ai-md [command] [options]
|
|
204
|
-
|
|
205
|
-
Layout:
|
|
206
|
-
~/.ai-md/skills, rules System (global) base — linked to ~/.cursor
|
|
207
|
-
~/.ai-md/templates/<type> Project-type starters (default: base)
|
|
208
|
-
~/.ai-md/projects/<name> Per-app overlays (repo .cursor → here)
|
|
209
|
-
~/.ai-md/scripts/<name> Private machine scripts (ai-md script / setup --script)
|
|
210
245
|
|
|
211
|
-
|
|
212
|
-
~/.
|
|
213
|
-
|
|
246
|
+
Layout (source):
|
|
247
|
+
~/.ai-md/shared/{rules,skills} Cross-harness
|
|
248
|
+
~/.ai-md/agents/<id>/ Harness overlays
|
|
249
|
+
~/.ai-md/dist/<id>/ Build output (gitignored)
|
|
250
|
+
~/.ai-md/templates, projects, scripts
|
|
214
251
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
claude ~/.claude/skills
|
|
218
|
-
agents ~/.agents/skills
|
|
252
|
+
Unique sync targets: cursor, claude, gemini, opencode, copilot
|
|
253
|
+
Shared ~/.agents/skills: agents (canonical); codex is an alias
|
|
219
254
|
|
|
220
255
|
Commands:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
256
|
+
init Scaffold skeleton into ~/.ai-md (no remote required)
|
|
257
|
+
seed-skeleton Add missing recommended files only
|
|
258
|
+
setup Persist config + install (+ optional --init / --script)
|
|
259
|
+
config Show config; config set --remote/--dir/--link-mode
|
|
260
|
+
status Snapshot (default)
|
|
261
|
+
doctor Diagnose; --fix rebuilds + relinks (installed AIs only)
|
|
262
|
+
build Merge shared+agents → dist/
|
|
263
|
+
rescue Promote dirty dist → agents/<id>/
|
|
264
|
+
install | pull Git sync + build + link (skip harnesses not installed)
|
|
265
|
+
push Commit + push private repo
|
|
266
|
+
harness list | show | set | unset | enable | disable
|
|
267
|
+
script Run ~/.ai-md/scripts/<name>
|
|
268
|
+
init-project Seed projects/<name> + link .cursor/
|
|
269
|
+
apply-template | link-project | link
|
|
270
|
+
help
|
|
232
271
|
|
|
233
272
|
Options:
|
|
234
|
-
--
|
|
235
|
-
--
|
|
236
|
-
--
|
|
237
|
-
--
|
|
238
|
-
--
|
|
239
|
-
--
|
|
240
|
-
--
|
|
241
|
-
--name <id> Project id under projects/
|
|
242
|
-
--project <id> Target project for apply-template
|
|
243
|
-
--from <id> Template under templates/ (default: base)
|
|
244
|
-
--force Replace non-symlink paths
|
|
245
|
-
--dry-run Preview without writing (before script name / before --)
|
|
246
|
-
--fix doctor: repair symlinks
|
|
247
|
-
-m, --message push commit message
|
|
248
|
-
-- End of ai-md options; remaining args go to private scripts
|
|
273
|
+
--agents <list> Harnesses (default: config agents or cursor)
|
|
274
|
+
--force-link Link even if AI does not look installed
|
|
275
|
+
--force Replace real dirs / discard dirty dist on build
|
|
276
|
+
--link-mode <m> symlink | junction | copy
|
|
277
|
+
--dry-run --json --full --verbose
|
|
278
|
+
--remote --dir --skills --rules --format
|
|
279
|
+
--script <name> (repeatable on setup/install)
|
|
249
280
|
|
|
250
281
|
Examples:
|
|
251
|
-
ai-md
|
|
252
|
-
ai-md setup --remote
|
|
253
|
-
ai-md
|
|
254
|
-
ai-md
|
|
255
|
-
ai-md
|
|
256
|
-
ai-md install --remote https://github.com/<you>/.ai-md.git
|
|
257
|
-
ai-md doctor --fix --agents cursor,claude
|
|
258
|
-
ai-md init-project --repo ~/presenter --from base
|
|
282
|
+
ai-md init
|
|
283
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git
|
|
284
|
+
ai-md build && ai-md doctor --fix
|
|
285
|
+
ai-md harness set my-tool --skills ~/.my-tool/skills --format md
|
|
286
|
+
ai-md rescue --agents cursor
|
|
259
287
|
`);
|
|
260
288
|
}
|
|
261
289
|
|
|
262
|
-
function
|
|
263
|
-
|
|
264
|
-
stdio: "inherit",
|
|
265
|
-
env,
|
|
266
|
-
});
|
|
267
|
-
if (result.error) {
|
|
268
|
-
fail(result.error.message, { exitCode: 1 });
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
290
|
+
function agentsOrDefault(opts, cfg) {
|
|
291
|
+
return opts.agents || cfg.agents || ["cursor"];
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
function persistIfRequested(opts) {
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
function runInstall(cfg, opts) {
|
|
284
|
-
const bashArgs = ["install"];
|
|
285
|
-
if (opts.force) bashArgs.push("--force");
|
|
286
|
-
if (opts.dryRun) bashArgs.push("--dry-run");
|
|
287
|
-
const result = spawnSync(
|
|
288
|
-
"bash",
|
|
289
|
-
[path.join(scriptsDir, "sync-config.sh"), ...bashArgs],
|
|
290
|
-
{ stdio: "inherit", env: process.env }
|
|
291
|
-
);
|
|
292
|
-
if (result.status !== 0) {
|
|
293
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
295
|
+
if (
|
|
296
|
+
opts.remote == null &&
|
|
297
|
+
opts.dir == null &&
|
|
298
|
+
opts.linkMode == null &&
|
|
299
|
+
opts.agents == null
|
|
300
|
+
) {
|
|
301
|
+
return null;
|
|
294
302
|
}
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
];
|
|
302
|
-
return links;
|
|
303
|
+
const patch = {};
|
|
304
|
+
if (opts.dir != null) patch.dir = opts.dir;
|
|
305
|
+
if (opts.remote != null) patch.remote = opts.remote;
|
|
306
|
+
if (opts.linkMode != null) patch.linkMode = opts.linkMode;
|
|
307
|
+
if (opts.agents != null) patch.agents = opts.agents;
|
|
308
|
+
return writeMachineConfig(patch, process.env, { dryRun: opts.dryRun });
|
|
303
309
|
}
|
|
304
310
|
|
|
305
311
|
function main() {
|
|
@@ -313,6 +319,7 @@ function main() {
|
|
|
313
319
|
let cfg = resolveConfig(process.env, {
|
|
314
320
|
dir: opts.dir || undefined,
|
|
315
321
|
remote: opts.remote || undefined,
|
|
322
|
+
linkMode: opts.linkMode || undefined,
|
|
316
323
|
});
|
|
317
324
|
applyEnvFromConfig(cfg);
|
|
318
325
|
|
|
@@ -321,21 +328,19 @@ function main() {
|
|
|
321
328
|
case "config": {
|
|
322
329
|
const sub = opts.rest[0] || "show";
|
|
323
330
|
if (sub === "set") {
|
|
324
|
-
if (
|
|
325
|
-
|
|
331
|
+
if (
|
|
332
|
+
opts.remote == null &&
|
|
333
|
+
opts.dir == null &&
|
|
334
|
+
opts.linkMode == null &&
|
|
335
|
+
opts.agents == null
|
|
336
|
+
) {
|
|
337
|
+
fail("config set requires --remote/--dir/--link-mode/--agents", {
|
|
326
338
|
exitCode: 2,
|
|
327
339
|
json: opts.json,
|
|
328
|
-
help: [
|
|
329
|
-
"ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md",
|
|
330
|
-
],
|
|
331
340
|
});
|
|
332
341
|
process.exit(2);
|
|
333
342
|
}
|
|
334
|
-
const saved =
|
|
335
|
-
{ dir: opts.dir, remote: opts.remote },
|
|
336
|
-
process.env,
|
|
337
|
-
{ dryRun: opts.dryRun }
|
|
338
|
-
);
|
|
343
|
+
const saved = persistIfRequested(opts);
|
|
339
344
|
cfg = resolveConfig(process.env);
|
|
340
345
|
emit({
|
|
341
346
|
data: {
|
|
@@ -343,14 +348,13 @@ function main() {
|
|
|
343
348
|
resolved: {
|
|
344
349
|
dir: cfg.dir,
|
|
345
350
|
remote: cfg.remote,
|
|
351
|
+
linkMode: cfg.linkMode,
|
|
352
|
+
agents: cfg.agents,
|
|
346
353
|
sources: cfg.sources,
|
|
347
354
|
},
|
|
348
355
|
},
|
|
349
356
|
json: opts.json,
|
|
350
|
-
help: [
|
|
351
|
-
"Run `ai-md install` if ~/.ai-md is not cloned yet",
|
|
352
|
-
"Run `ai-md setup --remote <url> --script ensure-tools` for first-time bootstrap",
|
|
353
|
-
],
|
|
357
|
+
help: ["Run `ai-md install` or `ai-md init`"],
|
|
354
358
|
});
|
|
355
359
|
break;
|
|
356
360
|
}
|
|
@@ -363,22 +367,188 @@ function main() {
|
|
|
363
367
|
resolved: {
|
|
364
368
|
dir: cfg.dir,
|
|
365
369
|
remote: cfg.remote,
|
|
370
|
+
linkMode: cfg.linkMode,
|
|
371
|
+
agents: cfg.agents,
|
|
372
|
+
defaultLinkMode: defaultLinkMode(),
|
|
366
373
|
sources: cfg.sources,
|
|
367
374
|
},
|
|
368
375
|
},
|
|
369
376
|
json: opts.json,
|
|
377
|
+
help: ["ai-md config set --remote <url> --dir ~/.ai-md"],
|
|
378
|
+
});
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case "init": {
|
|
382
|
+
const data = initRepo(cfg, {
|
|
383
|
+
noGit: opts.noGit,
|
|
384
|
+
force: opts.force,
|
|
385
|
+
dryRun: opts.dryRun,
|
|
386
|
+
});
|
|
387
|
+
writeMachineConfig(
|
|
388
|
+
{ dir: cfg.dir, agents: ["cursor"], linkMode: cfg.linkMode },
|
|
389
|
+
process.env,
|
|
390
|
+
{ dryRun: opts.dryRun }
|
|
391
|
+
);
|
|
392
|
+
cfg = resolveConfig(process.env);
|
|
393
|
+
let bl = null;
|
|
394
|
+
if (!opts.dryRun) {
|
|
395
|
+
bl = buildAndLink(cfg, {
|
|
396
|
+
agents: ["cursor"],
|
|
397
|
+
force: true,
|
|
398
|
+
forceLink: opts.forceLink,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
emit({
|
|
402
|
+
data: { init: data, buildLink: bl },
|
|
403
|
+
json: opts.json,
|
|
370
404
|
help: [
|
|
371
|
-
"
|
|
372
|
-
"
|
|
405
|
+
"Edit shared/ or agents/<id>/",
|
|
406
|
+
"Later: ai-md config set --remote <url> && git -C ~/.ai-md push -u origin main",
|
|
373
407
|
],
|
|
374
408
|
});
|
|
375
409
|
break;
|
|
376
410
|
}
|
|
411
|
+
case "seed-skeleton": {
|
|
412
|
+
const data = seedSkeleton(cfg, {
|
|
413
|
+
force: opts.force,
|
|
414
|
+
dryRun: opts.dryRun,
|
|
415
|
+
});
|
|
416
|
+
emit({
|
|
417
|
+
data,
|
|
418
|
+
json: opts.json,
|
|
419
|
+
help: ["Run `ai-md build` after seeding"],
|
|
420
|
+
});
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "build": {
|
|
424
|
+
const data = runBuild(cfg, {
|
|
425
|
+
agents: agentsOrDefault(opts, cfg),
|
|
426
|
+
force: opts.force,
|
|
427
|
+
dryRun: opts.dryRun,
|
|
428
|
+
verbose: opts.verbose,
|
|
429
|
+
forceLink: true,
|
|
430
|
+
includeUninstalled: true,
|
|
431
|
+
});
|
|
432
|
+
emit({
|
|
433
|
+
data,
|
|
434
|
+
json: opts.json,
|
|
435
|
+
help: ["Run `ai-md doctor --fix` to link installed harnesses"],
|
|
436
|
+
});
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case "rescue": {
|
|
440
|
+
const data = runRescue(cfg, {
|
|
441
|
+
agents: agentsOrDefault(opts, cfg),
|
|
442
|
+
dryRun: opts.dryRun,
|
|
443
|
+
});
|
|
444
|
+
emit({
|
|
445
|
+
data,
|
|
446
|
+
json: opts.json,
|
|
447
|
+
help: ["Review agents/<id>/ then `ai-md build`"],
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "harness": {
|
|
452
|
+
const sub = opts.rest[0] || "list";
|
|
453
|
+
const id = opts.rest[1];
|
|
454
|
+
if (sub === "list") {
|
|
455
|
+
emit({
|
|
456
|
+
data: { harnesses: harnessList(cfg) },
|
|
457
|
+
json: opts.json,
|
|
458
|
+
help: ["ai-md harness show cursor"],
|
|
459
|
+
});
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
if (sub === "show") {
|
|
463
|
+
if (!id) {
|
|
464
|
+
fail("harness show requires <id>", { exitCode: 2, json: opts.json });
|
|
465
|
+
process.exit(2);
|
|
466
|
+
}
|
|
467
|
+
emit({
|
|
468
|
+
data: harnessShow(cfg, id),
|
|
469
|
+
json: opts.json,
|
|
470
|
+
help: [],
|
|
471
|
+
});
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
if (sub === "set") {
|
|
475
|
+
if (!id) {
|
|
476
|
+
fail("harness set requires <id>", { exitCode: 2, json: opts.json });
|
|
477
|
+
process.exit(2);
|
|
478
|
+
}
|
|
479
|
+
const data = harnessSet(cfg, id, {
|
|
480
|
+
skills: opts.skills,
|
|
481
|
+
rules: opts.rules,
|
|
482
|
+
format: opts.format,
|
|
483
|
+
});
|
|
484
|
+
emit({
|
|
485
|
+
data,
|
|
486
|
+
json: opts.json,
|
|
487
|
+
help: [`Put overlays in agents/${id}/`, "ai-md build"],
|
|
488
|
+
});
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
if (sub === "unset") {
|
|
492
|
+
if (!id) {
|
|
493
|
+
fail("harness unset requires <id>", { exitCode: 2, json: opts.json });
|
|
494
|
+
process.exit(2);
|
|
495
|
+
}
|
|
496
|
+
emit({
|
|
497
|
+
data: harnessUnset(cfg, id, { pathsOnly: opts.pathsOnly }),
|
|
498
|
+
json: opts.json,
|
|
499
|
+
help: [],
|
|
500
|
+
});
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
if (sub === "enable" || sub === "disable") {
|
|
504
|
+
if (!id) {
|
|
505
|
+
fail(`harness ${sub} requires <id>`, { exitCode: 2, json: opts.json });
|
|
506
|
+
process.exit(2);
|
|
507
|
+
}
|
|
508
|
+
emit({
|
|
509
|
+
data: harnessEnable(cfg, id, sub === "enable"),
|
|
510
|
+
json: opts.json,
|
|
511
|
+
help: ["ai-md build && ai-md doctor --fix"],
|
|
512
|
+
});
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
fail(`unknown harness subcommand: ${sub}`, { exitCode: 2, json: opts.json });
|
|
516
|
+
process.exit(2);
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
377
519
|
case "setup": {
|
|
520
|
+
if (opts.init && !opts.remote) {
|
|
521
|
+
const data = initRepo(cfg, {
|
|
522
|
+
noGit: opts.noGit,
|
|
523
|
+
force: opts.force,
|
|
524
|
+
dryRun: opts.dryRun,
|
|
525
|
+
});
|
|
526
|
+
writeMachineConfig(
|
|
527
|
+
{ dir: cfg.dir, agents: ["cursor"], linkMode: cfg.linkMode },
|
|
528
|
+
process.env,
|
|
529
|
+
{ dryRun: opts.dryRun }
|
|
530
|
+
);
|
|
531
|
+
cfg = resolveConfig(process.env);
|
|
532
|
+
const bl = opts.dryRun
|
|
533
|
+
? null
|
|
534
|
+
: buildAndLink(cfg, {
|
|
535
|
+
agents: agentsOrDefault(opts, cfg),
|
|
536
|
+
force: true,
|
|
537
|
+
forceLink: opts.forceLink,
|
|
538
|
+
});
|
|
539
|
+
emit({
|
|
540
|
+
data: { setup: "init", init: data, buildLink: bl },
|
|
541
|
+
json: opts.json,
|
|
542
|
+
help: [],
|
|
543
|
+
});
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
378
546
|
const saved = writeMachineConfig(
|
|
379
547
|
{
|
|
380
548
|
dir: opts.dir || cfg.dir,
|
|
381
549
|
remote: opts.remote || cfg.remote,
|
|
550
|
+
agents: opts.agents || cfg.agents,
|
|
551
|
+
linkMode: opts.linkMode || cfg.linkMode,
|
|
382
552
|
},
|
|
383
553
|
process.env,
|
|
384
554
|
{ dryRun: opts.dryRun }
|
|
@@ -388,7 +558,13 @@ function main() {
|
|
|
388
558
|
remote: opts.remote || undefined,
|
|
389
559
|
});
|
|
390
560
|
applyEnvFromConfig(cfg);
|
|
391
|
-
const
|
|
561
|
+
const result = opts.dryRun
|
|
562
|
+
? { build: null, link: null }
|
|
563
|
+
: runInstall(cfg, {
|
|
564
|
+
agents: agentsOrDefault(opts, cfg),
|
|
565
|
+
force: opts.force,
|
|
566
|
+
forceLink: opts.forceLink,
|
|
567
|
+
});
|
|
392
568
|
const scripts =
|
|
393
569
|
opts.scripts.length === 0
|
|
394
570
|
? []
|
|
@@ -398,24 +574,13 @@ function main() {
|
|
|
398
574
|
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
399
575
|
const data = collectStatus({
|
|
400
576
|
full: opts.full,
|
|
401
|
-
agents: opts
|
|
577
|
+
agents: agentsOrDefault(opts, cfg),
|
|
402
578
|
from: opts.from,
|
|
403
579
|
});
|
|
404
580
|
emit({
|
|
405
|
-
data: {
|
|
406
|
-
setup: "ok",
|
|
407
|
-
config: saved,
|
|
408
|
-
links,
|
|
409
|
-
scripts,
|
|
410
|
-
...data,
|
|
411
|
-
},
|
|
581
|
+
data: { setup: "ok", config: saved, ...result, scripts, ...data },
|
|
412
582
|
json: opts.json,
|
|
413
|
-
help:
|
|
414
|
-
opts.scripts.length
|
|
415
|
-
? "Run `ai-md status` to verify"
|
|
416
|
-
: "Run `ai-md script <name>` for private machine scripts (e.g. ensure-tools)",
|
|
417
|
-
"Run `ai-md init-project --repo <path> --from base` for a new app",
|
|
418
|
-
],
|
|
583
|
+
help: statusHelp(data),
|
|
419
584
|
});
|
|
420
585
|
if (failed) process.exit(failed.exitCode);
|
|
421
586
|
break;
|
|
@@ -423,7 +588,7 @@ function main() {
|
|
|
423
588
|
case "status": {
|
|
424
589
|
const data = collectStatus({
|
|
425
590
|
full: opts.full,
|
|
426
|
-
agents: opts
|
|
591
|
+
agents: agentsOrDefault(opts, cfg),
|
|
427
592
|
from: opts.from,
|
|
428
593
|
});
|
|
429
594
|
emit({ data, json: opts.json, help: statusHelp(data) });
|
|
@@ -434,14 +599,11 @@ function main() {
|
|
|
434
599
|
const data = runDoctor({
|
|
435
600
|
fix: opts.fix,
|
|
436
601
|
force: opts.force,
|
|
437
|
-
agents: opts
|
|
602
|
+
agents: agentsOrDefault(opts, cfg),
|
|
438
603
|
dryRun: opts.dryRun,
|
|
604
|
+
forceLink: opts.forceLink,
|
|
439
605
|
});
|
|
440
|
-
emit({
|
|
441
|
-
data,
|
|
442
|
-
json: opts.json,
|
|
443
|
-
help: data.help,
|
|
444
|
-
});
|
|
606
|
+
emit({ data, json: opts.json, help: data.help });
|
|
445
607
|
process.exitCode = data.after.problems.length > 0 ? 1 : 0;
|
|
446
608
|
break;
|
|
447
609
|
}
|
|
@@ -456,10 +618,7 @@ function main() {
|
|
|
456
618
|
emit({
|
|
457
619
|
data,
|
|
458
620
|
json: opts.json,
|
|
459
|
-
help: [
|
|
460
|
-
"Customize rules/agentic-workflow.mdc for this project",
|
|
461
|
-
'Run `ai-md push -m "Init <project>"` after reviewing the private repo diff',
|
|
462
|
-
],
|
|
621
|
+
help: ['Run `ai-md push -m "Init <project>"` after review'],
|
|
463
622
|
});
|
|
464
623
|
break;
|
|
465
624
|
}
|
|
@@ -469,14 +628,7 @@ function main() {
|
|
|
469
628
|
from: opts.from,
|
|
470
629
|
dryRun: opts.dryRun,
|
|
471
630
|
});
|
|
472
|
-
emit({
|
|
473
|
-
data,
|
|
474
|
-
json: opts.json,
|
|
475
|
-
help: [
|
|
476
|
-
"Review added files under ~/.ai-md/projects/<name>/",
|
|
477
|
-
'Run `ai-md push -m "Apply template to <project>"` when ready',
|
|
478
|
-
],
|
|
479
|
-
});
|
|
631
|
+
emit({ data, json: opts.json, help: [] });
|
|
480
632
|
break;
|
|
481
633
|
}
|
|
482
634
|
case "link-project":
|
|
@@ -487,13 +639,7 @@ function main() {
|
|
|
487
639
|
force: opts.force,
|
|
488
640
|
dryRun: opts.dryRun,
|
|
489
641
|
});
|
|
490
|
-
emit({
|
|
491
|
-
data,
|
|
492
|
-
json: opts.json,
|
|
493
|
-
help: [
|
|
494
|
-
"Prefer `ai-md init-project --repo <path>` to seed from template first",
|
|
495
|
-
],
|
|
496
|
-
});
|
|
642
|
+
emit({ data, json: opts.json, help: [] });
|
|
497
643
|
break;
|
|
498
644
|
}
|
|
499
645
|
case "install": {
|
|
@@ -505,7 +651,12 @@ function main() {
|
|
|
505
651
|
});
|
|
506
652
|
applyEnvFromConfig(cfg);
|
|
507
653
|
}
|
|
508
|
-
const
|
|
654
|
+
const result = runInstall(cfg, {
|
|
655
|
+
agents: agentsOrDefault(opts, cfg),
|
|
656
|
+
force: opts.force,
|
|
657
|
+
dryRun: opts.dryRun,
|
|
658
|
+
forceLink: opts.forceLink,
|
|
659
|
+
});
|
|
509
660
|
const scripts =
|
|
510
661
|
opts.scripts.length === 0
|
|
511
662
|
? []
|
|
@@ -513,30 +664,38 @@ function main() {
|
|
|
513
664
|
dryRun: opts.dryRun,
|
|
514
665
|
});
|
|
515
666
|
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
516
|
-
const data = collectStatus({
|
|
667
|
+
const data = collectStatus({
|
|
668
|
+
full: opts.full,
|
|
669
|
+
agents: agentsOrDefault(opts, cfg),
|
|
670
|
+
});
|
|
517
671
|
emit({
|
|
518
|
-
data: { install: "ok", config: saved,
|
|
672
|
+
data: { install: "ok", config: saved, ...result, scripts, ...data },
|
|
519
673
|
json: opts.json,
|
|
520
|
-
help:
|
|
521
|
-
"Run `ai-md script <name>` for private machine scripts",
|
|
522
|
-
"Run `ai-md init-project --repo <path>` for a new app",
|
|
523
|
-
],
|
|
674
|
+
help: statusHelp(data),
|
|
524
675
|
});
|
|
525
676
|
if (failed) process.exit(failed.exitCode);
|
|
526
677
|
break;
|
|
527
678
|
}
|
|
528
|
-
case "pull":
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
679
|
+
case "pull": {
|
|
680
|
+
const result = runPull(cfg, {
|
|
681
|
+
agents: agentsOrDefault(opts, cfg),
|
|
682
|
+
force: opts.force,
|
|
683
|
+
dryRun: opts.dryRun,
|
|
684
|
+
forceLink: opts.forceLink,
|
|
685
|
+
});
|
|
686
|
+
emit({
|
|
687
|
+
data: { pull: "ok", ...result },
|
|
688
|
+
json: opts.json,
|
|
689
|
+
help: ["Edit shared/ or agents/<id>/; ai-md push -m \"…\""],
|
|
690
|
+
});
|
|
534
691
|
break;
|
|
692
|
+
}
|
|
535
693
|
case "push": {
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
694
|
+
const data = runPush(cfg, {
|
|
695
|
+
message: opts.message,
|
|
696
|
+
dryRun: opts.dryRun,
|
|
697
|
+
});
|
|
698
|
+
emit({ data, json: opts.json, help: [] });
|
|
540
699
|
break;
|
|
541
700
|
}
|
|
542
701
|
case "script":
|
|
@@ -547,11 +706,7 @@ function main() {
|
|
|
547
706
|
emit({
|
|
548
707
|
data: { scripts: [result] },
|
|
549
708
|
json: opts.json,
|
|
550
|
-
help: [
|
|
551
|
-
"Scripts live in ~/.ai-md/scripts/ (private content repo)",
|
|
552
|
-
"ai-md script <name> -- [args...]",
|
|
553
|
-
"ai-md setup --script <name> -- [args...]",
|
|
554
|
-
],
|
|
709
|
+
help: ["Scripts live in ~/.ai-md/scripts/"],
|
|
555
710
|
});
|
|
556
711
|
process.exit(result.exitCode);
|
|
557
712
|
break;
|
|
@@ -566,9 +721,12 @@ function main() {
|
|
|
566
721
|
}
|
|
567
722
|
} catch (err) {
|
|
568
723
|
fail(err.message || String(err), {
|
|
569
|
-
exitCode: err.code === "EINVAL" || err.code === "ENOENT" ? 2 : 1,
|
|
724
|
+
exitCode: err.code === "EINVAL" || err.code === "ENOENT" || err.code === "EEXIST" || err.code === "EDIRTY" ? 2 : 1,
|
|
570
725
|
json: opts.json,
|
|
571
|
-
help:
|
|
726
|
+
help:
|
|
727
|
+
err.code === "EDIRTY"
|
|
728
|
+
? ["ai-md rescue --agents <id>", "ai-md build --force"]
|
|
729
|
+
: ["Run `ai-md --help`"],
|
|
572
730
|
});
|
|
573
731
|
process.exit(process.exitCode || 1);
|
|
574
732
|
}
|