@dujavi/ai-md 0.4.0 → 0.5.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 +104 -32
- package/bin/ai-md.js +192 -102
- package/docs/architecture.html +316 -0
- package/docs/architecture.png +0 -0
- package/lib/scripts.js +104 -0
- package/package.json +9 -6
- package/scripts/sync-config.sh +2 -2
- package/scripts/ensure-agent-tools.sh +0 -114
package/README.md
CHANGED
|
@@ -1,61 +1,133 @@
|
|
|
1
|
-
# ai-md
|
|
1
|
+
# @dujavi/ai-md
|
|
2
2
|
|
|
3
3
|
Public **AXI-shaped** CLI for a **private** personal AI config directory (`~/.ai-md`).
|
|
4
4
|
|
|
5
|
+
The npm package ships tooling only — no personal skills, rules, or secrets. Your content lives in a private git repo you point at with `--remote` (or machine config). Default reads are TOON + `help[]`; use `--json` when you need JSON.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## What it manages
|
|
10
|
+
|
|
5
11
|
| Layer | Path | Purpose |
|
|
6
12
|
|-------|------|---------|
|
|
7
|
-
| **System** | `skills/`, `rules/` | Global base — linked
|
|
13
|
+
| **System** | `skills/`, `rules/` | Global base — linked into agent harnesses |
|
|
8
14
|
| **Templates** | `templates/<type>/` | Project-type starters (`base`, later `forms`, …) |
|
|
9
15
|
| **Projects** | `projects/<name>/` | Per-app overlays (repo `.cursor` → here) |
|
|
16
|
+
| **Scripts** | `scripts/<name>` | Private machine scripts via `ai-md script` / `setup --script` |
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
## Supported harnesses
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
Skill (and for Cursor, rules) link targets via `--agents` (comma-separated):
|
|
21
|
+
|
|
22
|
+
| Harness | Skills link | Notes |
|
|
23
|
+
|---------|-------------|--------|
|
|
24
|
+
| `cursor` | `~/.cursor/skills` | **Default.** Install also links `~/.cursor/rules` → `~/.ai-md/rules` |
|
|
25
|
+
| `claude` | `~/.claude/skills` | Optional second harness |
|
|
26
|
+
| `agents` | `~/.agents/skills` | Optional agents-skills layout |
|
|
14
27
|
|
|
15
28
|
```bash
|
|
16
|
-
|
|
17
|
-
ai-md
|
|
18
|
-
# persists ~/.config/ai-md/config.json then clones + links (+ optional grok/quota-axi)
|
|
29
|
+
ai-md doctor --fix --agents cursor,claude
|
|
30
|
+
ai-md install --agents cursor,claude,agents
|
|
19
31
|
```
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
## Architecture
|
|
22
34
|
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
ai-md
|
|
26
|
-
ai-md
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart TB
|
|
37
|
+
subgraph publicPkg ["@dujavi/ai-md"]
|
|
38
|
+
cli["ai-md CLI"]
|
|
39
|
+
runner["lib/scripts.js"]
|
|
40
|
+
end
|
|
41
|
+
subgraph privateDir ["~/.ai-md"]
|
|
42
|
+
system["skills + rules"]
|
|
43
|
+
templates["templates/"]
|
|
44
|
+
projects["projects/"]
|
|
45
|
+
scripts["scripts/"]
|
|
46
|
+
end
|
|
47
|
+
subgraph harnesses ["Supported harnesses"]
|
|
48
|
+
cursor["cursor ~/.cursor"]
|
|
49
|
+
claude["claude ~/.claude"]
|
|
50
|
+
agents["agents ~/.agents"]
|
|
51
|
+
end
|
|
52
|
+
cli --> system
|
|
53
|
+
cli --> templates
|
|
54
|
+
cli --> projects
|
|
55
|
+
cli --> runner
|
|
56
|
+
runner --> scripts
|
|
57
|
+
system --> cursor
|
|
58
|
+
system --> claude
|
|
59
|
+
system --> agents
|
|
27
60
|
```
|
|
28
61
|
|
|
29
|
-
|
|
62
|
+
Personal installers (Grok, quota-axi, etc.) are **not** baked into this package. Put them in `~/.ai-md/scripts/` and run with `--script` / `ai-md script`.
|
|
30
63
|
|
|
31
|
-
##
|
|
64
|
+
## Quick start
|
|
32
65
|
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
```bash
|
|
67
|
+
npm i -g @dujavi/ai-md
|
|
68
|
+
|
|
69
|
+
# First machine: persist config, clone, link, run a private script
|
|
70
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git --script ensure-tools
|
|
71
|
+
|
|
72
|
+
# Or step by step
|
|
73
|
+
ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md
|
|
74
|
+
ai-md install
|
|
75
|
+
ai-md script ensure-tools
|
|
43
76
|
```
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
Precedence: `--remote` / `--dir` flags > `AI_MD_*` env > `~/.config/ai-md/config.json` > defaults (`~/.ai-md`, package default remote).
|
|
46
79
|
|
|
47
80
|
## Commands
|
|
48
81
|
|
|
49
82
|
```bash
|
|
50
|
-
ai-md # status
|
|
83
|
+
ai-md # status (AXI)
|
|
84
|
+
ai-md setup --remote <url> --script <name>
|
|
85
|
+
ai-md config | config set --remote <url> --dir ~/.ai-md
|
|
86
|
+
ai-md install [--agents cursor,claude]
|
|
87
|
+
ai-md doctor --fix
|
|
88
|
+
ai-md pull | push -m "why"
|
|
89
|
+
ai-md script <name> [--] [args...] # private ~/.ai-md/scripts/
|
|
51
90
|
ai-md init-project --repo ~/app --from base
|
|
52
91
|
ai-md apply-template --project app --from base
|
|
53
|
-
ai-md
|
|
54
|
-
|
|
55
|
-
|
|
92
|
+
ai-md link-project --repo ~/app --name app
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Private scripts
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ai-md script ensure-tools
|
|
99
|
+
ai-md script ensure-tools -- --dry-run
|
|
100
|
+
ai-md setup --remote <url> --script ensure-tools -- --dry-run
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Resolves `$AI_MD_DIR/scripts/<name>` or `<name>.sh` (basename only; no `..` / paths).
|
|
104
|
+
- Args after the script name (or after `--` on `setup` / `install`) are forwarded unchanged.
|
|
105
|
+
- Repeat `--script` on setup/install; the same trailing args apply to each.
|
|
106
|
+
|
|
107
|
+
## Machine config
|
|
108
|
+
|
|
109
|
+
Persisted at `~/.config/ai-md/config.json` (override path with `AI_MD_CONFIG`):
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{ "dir": "/home/you/.ai-md", "remote": "https://github.com/you/.ai-md.git" }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Env one-shots: `AI_MD_DIR`, `AI_MD_REMOTE`.
|
|
116
|
+
|
|
117
|
+
## Layout example
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
~/.ai-md/
|
|
121
|
+
skills/ # system — harnesses see these
|
|
122
|
+
rules/
|
|
123
|
+
scripts/
|
|
124
|
+
ensure-tools.sh # your private tooling
|
|
125
|
+
templates/
|
|
126
|
+
base/
|
|
127
|
+
projects/
|
|
128
|
+
my-app/
|
|
56
129
|
```
|
|
57
130
|
|
|
58
|
-
##
|
|
131
|
+
## License
|
|
59
132
|
|
|
60
|
-
|
|
61
|
-
- `AI_MD_CONFIG` — path to machine config JSON (default `~/.config/ai-md/config.json`)
|
|
133
|
+
MIT
|
package/bin/ai-md.js
CHANGED
|
@@ -20,9 +20,78 @@ const {
|
|
|
20
20
|
ensureCursorLinks,
|
|
21
21
|
ensureAgentSkillLinks,
|
|
22
22
|
} = require("../lib/commands");
|
|
23
|
+
const { runScript, runScripts } = require("../lib/scripts");
|
|
23
24
|
|
|
24
25
|
const scriptsDir = path.join(__dirname, "..", "scripts");
|
|
25
26
|
|
|
27
|
+
function applyFlag(out, flag, args) {
|
|
28
|
+
switch (flag) {
|
|
29
|
+
case "--json":
|
|
30
|
+
out.json = true;
|
|
31
|
+
return true;
|
|
32
|
+
case "--full":
|
|
33
|
+
out.full = true;
|
|
34
|
+
return true;
|
|
35
|
+
case "--force":
|
|
36
|
+
out.force = true;
|
|
37
|
+
return true;
|
|
38
|
+
case "--dry-run":
|
|
39
|
+
out.dryRun = true;
|
|
40
|
+
return true;
|
|
41
|
+
case "--fix":
|
|
42
|
+
out.fix = true;
|
|
43
|
+
out.force = true;
|
|
44
|
+
return true;
|
|
45
|
+
case "-m":
|
|
46
|
+
case "--message":
|
|
47
|
+
out.message = args.shift();
|
|
48
|
+
return true;
|
|
49
|
+
case "--repo":
|
|
50
|
+
out.repo = args.shift();
|
|
51
|
+
return true;
|
|
52
|
+
case "--name":
|
|
53
|
+
out.name = args.shift();
|
|
54
|
+
return true;
|
|
55
|
+
case "--project":
|
|
56
|
+
out.project = args.shift();
|
|
57
|
+
return true;
|
|
58
|
+
case "--from":
|
|
59
|
+
out.from = args.shift();
|
|
60
|
+
return true;
|
|
61
|
+
case "--remote":
|
|
62
|
+
out.remote = args.shift();
|
|
63
|
+
return true;
|
|
64
|
+
case "--dir":
|
|
65
|
+
out.dir = expandHome(args.shift());
|
|
66
|
+
return true;
|
|
67
|
+
case "--agents":
|
|
68
|
+
out.agents = String(args.shift() || "cursor")
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((s) => s.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
return true;
|
|
73
|
+
case "--script": {
|
|
74
|
+
const name = args.shift();
|
|
75
|
+
if (!name) {
|
|
76
|
+
fail("--script requires a name", {
|
|
77
|
+
exitCode: 2,
|
|
78
|
+
json: out.json,
|
|
79
|
+
help: ["ai-md setup --script ensure-tools"],
|
|
80
|
+
});
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
out.scripts.push(name);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
case "-h":
|
|
87
|
+
case "--help":
|
|
88
|
+
out.cmdHelp = true;
|
|
89
|
+
return true;
|
|
90
|
+
default:
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
26
95
|
function parseArgs(argv) {
|
|
27
96
|
const out = {
|
|
28
97
|
cmd: null,
|
|
@@ -31,7 +100,6 @@ function parseArgs(argv) {
|
|
|
31
100
|
force: false,
|
|
32
101
|
dryRun: false,
|
|
33
102
|
fix: false,
|
|
34
|
-
tools: false,
|
|
35
103
|
message: null,
|
|
36
104
|
repo: null,
|
|
37
105
|
name: null,
|
|
@@ -40,6 +108,9 @@ function parseArgs(argv) {
|
|
|
40
108
|
remote: null,
|
|
41
109
|
dir: null,
|
|
42
110
|
agents: ["cursor"],
|
|
111
|
+
scripts: [],
|
|
112
|
+
scriptName: null,
|
|
113
|
+
scriptArgs: [],
|
|
43
114
|
rest: [],
|
|
44
115
|
};
|
|
45
116
|
const args = [...argv];
|
|
@@ -57,67 +128,22 @@ function parseArgs(argv) {
|
|
|
57
128
|
} else {
|
|
58
129
|
out.cmd = args.shift();
|
|
59
130
|
}
|
|
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("-")) {
|
|
131
|
+
|
|
132
|
+
// `script <name> [--] [args...]` — ai-md flags only before the name
|
|
133
|
+
if (out.cmd === "script" || out.cmd === "run-script") {
|
|
134
|
+
while (args.length) {
|
|
135
|
+
const a = args[0];
|
|
136
|
+
if (a === "--") {
|
|
137
|
+
fail("script name required before `--`", {
|
|
138
|
+
exitCode: 2,
|
|
139
|
+
json: out.json,
|
|
140
|
+
help: ["ai-md script ensure-tools -- --dry-run"],
|
|
141
|
+
});
|
|
142
|
+
process.exit(2);
|
|
143
|
+
}
|
|
144
|
+
if (a.startsWith("-")) {
|
|
145
|
+
args.shift();
|
|
146
|
+
if (!applyFlag(out, a, args)) {
|
|
121
147
|
fail(`unknown flag: ${a}`, {
|
|
122
148
|
exitCode: 2,
|
|
123
149
|
json: out.json,
|
|
@@ -125,9 +151,46 @@ function parseArgs(argv) {
|
|
|
125
151
|
});
|
|
126
152
|
process.exit(2);
|
|
127
153
|
}
|
|
128
|
-
|
|
129
|
-
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
out.scriptName = args.shift();
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
if (!out.scriptName) {
|
|
160
|
+
fail("script requires a name", {
|
|
161
|
+
exitCode: 2,
|
|
162
|
+
json: out.json,
|
|
163
|
+
help: ["ai-md script <name> [--] [args...]"],
|
|
164
|
+
});
|
|
165
|
+
process.exit(2);
|
|
166
|
+
}
|
|
167
|
+
if (args[0] === "--") args.shift();
|
|
168
|
+
out.scriptArgs = args;
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
while (args.length) {
|
|
173
|
+
const a = args.shift();
|
|
174
|
+
if (a === "--") {
|
|
175
|
+
out.scriptArgs = args;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
if (a === "set" || a === "show") {
|
|
179
|
+
out.rest.push(a);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (a.startsWith("-")) {
|
|
183
|
+
if (!applyFlag(out, a, args)) {
|
|
184
|
+
fail(`unknown flag: ${a}`, {
|
|
185
|
+
exitCode: 2,
|
|
186
|
+
json: out.json,
|
|
187
|
+
help: ["Run `ai-md --help`"],
|
|
188
|
+
});
|
|
189
|
+
process.exit(2);
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
130
192
|
}
|
|
193
|
+
out.rest.push(a);
|
|
131
194
|
}
|
|
132
195
|
return out;
|
|
133
196
|
}
|
|
@@ -143,19 +206,25 @@ Layout:
|
|
|
143
206
|
~/.ai-md/skills, rules System (global) base — linked to ~/.cursor
|
|
144
207
|
~/.ai-md/templates/<type> Project-type starters (default: base)
|
|
145
208
|
~/.ai-md/projects/<name> Per-app overlays (repo .cursor → here)
|
|
209
|
+
~/.ai-md/scripts/<name> Private machine scripts (ai-md script / setup --script)
|
|
146
210
|
|
|
147
211
|
Machine config (persisted):
|
|
148
212
|
~/.config/ai-md/config.json dir + remote (override with AI_MD_CONFIG)
|
|
149
213
|
Precedence: --flag > env > config file > defaults
|
|
150
214
|
|
|
215
|
+
Supported harnesses (--agents):
|
|
216
|
+
cursor ~/.cursor/skills (+ ~/.cursor/rules via install) [default]
|
|
217
|
+
claude ~/.claude/skills
|
|
218
|
+
agents ~/.agents/skills
|
|
219
|
+
|
|
151
220
|
Commands:
|
|
152
|
-
setup First-time machine setup: save config, install, optional --
|
|
221
|
+
setup First-time machine setup: save config, install, optional --script
|
|
153
222
|
config Show persisted config (or: config set --remote/--dir)
|
|
154
223
|
status Snapshot (default when no command) [AXI]
|
|
155
224
|
doctor Diagnose links/projects; --fix repairs
|
|
156
225
|
install Clone remote if needed; link ~/.cursor + optional agents
|
|
157
226
|
pull | push Sync private git repo
|
|
158
|
-
|
|
227
|
+
script Run ~/.ai-md/scripts/<name> (alias: run-script)
|
|
159
228
|
init-project Seed projects/<name> from templates/<from> + link .cursor/
|
|
160
229
|
apply-template Merge missing files from a template into a project
|
|
161
230
|
link-project Link repo .cursor/ without seeding (alias: link)
|
|
@@ -166,22 +235,26 @@ Options:
|
|
|
166
235
|
--dir <path> Local AI_MD_DIR (default ~/.ai-md; persisted same way)
|
|
167
236
|
--json JSON instead of TOON
|
|
168
237
|
--full Include paths and drift details
|
|
169
|
-
--agents <list> Skill link
|
|
170
|
-
--
|
|
238
|
+
--agents <list> Skill link harnesses: cursor,claude,agents (default: cursor)
|
|
239
|
+
--script <name> With setup/install: run private script (repeatable)
|
|
171
240
|
--repo <path> App repository root
|
|
172
241
|
--name <id> Project id under projects/
|
|
173
242
|
--project <id> Target project for apply-template
|
|
174
243
|
--from <id> Template under templates/ (default: base)
|
|
175
244
|
--force Replace non-symlink paths
|
|
176
|
-
--dry-run Preview without writing
|
|
245
|
+
--dry-run Preview without writing (before script name / before --)
|
|
177
246
|
--fix doctor: repair symlinks
|
|
178
247
|
-m, --message push commit message
|
|
248
|
+
-- End of ai-md options; remaining args go to private scripts
|
|
179
249
|
|
|
180
250
|
Examples:
|
|
181
|
-
ai-md setup --remote https://github.com/<you>/.ai-md.git --tools
|
|
251
|
+
ai-md setup --remote https://github.com/<you>/.ai-md.git --script ensure-tools
|
|
252
|
+
ai-md setup --remote <url> --script ensure-tools -- --dry-run
|
|
253
|
+
ai-md script ensure-tools
|
|
254
|
+
ai-md script ensure-tools -- --dry-run
|
|
182
255
|
ai-md config set --remote https://github.com/<you>/.ai-md.git --dir ~/.ai-md
|
|
183
|
-
ai-md config
|
|
184
256
|
ai-md install --remote https://github.com/<you>/.ai-md.git
|
|
257
|
+
ai-md doctor --fix --agents cursor,claude
|
|
185
258
|
ai-md init-project --repo ~/presenter --from base
|
|
186
259
|
`);
|
|
187
260
|
}
|
|
@@ -237,7 +310,6 @@ function main() {
|
|
|
237
310
|
return;
|
|
238
311
|
}
|
|
239
312
|
|
|
240
|
-
// Resolve after flags so --dir/--remote apply
|
|
241
313
|
let cfg = resolveConfig(process.env, {
|
|
242
314
|
dir: opts.dir || undefined,
|
|
243
315
|
remote: opts.remote || undefined,
|
|
@@ -266,11 +338,18 @@ function main() {
|
|
|
266
338
|
);
|
|
267
339
|
cfg = resolveConfig(process.env);
|
|
268
340
|
emit({
|
|
269
|
-
data: {
|
|
341
|
+
data: {
|
|
342
|
+
...saved,
|
|
343
|
+
resolved: {
|
|
344
|
+
dir: cfg.dir,
|
|
345
|
+
remote: cfg.remote,
|
|
346
|
+
sources: cfg.sources,
|
|
347
|
+
},
|
|
348
|
+
},
|
|
270
349
|
json: opts.json,
|
|
271
350
|
help: [
|
|
272
351
|
"Run `ai-md install` if ~/.ai-md is not cloned yet",
|
|
273
|
-
"Run `ai-md setup --remote <url> --tools` for first-time
|
|
352
|
+
"Run `ai-md setup --remote <url> --script ensure-tools` for first-time bootstrap",
|
|
274
353
|
],
|
|
275
354
|
});
|
|
276
355
|
break;
|
|
@@ -296,9 +375,6 @@ function main() {
|
|
|
296
375
|
break;
|
|
297
376
|
}
|
|
298
377
|
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
378
|
const saved = writeMachineConfig(
|
|
303
379
|
{
|
|
304
380
|
dir: opts.dir || cfg.dir,
|
|
@@ -312,18 +388,14 @@ function main() {
|
|
|
312
388
|
remote: opts.remote || undefined,
|
|
313
389
|
});
|
|
314
390
|
applyEnvFromConfig(cfg);
|
|
315
|
-
const links = opts.dryRun
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
{ stdio: "inherit", env: process.env }
|
|
324
|
-
);
|
|
325
|
-
tools = { exitCode: tr.status };
|
|
326
|
-
}
|
|
391
|
+
const links = opts.dryRun ? [] : runInstall(cfg, opts);
|
|
392
|
+
const scripts =
|
|
393
|
+
opts.scripts.length === 0
|
|
394
|
+
? []
|
|
395
|
+
: runScripts(cfg, opts.scripts, opts.scriptArgs, {
|
|
396
|
+
dryRun: opts.dryRun,
|
|
397
|
+
});
|
|
398
|
+
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
327
399
|
const data = collectStatus({
|
|
328
400
|
full: opts.full,
|
|
329
401
|
agents: opts.agents,
|
|
@@ -334,17 +406,18 @@ function main() {
|
|
|
334
406
|
setup: "ok",
|
|
335
407
|
config: saved,
|
|
336
408
|
links,
|
|
337
|
-
|
|
409
|
+
scripts,
|
|
338
410
|
...data,
|
|
339
411
|
},
|
|
340
412
|
json: opts.json,
|
|
341
413
|
help: [
|
|
342
|
-
opts.
|
|
414
|
+
opts.scripts.length
|
|
343
415
|
? "Run `ai-md status` to verify"
|
|
344
|
-
: "Run `ai-md
|
|
416
|
+
: "Run `ai-md script <name>` for private machine scripts (e.g. ensure-tools)",
|
|
345
417
|
"Run `ai-md init-project --repo <path> --from base` for a new app",
|
|
346
418
|
],
|
|
347
419
|
});
|
|
420
|
+
if (failed) process.exit(failed.exitCode);
|
|
348
421
|
break;
|
|
349
422
|
}
|
|
350
423
|
case "status": {
|
|
@@ -433,15 +506,23 @@ function main() {
|
|
|
433
506
|
applyEnvFromConfig(cfg);
|
|
434
507
|
}
|
|
435
508
|
const links = runInstall(cfg, opts);
|
|
509
|
+
const scripts =
|
|
510
|
+
opts.scripts.length === 0
|
|
511
|
+
? []
|
|
512
|
+
: runScripts(cfg, opts.scripts, opts.scriptArgs, {
|
|
513
|
+
dryRun: opts.dryRun,
|
|
514
|
+
});
|
|
515
|
+
const failed = scripts.find((s) => s.exitCode !== 0);
|
|
436
516
|
const data = collectStatus({ full: opts.full, agents: opts.agents });
|
|
437
517
|
emit({
|
|
438
|
-
data: { install: "ok", config: saved, links, ...data },
|
|
518
|
+
data: { install: "ok", config: saved, links, scripts, ...data },
|
|
439
519
|
json: opts.json,
|
|
440
520
|
help: [
|
|
441
|
-
"Run `ai-md
|
|
521
|
+
"Run `ai-md script <name>` for private machine scripts",
|
|
442
522
|
"Run `ai-md init-project --repo <path>` for a new app",
|
|
443
523
|
],
|
|
444
524
|
});
|
|
525
|
+
if (failed) process.exit(failed.exitCode);
|
|
445
526
|
break;
|
|
446
527
|
}
|
|
447
528
|
case "pull":
|
|
@@ -458,14 +539,23 @@ function main() {
|
|
|
458
539
|
runBash("sync-config.sh", args, process.env);
|
|
459
540
|
break;
|
|
460
541
|
}
|
|
461
|
-
case "
|
|
462
|
-
case "
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
542
|
+
case "script":
|
|
543
|
+
case "run-script": {
|
|
544
|
+
const result = runScript(cfg, opts.scriptName, opts.scriptArgs, {
|
|
545
|
+
dryRun: opts.dryRun,
|
|
546
|
+
});
|
|
547
|
+
emit({
|
|
548
|
+
data: { scripts: [result] },
|
|
549
|
+
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
|
+
],
|
|
555
|
+
});
|
|
556
|
+
process.exit(result.exitCode);
|
|
468
557
|
break;
|
|
558
|
+
}
|
|
469
559
|
default:
|
|
470
560
|
fail(`unknown command: ${opts.cmd}`, {
|
|
471
561
|
exitCode: 2,
|
|
@@ -476,7 +566,7 @@ function main() {
|
|
|
476
566
|
}
|
|
477
567
|
} catch (err) {
|
|
478
568
|
fail(err.message || String(err), {
|
|
479
|
-
exitCode: err.code === "EINVAL" ? 2 : 1,
|
|
569
|
+
exitCode: err.code === "EINVAL" || err.code === "ENOENT" ? 2 : 1,
|
|
480
570
|
json: opts.json,
|
|
481
571
|
help: ["Run `ai-md --help`"],
|
|
482
572
|
});
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>@dujavi/ai-md architecture</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
background: #ffffff;
|
|
12
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
13
|
+
color: #111827;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.canvas {
|
|
18
|
+
width: 1100px;
|
|
19
|
+
padding: 36px 40px 44px;
|
|
20
|
+
background: #ffffff;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
h1 {
|
|
24
|
+
font-size: 22px;
|
|
25
|
+
font-weight: 700;
|
|
26
|
+
letter-spacing: -0.02em;
|
|
27
|
+
text-align: center;
|
|
28
|
+
margin-bottom: 28px;
|
|
29
|
+
color: #0f172a;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.columns {
|
|
33
|
+
display: grid;
|
|
34
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
35
|
+
gap: 28px;
|
|
36
|
+
align-items: start;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.column-label {
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
text-transform: none;
|
|
43
|
+
letter-spacing: 0.01em;
|
|
44
|
+
color: #475569;
|
|
45
|
+
margin-bottom: 10px;
|
|
46
|
+
text-align: center;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.panel {
|
|
50
|
+
border: 2px solid #1e293b;
|
|
51
|
+
border-radius: 10px;
|
|
52
|
+
background: #f8fafc;
|
|
53
|
+
padding: 14px 14px 16px;
|
|
54
|
+
min-height: 280px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.panel-title {
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
color: #0f172a;
|
|
61
|
+
text-align: center;
|
|
62
|
+
margin-bottom: 12px;
|
|
63
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.stack {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
gap: 10px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.box {
|
|
73
|
+
border: 1.5px solid #334155;
|
|
74
|
+
border-radius: 8px;
|
|
75
|
+
background: #ffffff;
|
|
76
|
+
padding: 12px 14px;
|
|
77
|
+
text-align: center;
|
|
78
|
+
font-size: 13px;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
color: #0f172a;
|
|
81
|
+
line-height: 1.35;
|
|
82
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.box.primary {
|
|
86
|
+
background: #eff6ff;
|
|
87
|
+
border-color: #1d4ed8;
|
|
88
|
+
color: #1e3a8a;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.box.system {
|
|
92
|
+
background: #ecfdf5;
|
|
93
|
+
border-color: #047857;
|
|
94
|
+
color: #064e3b;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.box.template {
|
|
98
|
+
background: #fffbeb;
|
|
99
|
+
border-color: #b45309;
|
|
100
|
+
color: #78350f;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.box.project {
|
|
104
|
+
background: #fdf2f8;
|
|
105
|
+
border-color: #be185d;
|
|
106
|
+
color: #831843;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.box.script {
|
|
110
|
+
background: #f1f5f9;
|
|
111
|
+
border-color: #475569;
|
|
112
|
+
color: #1e293b;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.box.harness {
|
|
116
|
+
background: #f5f3ff;
|
|
117
|
+
border-color: #5b21b6;
|
|
118
|
+
color: #4c1d95;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.flow {
|
|
122
|
+
position: relative;
|
|
123
|
+
margin-top: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* SVG overlay for arrows spanning columns */
|
|
127
|
+
.arrows {
|
|
128
|
+
position: absolute;
|
|
129
|
+
inset: 0;
|
|
130
|
+
width: 100%;
|
|
131
|
+
height: 100%;
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
overflow: visible;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.diagram {
|
|
137
|
+
position: relative;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.legend {
|
|
141
|
+
margin-top: 22px;
|
|
142
|
+
display: flex;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
gap: 28px;
|
|
145
|
+
flex-wrap: wrap;
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
color: #475569;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.legend-item {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.legend-line {
|
|
157
|
+
width: 28px;
|
|
158
|
+
height: 0;
|
|
159
|
+
border-top: 2px solid #1e293b;
|
|
160
|
+
position: relative;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.legend-line::after {
|
|
164
|
+
content: "";
|
|
165
|
+
position: absolute;
|
|
166
|
+
right: -1px;
|
|
167
|
+
top: -4px;
|
|
168
|
+
border: 4px solid transparent;
|
|
169
|
+
border-left: 7px solid #1e293b;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.footer-note {
|
|
173
|
+
margin-top: 14px;
|
|
174
|
+
text-align: center;
|
|
175
|
+
font-size: 11px;
|
|
176
|
+
color: #64748b;
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
<div class="canvas" id="capture">
|
|
182
|
+
<h1>@dujavi/ai-md</h1>
|
|
183
|
+
|
|
184
|
+
<div class="diagram" id="diagram">
|
|
185
|
+
<div class="columns" id="columns">
|
|
186
|
+
<!-- Left: package -->
|
|
187
|
+
<div class="col" id="col-pkg">
|
|
188
|
+
<div class="column-label">npm package</div>
|
|
189
|
+
<div class="panel">
|
|
190
|
+
<div class="panel-title">@dujavi/ai-md</div>
|
|
191
|
+
<div class="stack">
|
|
192
|
+
<div class="box primary" id="box-cli">ai-md CLI</div>
|
|
193
|
+
<div class="box script" id="box-runner">lib/scripts.js</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Center: private dir -->
|
|
199
|
+
<div class="col" id="col-private">
|
|
200
|
+
<div class="column-label">private config</div>
|
|
201
|
+
<div class="panel">
|
|
202
|
+
<div class="panel-title">~/.ai-md</div>
|
|
203
|
+
<div class="stack">
|
|
204
|
+
<div class="box system" id="box-system">skills + rules</div>
|
|
205
|
+
<div class="box template" id="box-templates">templates/</div>
|
|
206
|
+
<div class="box project" id="box-projects">projects/</div>
|
|
207
|
+
<div class="box script" id="box-scripts">scripts/</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<!-- Right: harnesses -->
|
|
213
|
+
<div class="col" id="col-harness">
|
|
214
|
+
<div class="column-label">Supported harnesses</div>
|
|
215
|
+
<div class="panel">
|
|
216
|
+
<div class="panel-title">Supported harnesses</div>
|
|
217
|
+
<div class="stack">
|
|
218
|
+
<div class="box harness" id="box-cursor">cursor ~/.cursor</div>
|
|
219
|
+
<div class="box harness" id="box-claude">claude ~/.claude</div>
|
|
220
|
+
<div class="box harness" id="box-agents">agents ~/.agents</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<svg class="arrows" id="arrows" xmlns="http://www.w3.org/2000/svg"></svg>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div class="legend">
|
|
230
|
+
<div class="legend-item"><span class="legend-line"></span> manages / links</div>
|
|
231
|
+
</div>
|
|
232
|
+
<p class="footer-note">Public package tooling · private content in ~/.ai-md · linked into agent harnesses</p>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<script>
|
|
236
|
+
function centerOf(el, root) {
|
|
237
|
+
const r = el.getBoundingClientRect();
|
|
238
|
+
const b = root.getBoundingClientRect();
|
|
239
|
+
return {
|
|
240
|
+
x: r.left - b.left + r.width / 2,
|
|
241
|
+
y: r.top - b.top + r.height / 2,
|
|
242
|
+
left: r.left - b.left,
|
|
243
|
+
right: r.left - b.left + r.width,
|
|
244
|
+
top: r.top - b.top,
|
|
245
|
+
bottom: r.top - b.top + r.height,
|
|
246
|
+
width: r.width,
|
|
247
|
+
height: r.height,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function draw() {
|
|
252
|
+
const root = document.getElementById("diagram");
|
|
253
|
+
const svg = document.getElementById("arrows");
|
|
254
|
+
const rect = root.getBoundingClientRect();
|
|
255
|
+
svg.setAttribute("width", rect.width);
|
|
256
|
+
svg.setAttribute("height", rect.height);
|
|
257
|
+
svg.style.width = rect.width + "px";
|
|
258
|
+
svg.style.height = rect.height + "px";
|
|
259
|
+
svg.innerHTML = "";
|
|
260
|
+
|
|
261
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
262
|
+
defs.innerHTML = `
|
|
263
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
|
|
264
|
+
markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
265
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1e293b"/>
|
|
266
|
+
</marker>
|
|
267
|
+
`;
|
|
268
|
+
svg.appendChild(defs);
|
|
269
|
+
|
|
270
|
+
const cli = centerOf(document.getElementById("box-cli"), root);
|
|
271
|
+
const runner = centerOf(document.getElementById("box-runner"), root);
|
|
272
|
+
const system = centerOf(document.getElementById("box-system"), root);
|
|
273
|
+
const templates = centerOf(document.getElementById("box-templates"), root);
|
|
274
|
+
const projects = centerOf(document.getElementById("box-projects"), root);
|
|
275
|
+
const scripts = centerOf(document.getElementById("box-scripts"), root);
|
|
276
|
+
const cursor = centerOf(document.getElementById("box-cursor"), root);
|
|
277
|
+
const claude = centerOf(document.getElementById("box-claude"), root);
|
|
278
|
+
const agents = centerOf(document.getElementById("box-agents"), root);
|
|
279
|
+
|
|
280
|
+
function line(x1, y1, x2, y2, opts = {}) {
|
|
281
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
282
|
+
const midX = (x1 + x2) / 2;
|
|
283
|
+
const d = opts.curve
|
|
284
|
+
? `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`
|
|
285
|
+
: `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
286
|
+
path.setAttribute("d", d);
|
|
287
|
+
path.setAttribute("fill", "none");
|
|
288
|
+
path.setAttribute("stroke", opts.color || "#1e293b");
|
|
289
|
+
path.setAttribute("stroke-width", opts.width || "2");
|
|
290
|
+
path.setAttribute("marker-end", "url(#arrow)");
|
|
291
|
+
if (opts.dash) path.setAttribute("stroke-dasharray", "5 4");
|
|
292
|
+
svg.appendChild(path);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// CLI → skills+rules, templates, projects
|
|
296
|
+
line(cli.right, cli.y, system.left, system.y, { curve: true });
|
|
297
|
+
line(cli.right, cli.y + 2, templates.left, templates.y, { curve: true });
|
|
298
|
+
line(cli.right, cli.y + 4, projects.left, projects.y, { curve: true });
|
|
299
|
+
|
|
300
|
+
// CLI → lib/scripts.js (vertical within left panel)
|
|
301
|
+
line(cli.x, cli.bottom, runner.x, runner.top, { curve: false });
|
|
302
|
+
|
|
303
|
+
// lib/scripts.js → scripts/
|
|
304
|
+
line(runner.right, runner.y, scripts.left, scripts.y, { curve: true });
|
|
305
|
+
|
|
306
|
+
// skills+rules → each harness
|
|
307
|
+
line(system.right, system.y, cursor.left, cursor.y, { curve: true });
|
|
308
|
+
line(system.right, system.y + 2, claude.left, claude.y, { curve: true });
|
|
309
|
+
line(system.right, system.y + 4, agents.left, agents.y, { curve: true });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
requestAnimationFrame(() => requestAnimationFrame(draw));
|
|
313
|
+
window.addEventListener("resize", draw);
|
|
314
|
+
</script>
|
|
315
|
+
</body>
|
|
316
|
+
</html>
|
|
Binary file
|
package/lib/scripts.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawnSync } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8
|
+
|
|
9
|
+
function scriptsDir(cfg) {
|
|
10
|
+
return path.join(cfg.dir, "scripts");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a private script by basename only (no path separators).
|
|
15
|
+
* Prefers exact name, then `<name>.sh`.
|
|
16
|
+
*/
|
|
17
|
+
function resolveScript(cfg, name) {
|
|
18
|
+
const raw = String(name || "").trim();
|
|
19
|
+
if (!raw || !NAME_RE.test(raw) || raw.includes("..")) {
|
|
20
|
+
const err = new Error(
|
|
21
|
+
`invalid script name: ${JSON.stringify(name)} (use a single basename)`
|
|
22
|
+
);
|
|
23
|
+
err.code = "EINVAL";
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dir = scriptsDir(cfg);
|
|
28
|
+
const exact = path.join(dir, raw);
|
|
29
|
+
const withSh = raw.endsWith(".sh") ? null : path.join(dir, `${raw}.sh`);
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(exact) && fs.statSync(exact).isFile()) {
|
|
32
|
+
return { name: raw, path: exact, dir };
|
|
33
|
+
}
|
|
34
|
+
if (withSh && fs.existsSync(withSh) && fs.statSync(withSh).isFile()) {
|
|
35
|
+
return { name: raw, path: withSh, dir };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const err = new Error(
|
|
39
|
+
`script not found: ${raw} (looked in ${dir}/${raw} and ${dir}/${raw}.sh)`
|
|
40
|
+
);
|
|
41
|
+
err.code = "ENOENT";
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run a private script with forwarded args. Returns { name, args, path, exitCode }.
|
|
47
|
+
* Uses bash for .sh; otherwise executes the file directly.
|
|
48
|
+
*/
|
|
49
|
+
function runScript(cfg, name, args = [], { dryRun = false } = {}) {
|
|
50
|
+
const resolved = resolveScript(cfg, name);
|
|
51
|
+
const scriptArgs = Array.isArray(args) ? args.map(String) : [];
|
|
52
|
+
const useBash = resolved.path.endsWith(".sh");
|
|
53
|
+
const argv = useBash
|
|
54
|
+
? ["bash", resolved.path, ...scriptArgs]
|
|
55
|
+
: [resolved.path, ...scriptArgs];
|
|
56
|
+
|
|
57
|
+
if (dryRun) {
|
|
58
|
+
return {
|
|
59
|
+
name: resolved.name,
|
|
60
|
+
args: scriptArgs,
|
|
61
|
+
path: resolved.path,
|
|
62
|
+
exitCode: 0,
|
|
63
|
+
dryRun: true,
|
|
64
|
+
command: argv,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = spawnSync(argv[0], argv.slice(1), {
|
|
69
|
+
stdio: "inherit",
|
|
70
|
+
env: process.env,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (result.error) {
|
|
74
|
+
const err = new Error(result.error.message);
|
|
75
|
+
err.code = result.error.code || "EEXEC";
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name: resolved.name,
|
|
81
|
+
args: scriptArgs,
|
|
82
|
+
path: resolved.path,
|
|
83
|
+
exitCode: result.status === null ? 1 : result.status,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function runScripts(cfg, names, args = [], { dryRun = false } = {}) {
|
|
88
|
+
const results = [];
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
const result = runScript(cfg, name, args, { dryRun });
|
|
91
|
+
results.push(result);
|
|
92
|
+
if (!dryRun && result.exitCode !== 0) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
scriptsDir,
|
|
101
|
+
resolveScript,
|
|
102
|
+
runScript,
|
|
103
|
+
runScripts,
|
|
104
|
+
};
|
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.5.0",
|
|
4
|
+
"description": "AXI-shaped CLI for a private ~/.ai-md: system skills/rules, templates, projects, and private script runner for agent harnesses (Cursor, Claude, agents)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-md": "bin/ai-md.js"
|
|
7
7
|
},
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"bin/",
|
|
10
10
|
"lib/",
|
|
11
11
|
"scripts/",
|
|
12
|
+
"docs/",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -22,14 +23,16 @@
|
|
|
22
23
|
"@toon-format/toon": "^2.3.1"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
26
|
+
"axi",
|
|
25
27
|
"cursor",
|
|
26
|
-
"
|
|
28
|
+
"claude",
|
|
29
|
+
"agents",
|
|
27
30
|
"skills",
|
|
28
31
|
"rules",
|
|
29
|
-
"
|
|
30
|
-
"grok",
|
|
32
|
+
"personal-config",
|
|
31
33
|
"cli",
|
|
32
|
-
"
|
|
34
|
+
"toon",
|
|
35
|
+
"ai-md"
|
|
33
36
|
],
|
|
34
37
|
"author": "dujavi",
|
|
35
38
|
"license": "MIT",
|
package/scripts/sync-config.sh
CHANGED
|
@@ -22,7 +22,7 @@ Commands:
|
|
|
22
22
|
status Show repo state, symlink health, and rule/skill counts
|
|
23
23
|
doctor Diagnose problems; with --fix, repair symlinks
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Private scripts: ai-md script <name> (from ~/.ai-md/scripts/)
|
|
26
26
|
|
|
27
27
|
Options:
|
|
28
28
|
-m, --message <msg> Commit message for push (default: generic update)
|
|
@@ -146,7 +146,7 @@ cmd_install() {
|
|
|
146
146
|
log "Install complete."
|
|
147
147
|
log " rules: $(count_rules)"
|
|
148
148
|
log " skills: $(count_skills)"
|
|
149
|
-
log "Next: ai-md
|
|
149
|
+
log "Next: ai-md script <name> # private ~/.ai-md/scripts/"
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
cmd_pull() {
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Ensure Grok Build CLI + quota-axi are installed on this machine.
|
|
3
|
-
# Safe to re-run (idempotent). Does not print secrets.
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
DRY_RUN=0
|
|
7
|
-
|
|
8
|
-
usage() {
|
|
9
|
-
cat <<'EOF'
|
|
10
|
-
Usage: ensure-agent-tools.sh [--dry-run]
|
|
11
|
-
|
|
12
|
-
Installs or updates:
|
|
13
|
-
- grok (Grok Build CLI) via https://x.ai/cli/install.sh
|
|
14
|
-
- quota-axi via volta (if present) or npm -g
|
|
15
|
-
|
|
16
|
-
Then prints version + auth/quota smoke checks (no secrets).
|
|
17
|
-
EOF
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
log() { printf '%s\n' "$*"; }
|
|
21
|
-
err() { printf 'error: %s\n' "$*" >&2; }
|
|
22
|
-
run() {
|
|
23
|
-
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
24
|
-
log "dry-run: $*"
|
|
25
|
-
else
|
|
26
|
-
"$@"
|
|
27
|
-
fi
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
while [[ $# -gt 0 ]]; do
|
|
31
|
-
case "$1" in
|
|
32
|
-
--dry-run) DRY_RUN=1; shift ;;
|
|
33
|
-
-h|--help|help) usage; exit 0 ;;
|
|
34
|
-
*) err "unknown argument: $1"; usage; exit 1 ;;
|
|
35
|
-
esac
|
|
36
|
-
done
|
|
37
|
-
|
|
38
|
-
ensure_path_hint() {
|
|
39
|
-
local need=0
|
|
40
|
-
case ":$PATH:" in
|
|
41
|
-
*":$HOME/.local/bin:"*) ;;
|
|
42
|
-
*) need=1; log "note: add \$HOME/.local/bin to PATH" ;;
|
|
43
|
-
esac
|
|
44
|
-
case ":$PATH:" in
|
|
45
|
-
*":$HOME/.grok/bin:"*) ;;
|
|
46
|
-
*) need=1; log "note: add \$HOME/.grok/bin to PATH (grok installer usually does)" ;;
|
|
47
|
-
esac
|
|
48
|
-
return 0
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
install_grok() {
|
|
52
|
-
if command -v grok >/dev/null 2>&1; then
|
|
53
|
-
log "grok present: $(grok --version 2>/dev/null || echo unknown)"
|
|
54
|
-
log "Updating grok via official installer…"
|
|
55
|
-
else
|
|
56
|
-
log "Installing grok via official installer…"
|
|
57
|
-
fi
|
|
58
|
-
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
59
|
-
log "dry-run: curl -fsSL https://x.ai/cli/install.sh | bash"
|
|
60
|
-
return 0
|
|
61
|
-
fi
|
|
62
|
-
curl -fsSL https://x.ai/cli/install.sh | bash
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
install_quota_axi() {
|
|
66
|
-
if command -v quota-axi >/dev/null 2>&1; then
|
|
67
|
-
log "quota-axi present: $(quota-axi --version 2>/dev/null || echo unknown)"
|
|
68
|
-
else
|
|
69
|
-
log "Installing quota-axi…"
|
|
70
|
-
fi
|
|
71
|
-
if command -v volta >/dev/null 2>&1; then
|
|
72
|
-
run volta install quota-axi
|
|
73
|
-
elif command -v npm >/dev/null 2>&1; then
|
|
74
|
-
run npm install -g quota-axi
|
|
75
|
-
else
|
|
76
|
-
err "Need volta or npm to install quota-axi"
|
|
77
|
-
return 1
|
|
78
|
-
fi
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
smoke() {
|
|
82
|
-
log ""
|
|
83
|
-
log "=== smoke checks ==="
|
|
84
|
-
if command -v grok >/dev/null 2>&1; then
|
|
85
|
-
log "grok: $(command -v grok)"
|
|
86
|
-
grok --version 2>&1 || true
|
|
87
|
-
if [[ -f "$HOME/.grok/auth.json" ]]; then
|
|
88
|
-
log "grok auth: ~/.grok/auth.json present"
|
|
89
|
-
else
|
|
90
|
-
log "grok auth: missing — run \`grok\` (browser) or set XAI_API_KEY"
|
|
91
|
-
fi
|
|
92
|
-
else
|
|
93
|
-
err "grok not on PATH after install"
|
|
94
|
-
fi
|
|
95
|
-
|
|
96
|
-
if command -v quota-axi >/dev/null 2>&1; then
|
|
97
|
-
log "quota-axi: $(command -v quota-axi)"
|
|
98
|
-
quota-axi auth 2>&1 || true
|
|
99
|
-
quota-axi --provider grok 2>&1 || true
|
|
100
|
-
else
|
|
101
|
-
err "quota-axi not on PATH after install"
|
|
102
|
-
fi
|
|
103
|
-
|
|
104
|
-
if ! command -v sqlite3 >/dev/null 2>&1; then
|
|
105
|
-
log "note: sqlite3 not found — Cursor quota via quota-axi may fail (install sqlite3)"
|
|
106
|
-
fi
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
ensure_path_hint
|
|
110
|
-
install_grok
|
|
111
|
-
install_quota_axi
|
|
112
|
-
smoke
|
|
113
|
-
log ""
|
|
114
|
-
log "Done. Re-run anytime; pair with: ai-md install"
|