@dujavi/ai-md 0.3.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.
@@ -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/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
- function resolveConfig(env = process.env) {
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 dir =
10
- env.AI_MD_DIR || env.CURSOR_MD_DIR || path.join(home, ".ai-md");
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
- env.AI_MD_REMOTE ||
13
- env.CURSOR_MD_REMOTE ||
14
- "https://github.com/dujavi/.ai-md.git";
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/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/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("Run `ai-md install` to clone AI_MD_REMOTE → ~/.ai-md and link ~/.cursor/{skills,rules}");
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.3.0",
4
- "description": "AXI-shaped CLI for ~/.ai-md system skills/rules, templates/, and projects/",
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
- "ai",
28
+ "claude",
29
+ "agents",
27
30
  "skills",
28
31
  "rules",
29
- "quota-axi",
30
- "grok",
32
+ "personal-config",
31
33
  "cli",
32
- "axi"
34
+ "toon",
35
+ "ai-md"
33
36
  ],
34
37
  "author": "dujavi",
35
38
  "license": "MIT",
@@ -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
- Also: ai-md ensure-tools (grok + quota-axi)
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 ensure-tools # grok + quota-axi"
149
+ log "Next: ai-md script <name> # private ~/.ai-md/scripts/"
150
150
  }
151
151
 
152
152
  cmd_pull() {