@dujavi/ai-md 0.3.0 → 0.4.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 +15 -3
- package/bin/ai-md.js +201 -44
- package/lib/config.js +110 -8
- package/lib/status.js +11 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,12 +10,24 @@ Public **AXI-shaped** CLI for a **private** personal AI config directory (`~/.ai
|
|
|
10
10
|
|
|
11
11
|
No personal content ships in this package. Reads default to TOON + `help[]` (`--json` available).
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## New machine
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm i -g @dujavi/ai-md
|
|
17
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git --tools
|
|
18
|
+
# persists ~/.config/ai-md/config.json then clones + links (+ optional grok/quota-axi)
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
Or step by step:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md
|
|
25
|
+
ai-md install
|
|
26
|
+
ai-md ensure-tools
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Precedence: `--remote`/`--dir` flags > `AI_MD_*` env > `~/.config/ai-md/config.json` > defaults.
|
|
30
|
+
|
|
19
31
|
## Layout idea
|
|
20
32
|
|
|
21
33
|
```text
|
|
@@ -45,5 +57,5 @@ ai-md ensure-tools
|
|
|
45
57
|
|
|
46
58
|
## Environment
|
|
47
59
|
|
|
48
|
-
- `AI_MD_DIR`
|
|
49
|
-
- `
|
|
60
|
+
- `AI_MD_DIR` / `AI_MD_REMOTE` — override for one shot (also set by the CLI after reading config)
|
|
61
|
+
- `AI_MD_CONFIG` — path to machine config JSON (default `~/.config/ai-md/config.json`)
|
package/bin/ai-md.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
resolveConfig,
|
|
8
|
+
applyEnvFromConfig,
|
|
9
|
+
writeMachineConfig,
|
|
10
|
+
readMachineConfig,
|
|
11
|
+
expandHome,
|
|
12
|
+
} = require("../lib/config");
|
|
7
13
|
const { emit, fail } = require("../lib/output");
|
|
8
14
|
const { collectStatus, statusHelp } = require("../lib/status");
|
|
9
15
|
const {
|
|
@@ -16,12 +22,6 @@ const {
|
|
|
16
22
|
} = require("../lib/commands");
|
|
17
23
|
|
|
18
24
|
const scriptsDir = path.join(__dirname, "..", "scripts");
|
|
19
|
-
const cfg = resolveConfig();
|
|
20
|
-
|
|
21
|
-
process.env.AI_MD_DIR = cfg.dir;
|
|
22
|
-
process.env.AI_MD_REMOTE = cfg.remote;
|
|
23
|
-
process.env.CURSOR_MD_DIR = cfg.dir;
|
|
24
|
-
process.env.CURSOR_MD_REMOTE = cfg.remote;
|
|
25
25
|
|
|
26
26
|
function parseArgs(argv) {
|
|
27
27
|
const out = {
|
|
@@ -31,11 +31,14 @@ function parseArgs(argv) {
|
|
|
31
31
|
force: false,
|
|
32
32
|
dryRun: false,
|
|
33
33
|
fix: false,
|
|
34
|
+
tools: false,
|
|
34
35
|
message: null,
|
|
35
36
|
repo: null,
|
|
36
37
|
name: null,
|
|
37
38
|
project: null,
|
|
38
39
|
from: "base",
|
|
40
|
+
remote: null,
|
|
41
|
+
dir: null,
|
|
39
42
|
agents: ["cursor"],
|
|
40
43
|
rest: [],
|
|
41
44
|
};
|
|
@@ -50,7 +53,6 @@ function parseArgs(argv) {
|
|
|
50
53
|
return out;
|
|
51
54
|
}
|
|
52
55
|
if (first.startsWith("-")) {
|
|
53
|
-
// flags before command → treat as status with flags
|
|
54
56
|
out.cmd = "status";
|
|
55
57
|
} else {
|
|
56
58
|
out.cmd = args.shift();
|
|
@@ -74,6 +76,9 @@ function parseArgs(argv) {
|
|
|
74
76
|
out.fix = true;
|
|
75
77
|
out.force = true;
|
|
76
78
|
break;
|
|
79
|
+
case "--tools":
|
|
80
|
+
out.tools = true;
|
|
81
|
+
break;
|
|
77
82
|
case "-m":
|
|
78
83
|
case "--message":
|
|
79
84
|
out.message = args.shift();
|
|
@@ -90,6 +95,12 @@ function parseArgs(argv) {
|
|
|
90
95
|
case "--from":
|
|
91
96
|
out.from = args.shift();
|
|
92
97
|
break;
|
|
98
|
+
case "--remote":
|
|
99
|
+
out.remote = args.shift();
|
|
100
|
+
break;
|
|
101
|
+
case "--dir":
|
|
102
|
+
out.dir = expandHome(args.shift());
|
|
103
|
+
break;
|
|
93
104
|
case "--agents":
|
|
94
105
|
out.agents = String(args.shift() || "cursor")
|
|
95
106
|
.split(",")
|
|
@@ -100,6 +111,11 @@ function parseArgs(argv) {
|
|
|
100
111
|
case "--help":
|
|
101
112
|
out.cmdHelp = true;
|
|
102
113
|
break;
|
|
114
|
+
case "set":
|
|
115
|
+
case "show":
|
|
116
|
+
// subcommand for `ai-md config set|show`
|
|
117
|
+
out.rest.push(a);
|
|
118
|
+
break;
|
|
103
119
|
default:
|
|
104
120
|
if (a.startsWith("-")) {
|
|
105
121
|
fail(`unknown flag: ${a}`, {
|
|
@@ -128,7 +144,13 @@ Layout:
|
|
|
128
144
|
~/.ai-md/templates/<type> Project-type starters (default: base)
|
|
129
145
|
~/.ai-md/projects/<name> Per-app overlays (repo .cursor → here)
|
|
130
146
|
|
|
147
|
+
Machine config (persisted):
|
|
148
|
+
~/.config/ai-md/config.json dir + remote (override with AI_MD_CONFIG)
|
|
149
|
+
Precedence: --flag > env > config file > defaults
|
|
150
|
+
|
|
131
151
|
Commands:
|
|
152
|
+
setup First-time machine setup: save config, install, optional --tools
|
|
153
|
+
config Show persisted config (or: config set --remote/--dir)
|
|
132
154
|
status Snapshot (default when no command) [AXI]
|
|
133
155
|
doctor Diagnose links/projects; --fix repairs
|
|
134
156
|
install Clone remote if needed; link ~/.cursor + optional agents
|
|
@@ -140,31 +162,34 @@ Commands:
|
|
|
140
162
|
help Show this help
|
|
141
163
|
|
|
142
164
|
Options:
|
|
143
|
-
--
|
|
165
|
+
--remote <url> Private content git remote (persisted by setup/config set/install)
|
|
166
|
+
--dir <path> Local AI_MD_DIR (default ~/.ai-md; persisted same way)
|
|
167
|
+
--json JSON instead of TOON
|
|
144
168
|
--full Include paths and drift details
|
|
145
169
|
--agents <list> Skill link targets: cursor,claude,agents (default: cursor)
|
|
170
|
+
--tools With setup: also run ensure-tools
|
|
146
171
|
--repo <path> App repository root
|
|
147
|
-
--name <id> Project id under projects/
|
|
172
|
+
--name <id> Project id under projects/
|
|
148
173
|
--project <id> Target project for apply-template
|
|
149
174
|
--from <id> Template under templates/ (default: base)
|
|
150
|
-
--force Replace non-symlink
|
|
175
|
+
--force Replace non-symlink paths
|
|
151
176
|
--dry-run Preview without writing
|
|
152
177
|
--fix doctor: repair symlinks
|
|
153
178
|
-m, --message push commit message
|
|
154
179
|
|
|
155
180
|
Examples:
|
|
156
|
-
ai-md
|
|
157
|
-
ai-md
|
|
181
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git --tools
|
|
182
|
+
ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md
|
|
183
|
+
ai-md config
|
|
184
|
+
ai-md install --remote https://github.com/<you>/.ai-md.git
|
|
158
185
|
ai-md init-project --repo ~/presenter --from base
|
|
159
|
-
ai-md apply-template --project presenter --from base
|
|
160
|
-
ai-md doctor --fix --agents cursor,claude
|
|
161
186
|
`);
|
|
162
187
|
}
|
|
163
188
|
|
|
164
|
-
function runBash(script, args) {
|
|
189
|
+
function runBash(script, args, env) {
|
|
165
190
|
const result = spawnSync("bash", [path.join(scriptsDir, script), ...args], {
|
|
166
191
|
stdio: "inherit",
|
|
167
|
-
env
|
|
192
|
+
env,
|
|
168
193
|
});
|
|
169
194
|
if (result.error) {
|
|
170
195
|
fail(result.error.message, { exitCode: 1 });
|
|
@@ -173,6 +198,37 @@ function runBash(script, args) {
|
|
|
173
198
|
process.exit(result.status === null ? 1 : result.status);
|
|
174
199
|
}
|
|
175
200
|
|
|
201
|
+
function persistIfRequested(opts) {
|
|
202
|
+
if (opts.remote == null && opts.dir == null) return null;
|
|
203
|
+
return writeMachineConfig(
|
|
204
|
+
{ dir: opts.dir, remote: opts.remote },
|
|
205
|
+
process.env,
|
|
206
|
+
{ dryRun: opts.dryRun }
|
|
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);
|
|
221
|
+
}
|
|
222
|
+
const links = [
|
|
223
|
+
...ensureCursorLinks(cfg, { force: opts.force, dryRun: opts.dryRun }),
|
|
224
|
+
...ensureAgentSkillLinks(cfg, opts.agents, {
|
|
225
|
+
force: opts.force,
|
|
226
|
+
dryRun: opts.dryRun,
|
|
227
|
+
}),
|
|
228
|
+
];
|
|
229
|
+
return links;
|
|
230
|
+
}
|
|
231
|
+
|
|
176
232
|
function main() {
|
|
177
233
|
const opts = parseArgs(process.argv.slice(2));
|
|
178
234
|
|
|
@@ -181,8 +237,116 @@ function main() {
|
|
|
181
237
|
return;
|
|
182
238
|
}
|
|
183
239
|
|
|
240
|
+
// Resolve after flags so --dir/--remote apply
|
|
241
|
+
let cfg = resolveConfig(process.env, {
|
|
242
|
+
dir: opts.dir || undefined,
|
|
243
|
+
remote: opts.remote || undefined,
|
|
244
|
+
});
|
|
245
|
+
applyEnvFromConfig(cfg);
|
|
246
|
+
|
|
184
247
|
try {
|
|
185
248
|
switch (opts.cmd) {
|
|
249
|
+
case "config": {
|
|
250
|
+
const sub = opts.rest[0] || "show";
|
|
251
|
+
if (sub === "set") {
|
|
252
|
+
if (opts.remote == null && opts.dir == null) {
|
|
253
|
+
fail("config set requires --remote and/or --dir", {
|
|
254
|
+
exitCode: 2,
|
|
255
|
+
json: opts.json,
|
|
256
|
+
help: [
|
|
257
|
+
"ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md",
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
process.exit(2);
|
|
261
|
+
}
|
|
262
|
+
const saved = writeMachineConfig(
|
|
263
|
+
{ dir: opts.dir, remote: opts.remote },
|
|
264
|
+
process.env,
|
|
265
|
+
{ dryRun: opts.dryRun }
|
|
266
|
+
);
|
|
267
|
+
cfg = resolveConfig(process.env);
|
|
268
|
+
emit({
|
|
269
|
+
data: { ...saved, resolved: { dir: cfg.dir, remote: cfg.remote, sources: cfg.sources } },
|
|
270
|
+
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
|
+
],
|
|
275
|
+
});
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
const stored = readMachineConfig();
|
|
279
|
+
cfg = resolveConfig(process.env);
|
|
280
|
+
emit({
|
|
281
|
+
data: {
|
|
282
|
+
path: stored.path,
|
|
283
|
+
stored: stored.raw,
|
|
284
|
+
resolved: {
|
|
285
|
+
dir: cfg.dir,
|
|
286
|
+
remote: cfg.remote,
|
|
287
|
+
sources: cfg.sources,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
json: opts.json,
|
|
291
|
+
help: [
|
|
292
|
+
"ai-md config set --remote <url> --dir ~/.ai-md",
|
|
293
|
+
"Flags and env override the config file",
|
|
294
|
+
],
|
|
295
|
+
});
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case "setup": {
|
|
299
|
+
if (!opts.remote && !cfg.machineConfig?.remote && cfg.sources.remote === "default") {
|
|
300
|
+
// Allow default for dujavi, but recommend explicit remote for others via help
|
|
301
|
+
}
|
|
302
|
+
const saved = writeMachineConfig(
|
|
303
|
+
{
|
|
304
|
+
dir: opts.dir || cfg.dir,
|
|
305
|
+
remote: opts.remote || cfg.remote,
|
|
306
|
+
},
|
|
307
|
+
process.env,
|
|
308
|
+
{ dryRun: opts.dryRun }
|
|
309
|
+
);
|
|
310
|
+
cfg = resolveConfig(process.env, {
|
|
311
|
+
dir: opts.dir || undefined,
|
|
312
|
+
remote: opts.remote || undefined,
|
|
313
|
+
});
|
|
314
|
+
applyEnvFromConfig(cfg);
|
|
315
|
+
const links = opts.dryRun
|
|
316
|
+
? []
|
|
317
|
+
: runInstall(cfg, opts);
|
|
318
|
+
let tools = null;
|
|
319
|
+
if (opts.tools && !opts.dryRun) {
|
|
320
|
+
const tr = spawnSync(
|
|
321
|
+
"bash",
|
|
322
|
+
[path.join(scriptsDir, "ensure-agent-tools.sh")],
|
|
323
|
+
{ stdio: "inherit", env: process.env }
|
|
324
|
+
);
|
|
325
|
+
tools = { exitCode: tr.status };
|
|
326
|
+
}
|
|
327
|
+
const data = collectStatus({
|
|
328
|
+
full: opts.full,
|
|
329
|
+
agents: opts.agents,
|
|
330
|
+
from: opts.from,
|
|
331
|
+
});
|
|
332
|
+
emit({
|
|
333
|
+
data: {
|
|
334
|
+
setup: "ok",
|
|
335
|
+
config: saved,
|
|
336
|
+
links,
|
|
337
|
+
tools,
|
|
338
|
+
...data,
|
|
339
|
+
},
|
|
340
|
+
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
|
+
],
|
|
347
|
+
});
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
186
350
|
case "status": {
|
|
187
351
|
const data = collectStatus({
|
|
188
352
|
full: opts.full,
|
|
@@ -260,28 +424,18 @@ function main() {
|
|
|
260
424
|
break;
|
|
261
425
|
}
|
|
262
426
|
case "install": {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
{ stdio: "inherit", env: process.env }
|
|
271
|
-
);
|
|
272
|
-
if (result.status !== 0) {
|
|
273
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
427
|
+
const saved = persistIfRequested(opts);
|
|
428
|
+
if (saved) {
|
|
429
|
+
cfg = resolveConfig(process.env, {
|
|
430
|
+
dir: opts.dir || undefined,
|
|
431
|
+
remote: opts.remote || undefined,
|
|
432
|
+
});
|
|
433
|
+
applyEnvFromConfig(cfg);
|
|
274
434
|
}
|
|
275
|
-
const links =
|
|
276
|
-
...ensureCursorLinks(cfg, { force: opts.force, dryRun: opts.dryRun }),
|
|
277
|
-
...ensureAgentSkillLinks(cfg, opts.agents, {
|
|
278
|
-
force: opts.force,
|
|
279
|
-
dryRun: opts.dryRun,
|
|
280
|
-
}),
|
|
281
|
-
];
|
|
435
|
+
const links = runInstall(cfg, opts);
|
|
282
436
|
const data = collectStatus({ full: opts.full, agents: opts.agents });
|
|
283
437
|
emit({
|
|
284
|
-
data: { install: "ok", links, ...data },
|
|
438
|
+
data: { install: "ok", config: saved, links, ...data },
|
|
285
439
|
json: opts.json,
|
|
286
440
|
help: [
|
|
287
441
|
"Run `ai-md ensure-tools` to install grok + quota-axi",
|
|
@@ -291,23 +445,26 @@ function main() {
|
|
|
291
445
|
break;
|
|
292
446
|
}
|
|
293
447
|
case "pull":
|
|
294
|
-
runBash(
|
|
295
|
-
"
|
|
296
|
-
...(opts.dryRun ? ["--dry-run"] : []),
|
|
297
|
-
|
|
448
|
+
runBash(
|
|
449
|
+
"sync-config.sh",
|
|
450
|
+
["pull", ...(opts.dryRun ? ["--dry-run"] : [])],
|
|
451
|
+
process.env
|
|
452
|
+
);
|
|
298
453
|
break;
|
|
299
454
|
case "push": {
|
|
300
455
|
const args = ["push"];
|
|
301
456
|
if (opts.message) args.push("-m", opts.message);
|
|
302
457
|
if (opts.dryRun) args.push("--dry-run");
|
|
303
|
-
runBash("sync-config.sh", args);
|
|
458
|
+
runBash("sync-config.sh", args, process.env);
|
|
304
459
|
break;
|
|
305
460
|
}
|
|
306
461
|
case "ensure-tools":
|
|
307
462
|
case "tools":
|
|
308
|
-
runBash(
|
|
309
|
-
|
|
310
|
-
|
|
463
|
+
runBash(
|
|
464
|
+
"ensure-agent-tools.sh",
|
|
465
|
+
[...(opts.dryRun ? ["--dry-run"] : [])],
|
|
466
|
+
process.env
|
|
467
|
+
);
|
|
311
468
|
break;
|
|
312
469
|
default:
|
|
313
470
|
fail(`unknown command: ${opts.cmd}`, {
|
package/lib/config.js
CHANGED
|
@@ -4,30 +4,127 @@ const fs = require("fs");
|
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const DEFAULT_REMOTE = "https://github.com/dujavi/.ai-md.git";
|
|
8
|
+
|
|
9
|
+
function expandHome(p) {
|
|
10
|
+
if (!p) return p;
|
|
11
|
+
if (p === "~") return os.homedir();
|
|
12
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Machine-local config (not inside the private content repo). */
|
|
17
|
+
function machineConfigPath(env = process.env) {
|
|
18
|
+
if (env.AI_MD_CONFIG) return expandHome(env.AI_MD_CONFIG);
|
|
19
|
+
const xdg = env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
20
|
+
return path.join(xdg, "ai-md", "config.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readMachineConfig(env = process.env) {
|
|
24
|
+
const configPath = machineConfigPath(env);
|
|
25
|
+
try {
|
|
26
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
27
|
+
const data = JSON.parse(raw);
|
|
28
|
+
return {
|
|
29
|
+
path: configPath,
|
|
30
|
+
dir: data.dir ? expandHome(String(data.dir)) : null,
|
|
31
|
+
remote: data.remote ? String(data.remote) : null,
|
|
32
|
+
raw: data,
|
|
33
|
+
};
|
|
34
|
+
} catch {
|
|
35
|
+
return { path: configPath, dir: null, remote: null, raw: null };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeMachineConfig({ dir, remote }, env = process.env, { dryRun = false } = {}) {
|
|
40
|
+
const configPath = machineConfigPath(env);
|
|
41
|
+
const existing = readMachineConfig(env).raw || {};
|
|
42
|
+
const next = {
|
|
43
|
+
...existing,
|
|
44
|
+
...(dir != null ? { dir: expandHome(dir) } : {}),
|
|
45
|
+
...(remote != null ? { remote: String(remote) } : {}),
|
|
46
|
+
updatedAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
if (!next.dir && !next.remote) {
|
|
49
|
+
const err = new Error("nothing to write: provide --dir and/or --remote");
|
|
50
|
+
err.code = "EINVAL";
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
if (dryRun) {
|
|
54
|
+
return { action: "would_write", path: configPath, config: next };
|
|
55
|
+
}
|
|
56
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
57
|
+
fs.writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, {
|
|
58
|
+
mode: 0o600,
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
fs.chmodSync(configPath, 0o600);
|
|
62
|
+
} catch {
|
|
63
|
+
/* best effort */
|
|
64
|
+
}
|
|
65
|
+
return { action: "wrote", path: configPath, config: next };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Precedence: explicit opts > env > machine config file > defaults.
|
|
70
|
+
* @param {{ dir?: string, remote?: string }} [opts]
|
|
71
|
+
*/
|
|
72
|
+
function resolveConfig(env = process.env, opts = {}) {
|
|
8
73
|
const home = os.homedir();
|
|
9
|
-
const
|
|
10
|
-
|
|
74
|
+
const stored = readMachineConfig(env);
|
|
75
|
+
const defaultDir = path.join(home, ".ai-md");
|
|
76
|
+
|
|
77
|
+
const envDir = env.AI_MD_DIR || env.CURSOR_MD_DIR || null;
|
|
78
|
+
const envRemote = env.AI_MD_REMOTE || env.CURSOR_MD_REMOTE || null;
|
|
79
|
+
|
|
80
|
+
const dir = expandHome(
|
|
81
|
+
opts.dir || envDir || stored.dir || defaultDir
|
|
82
|
+
);
|
|
11
83
|
const remote =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
84
|
+
opts.remote || envRemote || stored.remote || DEFAULT_REMOTE;
|
|
85
|
+
|
|
86
|
+
const sources = {
|
|
87
|
+
dir: opts.dir
|
|
88
|
+
? "flag"
|
|
89
|
+
: envDir && !(stored.dir && expandHome(envDir) === stored.dir)
|
|
90
|
+
? "env"
|
|
91
|
+
: stored.dir
|
|
92
|
+
? "config"
|
|
93
|
+
: "default",
|
|
94
|
+
remote: opts.remote
|
|
95
|
+
? "flag"
|
|
96
|
+
: envRemote && !(stored.remote && envRemote === stored.remote)
|
|
97
|
+
? "env"
|
|
98
|
+
: stored.remote
|
|
99
|
+
? "config"
|
|
100
|
+
: "default",
|
|
101
|
+
};
|
|
102
|
+
|
|
15
103
|
const templatesDir = path.join(dir, "templates");
|
|
16
104
|
return {
|
|
17
105
|
home,
|
|
18
106
|
dir,
|
|
19
107
|
remote,
|
|
108
|
+
sources,
|
|
109
|
+
machineConfigPath: stored.path,
|
|
110
|
+
machineConfig: stored.raw,
|
|
20
111
|
cursorSkills: path.join(home, ".cursor", "skills"),
|
|
21
112
|
cursorRules: path.join(home, ".cursor", "rules"),
|
|
22
113
|
projectsDir: path.join(dir, "projects"),
|
|
23
114
|
templatesDir,
|
|
24
|
-
/** @deprecated use templatePath(cfg, name) */
|
|
25
115
|
templateDir: path.join(templatesDir, "base"),
|
|
26
116
|
skillsDir: path.join(dir, "skills"),
|
|
27
117
|
rulesDir: path.join(dir, "rules"),
|
|
28
118
|
};
|
|
29
119
|
}
|
|
30
120
|
|
|
121
|
+
function applyEnvFromConfig(cfg) {
|
|
122
|
+
process.env.AI_MD_DIR = cfg.dir;
|
|
123
|
+
process.env.AI_MD_REMOTE = cfg.remote;
|
|
124
|
+
process.env.CURSOR_MD_DIR = cfg.dir;
|
|
125
|
+
process.env.CURSOR_MD_REMOTE = cfg.remote;
|
|
126
|
+
}
|
|
127
|
+
|
|
31
128
|
function templatePath(cfg, name = "base") {
|
|
32
129
|
return path.join(cfg.templatesDir, name);
|
|
33
130
|
}
|
|
@@ -104,7 +201,6 @@ function agentSkillTargets(home, agents) {
|
|
|
104
201
|
.map((name) => ({ name, path: map[name] }));
|
|
105
202
|
}
|
|
106
203
|
|
|
107
|
-
/** One-time: projects/template → templates/base */
|
|
108
204
|
function migrateLegacyTemplate(cfg, { dryRun = false } = {}) {
|
|
109
205
|
const legacy = path.join(cfg.projectsDir, "template");
|
|
110
206
|
const dest = templatePath(cfg, "base");
|
|
@@ -118,7 +214,13 @@ function migrateLegacyTemplate(cfg, { dryRun = false } = {}) {
|
|
|
118
214
|
}
|
|
119
215
|
|
|
120
216
|
module.exports = {
|
|
217
|
+
DEFAULT_REMOTE,
|
|
218
|
+
machineConfigPath,
|
|
219
|
+
readMachineConfig,
|
|
220
|
+
writeMachineConfig,
|
|
121
221
|
resolveConfig,
|
|
222
|
+
applyEnvFromConfig,
|
|
223
|
+
expandHome,
|
|
122
224
|
templatePath,
|
|
123
225
|
listTemplates,
|
|
124
226
|
migrateLegacyTemplate,
|
package/lib/status.js
CHANGED
|
@@ -135,6 +135,9 @@ function collectStatus(opts = {}) {
|
|
|
135
135
|
generatedAt: new Date().toISOString(),
|
|
136
136
|
dir: cfg.dir,
|
|
137
137
|
remote: remote || cfg.remote,
|
|
138
|
+
sources: cfg.sources,
|
|
139
|
+
machineConfigPath: cfg.machineConfigPath,
|
|
140
|
+
machineConfig: cfg.machineConfig,
|
|
138
141
|
branch,
|
|
139
142
|
dirty,
|
|
140
143
|
statusLine: aheadBehind,
|
|
@@ -173,7 +176,14 @@ function collectStatus(opts = {}) {
|
|
|
173
176
|
function statusHelp(data) {
|
|
174
177
|
const help = [];
|
|
175
178
|
if (data.problems.includes("ai_md_missing") || data.problems.includes("ai_md_not_git")) {
|
|
176
|
-
help.push(
|
|
179
|
+
help.push(
|
|
180
|
+
"Run `ai-md setup --remote <git-url>` (or `ai-md install --remote <git-url>`) to clone and link"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (!data.machineConfig) {
|
|
184
|
+
help.push(
|
|
185
|
+
"Persist remote/dir with `ai-md setup --remote <url>` or `ai-md config set --remote <url> --dir ~/.ai-md`"
|
|
186
|
+
);
|
|
177
187
|
}
|
|
178
188
|
if (data.links.some((l) => l.state !== "ok")) {
|
|
179
189
|
help.push("Run `ai-md doctor --fix` to repair ~/.cursor skills/rules symlinks");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dujavi/ai-md",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AXI-shaped CLI for ~/.ai-md
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "AXI-shaped CLI for ~/.ai-md with persisted dir/remote machine config, templates/, and projects/",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-md": "bin/ai-md.js"
|
|
7
7
|
},
|