@dujavi/ai-md 0.4.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 +54 -42
- package/bin/ai-md.js +497 -249
- package/docs/architecture.html +316 -0
- package/docs/architecture.png +0 -0
- 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/scripts.js +104 -0
- package/lib/skeleton.js +117 -0
- package/lib/status.js +110 -35
- package/package.json +11 -7
- package/scripts/sync-config.sh +2 -2
- 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/scripts/ensure-agent-tools.sh +0 -114
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,11 +15,117 @@ 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");
|
|
32
|
+
const { runScript, runScripts } = require("../lib/scripts");
|
|
33
|
+
const { defaultLinkMode } = require("../lib/config-paths");
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
function applyFlag(out, flag, args) {
|
|
36
|
+
switch (flag) {
|
|
37
|
+
case "--json":
|
|
38
|
+
out.json = true;
|
|
39
|
+
return true;
|
|
40
|
+
case "--full":
|
|
41
|
+
out.full = true;
|
|
42
|
+
return true;
|
|
43
|
+
case "--force":
|
|
44
|
+
out.force = true;
|
|
45
|
+
return true;
|
|
46
|
+
case "--dry-run":
|
|
47
|
+
out.dryRun = true;
|
|
48
|
+
return true;
|
|
49
|
+
case "--fix":
|
|
50
|
+
out.fix = true;
|
|
51
|
+
out.force = true;
|
|
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;
|
|
68
|
+
case "-m":
|
|
69
|
+
case "--message":
|
|
70
|
+
out.message = args.shift();
|
|
71
|
+
return true;
|
|
72
|
+
case "--repo":
|
|
73
|
+
out.repo = args.shift();
|
|
74
|
+
return true;
|
|
75
|
+
case "--name":
|
|
76
|
+
out.name = args.shift();
|
|
77
|
+
return true;
|
|
78
|
+
case "--project":
|
|
79
|
+
out.project = args.shift();
|
|
80
|
+
return true;
|
|
81
|
+
case "--from":
|
|
82
|
+
out.from = args.shift();
|
|
83
|
+
return true;
|
|
84
|
+
case "--remote":
|
|
85
|
+
out.remote = args.shift();
|
|
86
|
+
return true;
|
|
87
|
+
case "--dir":
|
|
88
|
+
out.dir = expandHome(args.shift());
|
|
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;
|
|
102
|
+
case "--agents":
|
|
103
|
+
out.agents = String(args.shift() || "cursor")
|
|
104
|
+
.split(",")
|
|
105
|
+
.map((s) => s.trim())
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
return true;
|
|
108
|
+
case "--script": {
|
|
109
|
+
const name = args.shift();
|
|
110
|
+
if (!name) {
|
|
111
|
+
fail("--script requires a name", {
|
|
112
|
+
exitCode: 2,
|
|
113
|
+
json: out.json,
|
|
114
|
+
help: ["ai-md setup --script ensure-tools"],
|
|
115
|
+
});
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
out.scripts.push(name);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
case "-h":
|
|
122
|
+
case "--help":
|
|
123
|
+
out.cmdHelp = true;
|
|
124
|
+
return true;
|
|
125
|
+
default:
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
25
129
|
|
|
26
130
|
function parseArgs(argv) {
|
|
27
131
|
const out = {
|
|
@@ -31,7 +135,11 @@ function parseArgs(argv) {
|
|
|
31
135
|
force: false,
|
|
32
136
|
dryRun: false,
|
|
33
137
|
fix: false,
|
|
34
|
-
|
|
138
|
+
forceLink: false,
|
|
139
|
+
init: false,
|
|
140
|
+
noGit: false,
|
|
141
|
+
verbose: false,
|
|
142
|
+
pathsOnly: false,
|
|
35
143
|
message: null,
|
|
36
144
|
repo: null,
|
|
37
145
|
name: null,
|
|
@@ -39,7 +147,14 @@ function parseArgs(argv) {
|
|
|
39
147
|
from: "base",
|
|
40
148
|
remote: null,
|
|
41
149
|
dir: null,
|
|
42
|
-
|
|
150
|
+
skills: null,
|
|
151
|
+
rules: null,
|
|
152
|
+
format: null,
|
|
153
|
+
linkMode: null,
|
|
154
|
+
agents: null,
|
|
155
|
+
scripts: [],
|
|
156
|
+
scriptName: null,
|
|
157
|
+
scriptArgs: [],
|
|
43
158
|
rest: [],
|
|
44
159
|
};
|
|
45
160
|
const args = [...argv];
|
|
@@ -57,176 +172,140 @@ function parseArgs(argv) {
|
|
|
57
172
|
} else {
|
|
58
173
|
out.cmd = args.shift();
|
|
59
174
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
out.fix = true;
|
|
77
|
-
out.force = true;
|
|
78
|
-
break;
|
|
79
|
-
case "--tools":
|
|
80
|
-
out.tools = true;
|
|
81
|
-
break;
|
|
82
|
-
case "-m":
|
|
83
|
-
case "--message":
|
|
84
|
-
out.message = args.shift();
|
|
85
|
-
break;
|
|
86
|
-
case "--repo":
|
|
87
|
-
out.repo = args.shift();
|
|
88
|
-
break;
|
|
89
|
-
case "--name":
|
|
90
|
-
out.name = args.shift();
|
|
91
|
-
break;
|
|
92
|
-
case "--project":
|
|
93
|
-
out.project = args.shift();
|
|
94
|
-
break;
|
|
95
|
-
case "--from":
|
|
96
|
-
out.from = args.shift();
|
|
97
|
-
break;
|
|
98
|
-
case "--remote":
|
|
99
|
-
out.remote = args.shift();
|
|
100
|
-
break;
|
|
101
|
-
case "--dir":
|
|
102
|
-
out.dir = expandHome(args.shift());
|
|
103
|
-
break;
|
|
104
|
-
case "--agents":
|
|
105
|
-
out.agents = String(args.shift() || "cursor")
|
|
106
|
-
.split(",")
|
|
107
|
-
.map((s) => s.trim())
|
|
108
|
-
.filter(Boolean);
|
|
109
|
-
break;
|
|
110
|
-
case "-h":
|
|
111
|
-
case "--help":
|
|
112
|
-
out.cmdHelp = true;
|
|
113
|
-
break;
|
|
114
|
-
case "set":
|
|
115
|
-
case "show":
|
|
116
|
-
// subcommand for `ai-md config set|show`
|
|
117
|
-
out.rest.push(a);
|
|
118
|
-
break;
|
|
119
|
-
default:
|
|
120
|
-
if (a.startsWith("-")) {
|
|
121
|
-
fail(`unknown flag: ${a}`, {
|
|
122
|
-
exitCode: 2,
|
|
123
|
-
json: out.json,
|
|
124
|
-
help: ["Run `ai-md --help`"],
|
|
125
|
-
});
|
|
175
|
+
|
|
176
|
+
if (out.cmd === "script" || out.cmd === "run-script") {
|
|
177
|
+
while (args.length) {
|
|
178
|
+
const a = args[0];
|
|
179
|
+
if (a === "--") {
|
|
180
|
+
fail("script name required before `--`", {
|
|
181
|
+
exitCode: 2,
|
|
182
|
+
json: out.json,
|
|
183
|
+
help: ["ai-md script ensure-tools -- --dry-run"],
|
|
184
|
+
});
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
if (a.startsWith("-")) {
|
|
188
|
+
args.shift();
|
|
189
|
+
if (!applyFlag(out, a, args)) {
|
|
190
|
+
fail(`unknown flag: ${a}`, { exitCode: 2, json: out.json });
|
|
126
191
|
process.exit(2);
|
|
127
192
|
}
|
|
128
|
-
|
|
129
|
-
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
out.scriptName = args.shift();
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (!out.scriptName) {
|
|
199
|
+
fail("script requires a name", { exitCode: 2, json: out.json });
|
|
200
|
+
process.exit(2);
|
|
201
|
+
}
|
|
202
|
+
if (args[0] === "--") args.shift();
|
|
203
|
+
out.scriptArgs = args;
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
while (args.length) {
|
|
208
|
+
const a = args.shift();
|
|
209
|
+
if (a === "--") {
|
|
210
|
+
out.scriptArgs = args;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
if (
|
|
214
|
+
a === "set" ||
|
|
215
|
+
a === "show" ||
|
|
216
|
+
a === "list" ||
|
|
217
|
+
a === "unset" ||
|
|
218
|
+
a === "enable" ||
|
|
219
|
+
a === "disable"
|
|
220
|
+
) {
|
|
221
|
+
out.rest.push(a);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (a.startsWith("-")) {
|
|
225
|
+
if (!applyFlag(out, a, args)) {
|
|
226
|
+
fail(`unknown flag: ${a}`, {
|
|
227
|
+
exitCode: 2,
|
|
228
|
+
json: out.json,
|
|
229
|
+
help: ["Run `ai-md --help`"],
|
|
230
|
+
});
|
|
231
|
+
process.exit(2);
|
|
232
|
+
}
|
|
233
|
+
continue;
|
|
130
234
|
}
|
|
235
|
+
out.rest.push(a);
|
|
131
236
|
}
|
|
132
237
|
return out;
|
|
133
238
|
}
|
|
134
239
|
|
|
135
240
|
function printHelp() {
|
|
136
|
-
process.stdout.write(`ai-md — private ~/.ai-md:
|
|
241
|
+
process.stdout.write(`ai-md — private ~/.ai-md: shared + agents → dist → live harnesses
|
|
137
242
|
|
|
138
243
|
Usage:
|
|
139
244
|
ai-md [command] [options]
|
|
140
|
-
npx -y @dujavi/ai-md [command] [options]
|
|
141
245
|
|
|
142
|
-
Layout:
|
|
143
|
-
~/.ai-md/skills
|
|
144
|
-
~/.ai-md/
|
|
145
|
-
~/.ai-md/
|
|
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
|
|
146
251
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
Precedence: --flag > env > config file > defaults
|
|
252
|
+
Unique sync targets: cursor, claude, gemini, opencode, copilot
|
|
253
|
+
Shared ~/.agents/skills: agents (canonical); codex is an alias
|
|
150
254
|
|
|
151
255
|
Commands:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
163
271
|
|
|
164
272
|
Options:
|
|
165
|
-
--
|
|
166
|
-
--
|
|
167
|
-
--
|
|
168
|
-
--
|
|
169
|
-
--
|
|
170
|
-
--
|
|
171
|
-
--
|
|
172
|
-
--name <id> Project id under projects/
|
|
173
|
-
--project <id> Target project for apply-template
|
|
174
|
-
--from <id> Template under templates/ (default: base)
|
|
175
|
-
--force Replace non-symlink paths
|
|
176
|
-
--dry-run Preview without writing
|
|
177
|
-
--fix doctor: repair symlinks
|
|
178
|
-
-m, --message push commit message
|
|
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)
|
|
179
280
|
|
|
180
281
|
Examples:
|
|
181
|
-
ai-md
|
|
182
|
-
ai-md
|
|
183
|
-
ai-md
|
|
184
|
-
ai-md
|
|
185
|
-
ai-md
|
|
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
|
|
186
287
|
`);
|
|
187
288
|
}
|
|
188
289
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
stdio: "inherit",
|
|
192
|
-
env,
|
|
193
|
-
});
|
|
194
|
-
if (result.error) {
|
|
195
|
-
fail(result.error.message, { exitCode: 1 });
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
290
|
+
function agentsOrDefault(opts, cfg) {
|
|
291
|
+
return opts.agents || cfg.agents || ["cursor"];
|
|
199
292
|
}
|
|
200
293
|
|
|
201
294
|
function persistIfRequested(opts) {
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
function runInstall(cfg, opts) {
|
|
211
|
-
const bashArgs = ["install"];
|
|
212
|
-
if (opts.force) bashArgs.push("--force");
|
|
213
|
-
if (opts.dryRun) bashArgs.push("--dry-run");
|
|
214
|
-
const result = spawnSync(
|
|
215
|
-
"bash",
|
|
216
|
-
[path.join(scriptsDir, "sync-config.sh"), ...bashArgs],
|
|
217
|
-
{ stdio: "inherit", env: process.env }
|
|
218
|
-
);
|
|
219
|
-
if (result.status !== 0) {
|
|
220
|
-
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;
|
|
221
302
|
}
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
];
|
|
229
|
-
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 });
|
|
230
309
|
}
|
|
231
310
|
|
|
232
311
|
function main() {
|
|
@@ -237,10 +316,10 @@ function main() {
|
|
|
237
316
|
return;
|
|
238
317
|
}
|
|
239
318
|
|
|
240
|
-
// Resolve after flags so --dir/--remote apply
|
|
241
319
|
let cfg = resolveConfig(process.env, {
|
|
242
320
|
dir: opts.dir || undefined,
|
|
243
321
|
remote: opts.remote || undefined,
|
|
322
|
+
linkMode: opts.linkMode || undefined,
|
|
244
323
|
});
|
|
245
324
|
applyEnvFromConfig(cfg);
|
|
246
325
|
|
|
@@ -249,29 +328,33 @@ function main() {
|
|
|
249
328
|
case "config": {
|
|
250
329
|
const sub = opts.rest[0] || "show";
|
|
251
330
|
if (sub === "set") {
|
|
252
|
-
if (
|
|
253
|
-
|
|
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", {
|
|
254
338
|
exitCode: 2,
|
|
255
339
|
json: opts.json,
|
|
256
|
-
help: [
|
|
257
|
-
"ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md",
|
|
258
|
-
],
|
|
259
340
|
});
|
|
260
341
|
process.exit(2);
|
|
261
342
|
}
|
|
262
|
-
const saved =
|
|
263
|
-
{ dir: opts.dir, remote: opts.remote },
|
|
264
|
-
process.env,
|
|
265
|
-
{ dryRun: opts.dryRun }
|
|
266
|
-
);
|
|
343
|
+
const saved = persistIfRequested(opts);
|
|
267
344
|
cfg = resolveConfig(process.env);
|
|
268
345
|
emit({
|
|
269
|
-
data: {
|
|
346
|
+
data: {
|
|
347
|
+
...saved,
|
|
348
|
+
resolved: {
|
|
349
|
+
dir: cfg.dir,
|
|
350
|
+
remote: cfg.remote,
|
|
351
|
+
linkMode: cfg.linkMode,
|
|
352
|
+
agents: cfg.agents,
|
|
353
|
+
sources: cfg.sources,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
270
356
|
json: opts.json,
|
|
271
|
-
help: [
|
|
272
|
-
"Run `ai-md install` if ~/.ai-md is not cloned yet",
|
|
273
|
-
"Run `ai-md setup --remote <url> --tools` for first-time machine bootstrap",
|
|
274
|
-
],
|
|
357
|
+
help: ["Run `ai-md install` or `ai-md init`"],
|
|
275
358
|
});
|
|
276
359
|
break;
|
|
277
360
|
}
|
|
@@ -284,25 +367,188 @@ function main() {
|
|
|
284
367
|
resolved: {
|
|
285
368
|
dir: cfg.dir,
|
|
286
369
|
remote: cfg.remote,
|
|
370
|
+
linkMode: cfg.linkMode,
|
|
371
|
+
agents: cfg.agents,
|
|
372
|
+
defaultLinkMode: defaultLinkMode(),
|
|
287
373
|
sources: cfg.sources,
|
|
288
374
|
},
|
|
289
375
|
},
|
|
290
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,
|
|
291
404
|
help: [
|
|
292
|
-
"
|
|
293
|
-
"
|
|
405
|
+
"Edit shared/ or agents/<id>/",
|
|
406
|
+
"Later: ai-md config set --remote <url> && git -C ~/.ai-md push -u origin main",
|
|
294
407
|
],
|
|
295
408
|
});
|
|
296
409
|
break;
|
|
297
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
|
+
}
|
|
298
519
|
case "setup": {
|
|
299
|
-
if (
|
|
300
|
-
|
|
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;
|
|
301
545
|
}
|
|
302
546
|
const saved = writeMachineConfig(
|
|
303
547
|
{
|
|
304
548
|
dir: opts.dir || cfg.dir,
|
|
305
549
|
remote: opts.remote || cfg.remote,
|
|
550
|
+
agents: opts.agents || cfg.agents,
|
|
551
|
+
linkMode: opts.linkMode || cfg.linkMode,
|
|
306
552
|
},
|
|
307
553
|
process.env,
|
|
308
554
|
{ dryRun: opts.dryRun }
|
|
@@ -312,45 +558,37 @@ function main() {
|
|
|
312
558
|
remote: opts.remote || undefined,
|
|
313
559
|
});
|
|
314
560
|
applyEnvFromConfig(cfg);
|
|
315
|
-
const
|
|
316
|
-
?
|
|
317
|
-
: runInstall(cfg,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
+
});
|
|
568
|
+
const scripts =
|
|
569
|
+
opts.scripts.length === 0
|
|
570
|
+
? []
|
|
571
|
+
: runScripts(cfg, opts.scripts, opts.scriptArgs, {
|
|
572
|
+
dryRun: opts.dryRun,
|
|
573
|
+
});
|
|
574
|
+
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
327
575
|
const data = collectStatus({
|
|
328
576
|
full: opts.full,
|
|
329
|
-
agents: opts
|
|
577
|
+
agents: agentsOrDefault(opts, cfg),
|
|
330
578
|
from: opts.from,
|
|
331
579
|
});
|
|
332
580
|
emit({
|
|
333
|
-
data: {
|
|
334
|
-
setup: "ok",
|
|
335
|
-
config: saved,
|
|
336
|
-
links,
|
|
337
|
-
tools,
|
|
338
|
-
...data,
|
|
339
|
-
},
|
|
581
|
+
data: { setup: "ok", config: saved, ...result, scripts, ...data },
|
|
340
582
|
json: opts.json,
|
|
341
|
-
help:
|
|
342
|
-
opts.tools
|
|
343
|
-
? "Run `ai-md status` to verify"
|
|
344
|
-
: "Run `ai-md ensure-tools` (or re-run setup with --tools)",
|
|
345
|
-
"Run `ai-md init-project --repo <path> --from base` for a new app",
|
|
346
|
-
],
|
|
583
|
+
help: statusHelp(data),
|
|
347
584
|
});
|
|
585
|
+
if (failed) process.exit(failed.exitCode);
|
|
348
586
|
break;
|
|
349
587
|
}
|
|
350
588
|
case "status": {
|
|
351
589
|
const data = collectStatus({
|
|
352
590
|
full: opts.full,
|
|
353
|
-
agents: opts
|
|
591
|
+
agents: agentsOrDefault(opts, cfg),
|
|
354
592
|
from: opts.from,
|
|
355
593
|
});
|
|
356
594
|
emit({ data, json: opts.json, help: statusHelp(data) });
|
|
@@ -361,14 +599,11 @@ function main() {
|
|
|
361
599
|
const data = runDoctor({
|
|
362
600
|
fix: opts.fix,
|
|
363
601
|
force: opts.force,
|
|
364
|
-
agents: opts
|
|
602
|
+
agents: agentsOrDefault(opts, cfg),
|
|
365
603
|
dryRun: opts.dryRun,
|
|
604
|
+
forceLink: opts.forceLink,
|
|
366
605
|
});
|
|
367
|
-
emit({
|
|
368
|
-
data,
|
|
369
|
-
json: opts.json,
|
|
370
|
-
help: data.help,
|
|
371
|
-
});
|
|
606
|
+
emit({ data, json: opts.json, help: data.help });
|
|
372
607
|
process.exitCode = data.after.problems.length > 0 ? 1 : 0;
|
|
373
608
|
break;
|
|
374
609
|
}
|
|
@@ -383,10 +618,7 @@ function main() {
|
|
|
383
618
|
emit({
|
|
384
619
|
data,
|
|
385
620
|
json: opts.json,
|
|
386
|
-
help: [
|
|
387
|
-
"Customize rules/agentic-workflow.mdc for this project",
|
|
388
|
-
'Run `ai-md push -m "Init <project>"` after reviewing the private repo diff',
|
|
389
|
-
],
|
|
621
|
+
help: ['Run `ai-md push -m "Init <project>"` after review'],
|
|
390
622
|
});
|
|
391
623
|
break;
|
|
392
624
|
}
|
|
@@ -396,14 +628,7 @@ function main() {
|
|
|
396
628
|
from: opts.from,
|
|
397
629
|
dryRun: opts.dryRun,
|
|
398
630
|
});
|
|
399
|
-
emit({
|
|
400
|
-
data,
|
|
401
|
-
json: opts.json,
|
|
402
|
-
help: [
|
|
403
|
-
"Review added files under ~/.ai-md/projects/<name>/",
|
|
404
|
-
'Run `ai-md push -m "Apply template to <project>"` when ready',
|
|
405
|
-
],
|
|
406
|
-
});
|
|
631
|
+
emit({ data, json: opts.json, help: [] });
|
|
407
632
|
break;
|
|
408
633
|
}
|
|
409
634
|
case "link-project":
|
|
@@ -414,13 +639,7 @@ function main() {
|
|
|
414
639
|
force: opts.force,
|
|
415
640
|
dryRun: opts.dryRun,
|
|
416
641
|
});
|
|
417
|
-
emit({
|
|
418
|
-
data,
|
|
419
|
-
json: opts.json,
|
|
420
|
-
help: [
|
|
421
|
-
"Prefer `ai-md init-project --repo <path>` to seed from template first",
|
|
422
|
-
],
|
|
423
|
-
});
|
|
642
|
+
emit({ data, json: opts.json, help: [] });
|
|
424
643
|
break;
|
|
425
644
|
}
|
|
426
645
|
case "install": {
|
|
@@ -432,40 +651,66 @@ function main() {
|
|
|
432
651
|
});
|
|
433
652
|
applyEnvFromConfig(cfg);
|
|
434
653
|
}
|
|
435
|
-
const
|
|
436
|
-
|
|
654
|
+
const result = runInstall(cfg, {
|
|
655
|
+
agents: agentsOrDefault(opts, cfg),
|
|
656
|
+
force: opts.force,
|
|
657
|
+
dryRun: opts.dryRun,
|
|
658
|
+
forceLink: opts.forceLink,
|
|
659
|
+
});
|
|
660
|
+
const scripts =
|
|
661
|
+
opts.scripts.length === 0
|
|
662
|
+
? []
|
|
663
|
+
: runScripts(cfg, opts.scripts, opts.scriptArgs, {
|
|
664
|
+
dryRun: opts.dryRun,
|
|
665
|
+
});
|
|
666
|
+
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
667
|
+
const data = collectStatus({
|
|
668
|
+
full: opts.full,
|
|
669
|
+
agents: agentsOrDefault(opts, cfg),
|
|
670
|
+
});
|
|
437
671
|
emit({
|
|
438
|
-
data: { install: "ok", config: saved,
|
|
672
|
+
data: { install: "ok", config: saved, ...result, scripts, ...data },
|
|
439
673
|
json: opts.json,
|
|
440
|
-
help:
|
|
441
|
-
"Run `ai-md ensure-tools` to install grok + quota-axi",
|
|
442
|
-
"Run `ai-md init-project --repo <path>` for a new app",
|
|
443
|
-
],
|
|
674
|
+
help: statusHelp(data),
|
|
444
675
|
});
|
|
676
|
+
if (failed) process.exit(failed.exitCode);
|
|
445
677
|
break;
|
|
446
678
|
}
|
|
447
|
-
case "pull":
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
+
});
|
|
453
691
|
break;
|
|
692
|
+
}
|
|
454
693
|
case "push": {
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
694
|
+
const data = runPush(cfg, {
|
|
695
|
+
message: opts.message,
|
|
696
|
+
dryRun: opts.dryRun,
|
|
697
|
+
});
|
|
698
|
+
emit({ data, json: opts.json, help: [] });
|
|
459
699
|
break;
|
|
460
700
|
}
|
|
461
|
-
case "
|
|
462
|
-
case "
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
701
|
+
case "script":
|
|
702
|
+
case "run-script": {
|
|
703
|
+
const result = runScript(cfg, opts.scriptName, opts.scriptArgs, {
|
|
704
|
+
dryRun: opts.dryRun,
|
|
705
|
+
});
|
|
706
|
+
emit({
|
|
707
|
+
data: { scripts: [result] },
|
|
708
|
+
json: opts.json,
|
|
709
|
+
help: ["Scripts live in ~/.ai-md/scripts/"],
|
|
710
|
+
});
|
|
711
|
+
process.exit(result.exitCode);
|
|
468
712
|
break;
|
|
713
|
+
}
|
|
469
714
|
default:
|
|
470
715
|
fail(`unknown command: ${opts.cmd}`, {
|
|
471
716
|
exitCode: 2,
|
|
@@ -476,9 +721,12 @@ function main() {
|
|
|
476
721
|
}
|
|
477
722
|
} catch (err) {
|
|
478
723
|
fail(err.message || String(err), {
|
|
479
|
-
exitCode: err.code === "EINVAL" ? 2 : 1,
|
|
724
|
+
exitCode: err.code === "EINVAL" || err.code === "ENOENT" || err.code === "EEXIST" || err.code === "EDIRTY" ? 2 : 1,
|
|
480
725
|
json: opts.json,
|
|
481
|
-
help:
|
|
726
|
+
help:
|
|
727
|
+
err.code === "EDIRTY"
|
|
728
|
+
? ["ai-md rescue --agents <id>", "ai-md build --force"]
|
|
729
|
+
: ["Run `ai-md --help`"],
|
|
482
730
|
});
|
|
483
731
|
process.exit(process.exitCode || 1);
|
|
484
732
|
}
|