@dujavi/ai-md 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -106
- package/bin/ai-md.js +358 -191
- package/lib/build.js +268 -0
- package/lib/commands.js +421 -99
- package/lib/config-paths.js +73 -0
- package/lib/config.js +124 -67
- package/lib/harnesses/index.js +309 -0
- package/lib/link.js +224 -0
- package/lib/remote.js +116 -0
- package/lib/rescue.js +95 -0
- package/lib/skeleton.js +117 -0
- package/lib/status.js +114 -36
- package/package.json +4 -3
- package/scripts/sync-config.sh +10 -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,23 @@ 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
|
+
bootstrapContent,
|
|
25
|
+
seedSkeleton,
|
|
26
|
+
initRepo,
|
|
27
|
+
harnessList,
|
|
28
|
+
harnessShow,
|
|
29
|
+
harnessSet,
|
|
30
|
+
harnessUnset,
|
|
31
|
+
harnessEnable,
|
|
22
32
|
} = require("../lib/commands");
|
|
23
33
|
const { runScript, runScripts } = require("../lib/scripts");
|
|
24
|
-
|
|
25
|
-
const scriptsDir = path.join(__dirname, "..", "scripts");
|
|
34
|
+
const { defaultLinkMode } = require("../lib/config-paths");
|
|
26
35
|
|
|
27
36
|
function applyFlag(out, flag, args) {
|
|
28
37
|
switch (flag) {
|
|
@@ -42,6 +51,21 @@ function applyFlag(out, flag, args) {
|
|
|
42
51
|
out.fix = true;
|
|
43
52
|
out.force = true;
|
|
44
53
|
return true;
|
|
54
|
+
case "--force-link":
|
|
55
|
+
out.forceLink = true;
|
|
56
|
+
return true;
|
|
57
|
+
case "--init":
|
|
58
|
+
out.init = true;
|
|
59
|
+
return true;
|
|
60
|
+
case "--no-git":
|
|
61
|
+
out.noGit = true;
|
|
62
|
+
return true;
|
|
63
|
+
case "--verbose":
|
|
64
|
+
out.verbose = true;
|
|
65
|
+
return true;
|
|
66
|
+
case "--paths-only":
|
|
67
|
+
out.pathsOnly = true;
|
|
68
|
+
return true;
|
|
45
69
|
case "-m":
|
|
46
70
|
case "--message":
|
|
47
71
|
out.message = args.shift();
|
|
@@ -64,6 +88,18 @@ function applyFlag(out, flag, args) {
|
|
|
64
88
|
case "--dir":
|
|
65
89
|
out.dir = expandHome(args.shift());
|
|
66
90
|
return true;
|
|
91
|
+
case "--skills":
|
|
92
|
+
out.skills = args.shift();
|
|
93
|
+
return true;
|
|
94
|
+
case "--rules":
|
|
95
|
+
out.rules = args.shift();
|
|
96
|
+
return true;
|
|
97
|
+
case "--format":
|
|
98
|
+
out.format = args.shift();
|
|
99
|
+
return true;
|
|
100
|
+
case "--link-mode":
|
|
101
|
+
out.linkMode = args.shift();
|
|
102
|
+
return true;
|
|
67
103
|
case "--agents":
|
|
68
104
|
out.agents = String(args.shift() || "cursor")
|
|
69
105
|
.split(",")
|
|
@@ -100,6 +136,11 @@ function parseArgs(argv) {
|
|
|
100
136
|
force: false,
|
|
101
137
|
dryRun: false,
|
|
102
138
|
fix: false,
|
|
139
|
+
forceLink: false,
|
|
140
|
+
init: false,
|
|
141
|
+
noGit: false,
|
|
142
|
+
verbose: false,
|
|
143
|
+
pathsOnly: false,
|
|
103
144
|
message: null,
|
|
104
145
|
repo: null,
|
|
105
146
|
name: null,
|
|
@@ -107,7 +148,11 @@ function parseArgs(argv) {
|
|
|
107
148
|
from: "base",
|
|
108
149
|
remote: null,
|
|
109
150
|
dir: null,
|
|
110
|
-
|
|
151
|
+
skills: null,
|
|
152
|
+
rules: null,
|
|
153
|
+
format: null,
|
|
154
|
+
linkMode: null,
|
|
155
|
+
agents: null,
|
|
111
156
|
scripts: [],
|
|
112
157
|
scriptName: null,
|
|
113
158
|
scriptArgs: [],
|
|
@@ -129,7 +174,6 @@ function parseArgs(argv) {
|
|
|
129
174
|
out.cmd = args.shift();
|
|
130
175
|
}
|
|
131
176
|
|
|
132
|
-
// `script <name> [--] [args...]` — ai-md flags only before the name
|
|
133
177
|
if (out.cmd === "script" || out.cmd === "run-script") {
|
|
134
178
|
while (args.length) {
|
|
135
179
|
const a = args[0];
|
|
@@ -144,11 +188,7 @@ function parseArgs(argv) {
|
|
|
144
188
|
if (a.startsWith("-")) {
|
|
145
189
|
args.shift();
|
|
146
190
|
if (!applyFlag(out, a, args)) {
|
|
147
|
-
fail(`unknown flag: ${a}`, {
|
|
148
|
-
exitCode: 2,
|
|
149
|
-
json: out.json,
|
|
150
|
-
help: ["Run `ai-md --help`"],
|
|
151
|
-
});
|
|
191
|
+
fail(`unknown flag: ${a}`, { exitCode: 2, json: out.json });
|
|
152
192
|
process.exit(2);
|
|
153
193
|
}
|
|
154
194
|
continue;
|
|
@@ -157,11 +197,7 @@ function parseArgs(argv) {
|
|
|
157
197
|
break;
|
|
158
198
|
}
|
|
159
199
|
if (!out.scriptName) {
|
|
160
|
-
fail("script requires a name", {
|
|
161
|
-
exitCode: 2,
|
|
162
|
-
json: out.json,
|
|
163
|
-
help: ["ai-md script <name> [--] [args...]"],
|
|
164
|
-
});
|
|
200
|
+
fail("script requires a name", { exitCode: 2, json: out.json });
|
|
165
201
|
process.exit(2);
|
|
166
202
|
}
|
|
167
203
|
if (args[0] === "--") args.shift();
|
|
@@ -175,7 +211,14 @@ function parseArgs(argv) {
|
|
|
175
211
|
out.scriptArgs = args;
|
|
176
212
|
break;
|
|
177
213
|
}
|
|
178
|
-
if (
|
|
214
|
+
if (
|
|
215
|
+
a === "set" ||
|
|
216
|
+
a === "show" ||
|
|
217
|
+
a === "list" ||
|
|
218
|
+
a === "unset" ||
|
|
219
|
+
a === "enable" ||
|
|
220
|
+
a === "disable"
|
|
221
|
+
) {
|
|
179
222
|
out.rest.push(a);
|
|
180
223
|
continue;
|
|
181
224
|
}
|
|
@@ -196,110 +239,74 @@ function parseArgs(argv) {
|
|
|
196
239
|
}
|
|
197
240
|
|
|
198
241
|
function printHelp() {
|
|
199
|
-
process.stdout.write(`ai-md — private ~/.ai-md:
|
|
242
|
+
process.stdout.write(`ai-md — private ~/.ai-md: shared + agents → dist → live harnesses
|
|
200
243
|
|
|
201
244
|
Usage:
|
|
202
245
|
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
246
|
|
|
211
|
-
|
|
212
|
-
~/.
|
|
213
|
-
|
|
247
|
+
Layout (source):
|
|
248
|
+
~/.ai-md/shared/{rules,skills} Cross-harness
|
|
249
|
+
~/.ai-md/agents/<id>/ Harness overlays
|
|
250
|
+
~/.ai-md/dist/<id>/ Build output (gitignored)
|
|
251
|
+
~/.ai-md/templates, projects, scripts
|
|
214
252
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
claude ~/.claude/skills
|
|
218
|
-
agents ~/.agents/skills
|
|
253
|
+
Unique sync targets: cursor, claude, gemini, opencode, copilot
|
|
254
|
+
Shared ~/.agents/skills: agents (canonical); codex is an alias
|
|
219
255
|
|
|
220
256
|
Commands:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
257
|
+
init Bootstrap ~/.ai-md (clone if remote known, else skeleton)
|
|
258
|
+
seed-skeleton Add missing recommended files only
|
|
259
|
+
setup Clone/sync if remote known; --init for skeleton; --script
|
|
260
|
+
config Show config; config set --remote/--dir/--link-mode
|
|
261
|
+
status Snapshot (default)
|
|
262
|
+
doctor Diagnose; --fix rebuilds + relinks (installed AIs only)
|
|
263
|
+
build Merge shared+agents → dist/
|
|
264
|
+
rescue Promote dirty dist → agents/<id>/
|
|
265
|
+
install | pull Git sync + build + link (skip harnesses not installed)
|
|
266
|
+
push Commit + push private repo
|
|
267
|
+
harness list | show | set | unset | enable | disable
|
|
268
|
+
script Run ~/.ai-md/scripts/<name>
|
|
269
|
+
init-project Seed projects/<name> + link .cursor/
|
|
270
|
+
apply-template | link-project | link
|
|
271
|
+
help
|
|
232
272
|
|
|
233
273
|
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
|
|
274
|
+
--agents <list> Harnesses (default: config agents or cursor)
|
|
275
|
+
--force-link Link even if AI does not look installed
|
|
276
|
+
--force Replace real dirs / discard dirty dist on build
|
|
277
|
+
--link-mode <m> symlink | junction | copy
|
|
278
|
+
--dry-run --json --full --verbose
|
|
279
|
+
--remote --dir --skills --rules --format
|
|
280
|
+
--script <name> (repeatable on setup/install)
|
|
249
281
|
|
|
250
282
|
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
|
|
283
|
+
ai-md init
|
|
284
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git
|
|
285
|
+
ai-md build && ai-md doctor --fix
|
|
286
|
+
ai-md harness set my-tool --skills ~/.my-tool/skills --format md
|
|
287
|
+
ai-md rescue --agents cursor
|
|
259
288
|
`);
|
|
260
289
|
}
|
|
261
290
|
|
|
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);
|
|
291
|
+
function agentsOrDefault(opts, cfg) {
|
|
292
|
+
return opts.agents || cfg.agents || ["cursor"];
|
|
272
293
|
}
|
|
273
294
|
|
|
274
295
|
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);
|
|
296
|
+
if (
|
|
297
|
+
opts.remote == null &&
|
|
298
|
+
opts.dir == null &&
|
|
299
|
+
opts.linkMode == null &&
|
|
300
|
+
opts.agents == null
|
|
301
|
+
) {
|
|
302
|
+
return null;
|
|
294
303
|
}
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
];
|
|
302
|
-
return links;
|
|
304
|
+
const patch = {};
|
|
305
|
+
if (opts.dir != null) patch.dir = opts.dir;
|
|
306
|
+
if (opts.remote != null) patch.remote = opts.remote;
|
|
307
|
+
if (opts.linkMode != null) patch.linkMode = opts.linkMode;
|
|
308
|
+
if (opts.agents != null) patch.agents = opts.agents;
|
|
309
|
+
return writeMachineConfig(patch, process.env, { dryRun: opts.dryRun });
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
function main() {
|
|
@@ -313,6 +320,7 @@ function main() {
|
|
|
313
320
|
let cfg = resolveConfig(process.env, {
|
|
314
321
|
dir: opts.dir || undefined,
|
|
315
322
|
remote: opts.remote || undefined,
|
|
323
|
+
linkMode: opts.linkMode || undefined,
|
|
316
324
|
});
|
|
317
325
|
applyEnvFromConfig(cfg);
|
|
318
326
|
|
|
@@ -321,21 +329,19 @@ function main() {
|
|
|
321
329
|
case "config": {
|
|
322
330
|
const sub = opts.rest[0] || "show";
|
|
323
331
|
if (sub === "set") {
|
|
324
|
-
if (
|
|
325
|
-
|
|
332
|
+
if (
|
|
333
|
+
opts.remote == null &&
|
|
334
|
+
opts.dir == null &&
|
|
335
|
+
opts.linkMode == null &&
|
|
336
|
+
opts.agents == null
|
|
337
|
+
) {
|
|
338
|
+
fail("config set requires --remote/--dir/--link-mode/--agents", {
|
|
326
339
|
exitCode: 2,
|
|
327
340
|
json: opts.json,
|
|
328
|
-
help: [
|
|
329
|
-
"ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md",
|
|
330
|
-
],
|
|
331
341
|
});
|
|
332
342
|
process.exit(2);
|
|
333
343
|
}
|
|
334
|
-
const saved =
|
|
335
|
-
{ dir: opts.dir, remote: opts.remote },
|
|
336
|
-
process.env,
|
|
337
|
-
{ dryRun: opts.dryRun }
|
|
338
|
-
);
|
|
344
|
+
const saved = persistIfRequested(opts);
|
|
339
345
|
cfg = resolveConfig(process.env);
|
|
340
346
|
emit({
|
|
341
347
|
data: {
|
|
@@ -343,14 +349,13 @@ function main() {
|
|
|
343
349
|
resolved: {
|
|
344
350
|
dir: cfg.dir,
|
|
345
351
|
remote: cfg.remote,
|
|
352
|
+
linkMode: cfg.linkMode,
|
|
353
|
+
agents: cfg.agents,
|
|
346
354
|
sources: cfg.sources,
|
|
347
355
|
},
|
|
348
356
|
},
|
|
349
357
|
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
|
-
],
|
|
358
|
+
help: ["Run `ai-md install` or `ai-md init`"],
|
|
354
359
|
});
|
|
355
360
|
break;
|
|
356
361
|
}
|
|
@@ -363,32 +368,212 @@ function main() {
|
|
|
363
368
|
resolved: {
|
|
364
369
|
dir: cfg.dir,
|
|
365
370
|
remote: cfg.remote,
|
|
371
|
+
linkMode: cfg.linkMode,
|
|
372
|
+
agents: cfg.agents,
|
|
373
|
+
defaultLinkMode: defaultLinkMode(),
|
|
366
374
|
sources: cfg.sources,
|
|
375
|
+
remoteDetection: cfg.remoteDetection,
|
|
367
376
|
},
|
|
368
377
|
},
|
|
369
378
|
json: opts.json,
|
|
370
|
-
help: [
|
|
371
|
-
"ai-md config set --remote <url> --dir ~/.ai-md",
|
|
372
|
-
"Flags and env override the config file",
|
|
373
|
-
],
|
|
379
|
+
help: ["ai-md config set --remote <url> --dir ~/.ai-md"],
|
|
374
380
|
});
|
|
375
381
|
break;
|
|
376
382
|
}
|
|
383
|
+
case "init": {
|
|
384
|
+
const data = bootstrapContent(cfg, {
|
|
385
|
+
noGit: opts.noGit,
|
|
386
|
+
force: opts.force,
|
|
387
|
+
dryRun: opts.dryRun,
|
|
388
|
+
forceLink: opts.forceLink,
|
|
389
|
+
agents: agentsOrDefault(opts, cfg),
|
|
390
|
+
});
|
|
391
|
+
const help =
|
|
392
|
+
data.action === "cloned" ||
|
|
393
|
+
data.action === "synced" ||
|
|
394
|
+
data.action === "would_clone" ||
|
|
395
|
+
data.action === "would_replace_and_clone"
|
|
396
|
+
? [
|
|
397
|
+
`Synced from ${data.remote || cfg.remote}`,
|
|
398
|
+
"Edit shared/ or agents/<id>/; then ai-md build && ai-md push -m \"…\"",
|
|
399
|
+
]
|
|
400
|
+
: [
|
|
401
|
+
"Skeleton created (no remote).",
|
|
402
|
+
"Connect later: ai-md setup --remote https://github.com/<you>/.ai-md.git",
|
|
403
|
+
];
|
|
404
|
+
emit({
|
|
405
|
+
data: { init: data },
|
|
406
|
+
json: opts.json,
|
|
407
|
+
help,
|
|
408
|
+
});
|
|
409
|
+
break;
|
|
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
|
+
const remoteCfg = resolveConfig(process.env, {
|
|
521
|
+
dir: opts.dir || undefined,
|
|
522
|
+
remote: opts.remote || undefined,
|
|
523
|
+
});
|
|
524
|
+
const remote = opts.remote || remoteCfg.remote || null;
|
|
525
|
+
|
|
526
|
+
// Explicit skeleton path only when no remote is known
|
|
527
|
+
if (opts.init && !remote) {
|
|
528
|
+
const data = bootstrapContent(remoteCfg, {
|
|
529
|
+
noGit: opts.noGit,
|
|
530
|
+
force: opts.force,
|
|
531
|
+
dryRun: opts.dryRun,
|
|
532
|
+
forceLink: opts.forceLink,
|
|
533
|
+
agents: agentsOrDefault(opts, remoteCfg),
|
|
534
|
+
});
|
|
535
|
+
emit({
|
|
536
|
+
data: { setup: "init", ...data },
|
|
537
|
+
json: opts.json,
|
|
538
|
+
help: [],
|
|
539
|
+
});
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (!remote) {
|
|
544
|
+
fail(
|
|
545
|
+
"setup needs a remote (or --init for local skeleton).\n" +
|
|
546
|
+
" ai-md setup --remote https://github.com/<you>/.ai-md.git\n" +
|
|
547
|
+
" ai-md init\n" +
|
|
548
|
+
" (auto-detect: authenticate `gh` and create github.com/<user>/.ai-md)",
|
|
549
|
+
{ exitCode: 2, json: opts.json }
|
|
550
|
+
);
|
|
551
|
+
process.exit(2);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Remote known → clone/sync first; never seed skeleton before sync
|
|
378
555
|
const saved = writeMachineConfig(
|
|
379
556
|
{
|
|
380
|
-
dir: opts.dir ||
|
|
381
|
-
remote
|
|
557
|
+
dir: opts.dir || remoteCfg.dir,
|
|
558
|
+
remote,
|
|
559
|
+
agents: opts.agents || remoteCfg.agents,
|
|
560
|
+
linkMode: opts.linkMode || remoteCfg.linkMode,
|
|
382
561
|
},
|
|
383
562
|
process.env,
|
|
384
563
|
{ dryRun: opts.dryRun }
|
|
385
564
|
);
|
|
386
565
|
cfg = resolveConfig(process.env, {
|
|
387
566
|
dir: opts.dir || undefined,
|
|
388
|
-
remote
|
|
567
|
+
remote,
|
|
568
|
+
skipRemoteDetect: true,
|
|
389
569
|
});
|
|
390
570
|
applyEnvFromConfig(cfg);
|
|
391
|
-
const
|
|
571
|
+
const result = bootstrapContent(cfg, {
|
|
572
|
+
force: opts.force,
|
|
573
|
+
dryRun: opts.dryRun,
|
|
574
|
+
forceLink: opts.forceLink,
|
|
575
|
+
agents: agentsOrDefault(opts, cfg),
|
|
576
|
+
});
|
|
392
577
|
const scripts =
|
|
393
578
|
opts.scripts.length === 0
|
|
394
579
|
? []
|
|
@@ -398,24 +583,13 @@ function main() {
|
|
|
398
583
|
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
399
584
|
const data = collectStatus({
|
|
400
585
|
full: opts.full,
|
|
401
|
-
agents: opts
|
|
586
|
+
agents: agentsOrDefault(opts, cfg),
|
|
402
587
|
from: opts.from,
|
|
403
588
|
});
|
|
404
589
|
emit({
|
|
405
|
-
data: {
|
|
406
|
-
setup: "ok",
|
|
407
|
-
config: saved,
|
|
408
|
-
links,
|
|
409
|
-
scripts,
|
|
410
|
-
...data,
|
|
411
|
-
},
|
|
590
|
+
data: { setup: "ok", config: saved, result, scripts, ...data },
|
|
412
591
|
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
|
-
],
|
|
592
|
+
help: statusHelp(data),
|
|
419
593
|
});
|
|
420
594
|
if (failed) process.exit(failed.exitCode);
|
|
421
595
|
break;
|
|
@@ -423,7 +597,7 @@ function main() {
|
|
|
423
597
|
case "status": {
|
|
424
598
|
const data = collectStatus({
|
|
425
599
|
full: opts.full,
|
|
426
|
-
agents: opts
|
|
600
|
+
agents: agentsOrDefault(opts, cfg),
|
|
427
601
|
from: opts.from,
|
|
428
602
|
});
|
|
429
603
|
emit({ data, json: opts.json, help: statusHelp(data) });
|
|
@@ -434,14 +608,11 @@ function main() {
|
|
|
434
608
|
const data = runDoctor({
|
|
435
609
|
fix: opts.fix,
|
|
436
610
|
force: opts.force,
|
|
437
|
-
agents: opts
|
|
611
|
+
agents: agentsOrDefault(opts, cfg),
|
|
438
612
|
dryRun: opts.dryRun,
|
|
613
|
+
forceLink: opts.forceLink,
|
|
439
614
|
});
|
|
440
|
-
emit({
|
|
441
|
-
data,
|
|
442
|
-
json: opts.json,
|
|
443
|
-
help: data.help,
|
|
444
|
-
});
|
|
615
|
+
emit({ data, json: opts.json, help: data.help });
|
|
445
616
|
process.exitCode = data.after.problems.length > 0 ? 1 : 0;
|
|
446
617
|
break;
|
|
447
618
|
}
|
|
@@ -456,10 +627,7 @@ function main() {
|
|
|
456
627
|
emit({
|
|
457
628
|
data,
|
|
458
629
|
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
|
-
],
|
|
630
|
+
help: ['Run `ai-md push -m "Init <project>"` after review'],
|
|
463
631
|
});
|
|
464
632
|
break;
|
|
465
633
|
}
|
|
@@ -469,14 +637,7 @@ function main() {
|
|
|
469
637
|
from: opts.from,
|
|
470
638
|
dryRun: opts.dryRun,
|
|
471
639
|
});
|
|
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
|
-
});
|
|
640
|
+
emit({ data, json: opts.json, help: [] });
|
|
480
641
|
break;
|
|
481
642
|
}
|
|
482
643
|
case "link-project":
|
|
@@ -487,13 +648,7 @@ function main() {
|
|
|
487
648
|
force: opts.force,
|
|
488
649
|
dryRun: opts.dryRun,
|
|
489
650
|
});
|
|
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
|
-
});
|
|
651
|
+
emit({ data, json: opts.json, help: [] });
|
|
497
652
|
break;
|
|
498
653
|
}
|
|
499
654
|
case "install": {
|
|
@@ -505,7 +660,12 @@ function main() {
|
|
|
505
660
|
});
|
|
506
661
|
applyEnvFromConfig(cfg);
|
|
507
662
|
}
|
|
508
|
-
const
|
|
663
|
+
const result = runInstall(cfg, {
|
|
664
|
+
agents: agentsOrDefault(opts, cfg),
|
|
665
|
+
force: opts.force,
|
|
666
|
+
dryRun: opts.dryRun,
|
|
667
|
+
forceLink: opts.forceLink,
|
|
668
|
+
});
|
|
509
669
|
const scripts =
|
|
510
670
|
opts.scripts.length === 0
|
|
511
671
|
? []
|
|
@@ -513,30 +673,38 @@ function main() {
|
|
|
513
673
|
dryRun: opts.dryRun,
|
|
514
674
|
});
|
|
515
675
|
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
516
|
-
const data = collectStatus({
|
|
676
|
+
const data = collectStatus({
|
|
677
|
+
full: opts.full,
|
|
678
|
+
agents: agentsOrDefault(opts, cfg),
|
|
679
|
+
});
|
|
517
680
|
emit({
|
|
518
|
-
data: { install: "ok", config: saved,
|
|
681
|
+
data: { install: "ok", config: saved, ...result, scripts, ...data },
|
|
519
682
|
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
|
-
],
|
|
683
|
+
help: statusHelp(data),
|
|
524
684
|
});
|
|
525
685
|
if (failed) process.exit(failed.exitCode);
|
|
526
686
|
break;
|
|
527
687
|
}
|
|
528
|
-
case "pull":
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
688
|
+
case "pull": {
|
|
689
|
+
const result = runPull(cfg, {
|
|
690
|
+
agents: agentsOrDefault(opts, cfg),
|
|
691
|
+
force: opts.force,
|
|
692
|
+
dryRun: opts.dryRun,
|
|
693
|
+
forceLink: opts.forceLink,
|
|
694
|
+
});
|
|
695
|
+
emit({
|
|
696
|
+
data: { pull: "ok", ...result },
|
|
697
|
+
json: opts.json,
|
|
698
|
+
help: ["Edit shared/ or agents/<id>/; ai-md push -m \"…\""],
|
|
699
|
+
});
|
|
534
700
|
break;
|
|
701
|
+
}
|
|
535
702
|
case "push": {
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
703
|
+
const data = runPush(cfg, {
|
|
704
|
+
message: opts.message,
|
|
705
|
+
dryRun: opts.dryRun,
|
|
706
|
+
});
|
|
707
|
+
emit({ data, json: opts.json, help: [] });
|
|
540
708
|
break;
|
|
541
709
|
}
|
|
542
710
|
case "script":
|
|
@@ -547,11 +715,7 @@ function main() {
|
|
|
547
715
|
emit({
|
|
548
716
|
data: { scripts: [result] },
|
|
549
717
|
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
|
-
],
|
|
718
|
+
help: ["Scripts live in ~/.ai-md/scripts/"],
|
|
555
719
|
});
|
|
556
720
|
process.exit(result.exitCode);
|
|
557
721
|
break;
|
|
@@ -566,9 +730,12 @@ function main() {
|
|
|
566
730
|
}
|
|
567
731
|
} catch (err) {
|
|
568
732
|
fail(err.message || String(err), {
|
|
569
|
-
exitCode: err.code === "EINVAL" || err.code === "ENOENT" ? 2 : 1,
|
|
733
|
+
exitCode: err.code === "EINVAL" || err.code === "ENOENT" || err.code === "EEXIST" || err.code === "EDIRTY" ? 2 : 1,
|
|
570
734
|
json: opts.json,
|
|
571
|
-
help:
|
|
735
|
+
help:
|
|
736
|
+
err.code === "EDIRTY"
|
|
737
|
+
? ["ai-md rescue --agents <id>", "ai-md build --force"]
|
|
738
|
+
: ["Run `ai-md --help`"],
|
|
572
739
|
});
|
|
573
740
|
process.exit(process.exitCode || 1);
|
|
574
741
|
}
|