@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/lib/status.js
CHANGED
|
@@ -12,9 +12,16 @@ const {
|
|
|
12
12
|
countSkills,
|
|
13
13
|
listSkillNames,
|
|
14
14
|
listRuleNames,
|
|
15
|
-
symlinkState,
|
|
16
|
-
agentSkillTargets,
|
|
17
15
|
} = require("./config");
|
|
16
|
+
const { linkState } = require("./link");
|
|
17
|
+
const { distRoot, distDirty, sharedRoots } = require("./build");
|
|
18
|
+
const {
|
|
19
|
+
selectHarnesses,
|
|
20
|
+
resolveHarness,
|
|
21
|
+
isHarnessInstalled,
|
|
22
|
+
} = require("./harnesses");
|
|
23
|
+
const { isWsl } = require("./config-paths");
|
|
24
|
+
const { SKELETON_VERSION } = require("./skeleton");
|
|
18
25
|
|
|
19
26
|
function git(repo, args) {
|
|
20
27
|
try {
|
|
@@ -60,9 +67,7 @@ function collectProjects(cfg, { full = false, from = "base" } = {}) {
|
|
|
60
67
|
const tmplDir = templatePath(cfg, from);
|
|
61
68
|
if (!fs.existsSync(cfg.projectsDir)) return projects;
|
|
62
69
|
for (const name of fs.readdirSync(cfg.projectsDir).sort()) {
|
|
63
|
-
if (name.startsWith(".")) continue;
|
|
64
|
-
// legacy leftover
|
|
65
|
-
if (name === "template") continue;
|
|
70
|
+
if (name.startsWith(".") || name === "template") continue;
|
|
66
71
|
const projectDir = path.join(cfg.projectsDir, name);
|
|
67
72
|
if (!fs.statSync(projectDir).isDirectory()) continue;
|
|
68
73
|
const drift = templateDrift(projectDir, tmplDir);
|
|
@@ -83,25 +88,50 @@ function collectProjects(cfg, { full = false, from = "base" } = {}) {
|
|
|
83
88
|
function collectStatus(opts = {}) {
|
|
84
89
|
const cfg = resolveConfig();
|
|
85
90
|
const full = Boolean(opts.full);
|
|
86
|
-
const agents = opts.agents || ["cursor"];
|
|
91
|
+
const agents = opts.agents || cfg.agents || ["cursor"];
|
|
87
92
|
const from = opts.from || "base";
|
|
88
93
|
|
|
89
94
|
const migration = migrateLegacyTemplate(cfg);
|
|
90
|
-
|
|
91
95
|
const exists = fs.existsSync(cfg.dir);
|
|
92
96
|
const isGit = exists && fs.existsSync(path.join(cfg.dir, ".git"));
|
|
97
|
+
const shared = sharedRoots(cfg);
|
|
93
98
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const { selected, skipped, warnings } = selectHarnesses(cfg, agents, {
|
|
100
|
+
forceLink: false,
|
|
101
|
+
includeUninstalled: true,
|
|
102
|
+
});
|
|
98
103
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
const links = [];
|
|
105
|
+
const harnesses = [];
|
|
106
|
+
for (const h of selected) {
|
|
107
|
+
const installed = isHarnessInstalled(h);
|
|
108
|
+
const dist = distRoot(cfg, h.distId);
|
|
109
|
+
const dirty = distDirty(dist);
|
|
110
|
+
const entry = {
|
|
111
|
+
id: h.id,
|
|
112
|
+
distId: h.distId,
|
|
113
|
+
installed,
|
|
114
|
+
aliasOf: h.aliasOf,
|
|
115
|
+
format: h.format,
|
|
116
|
+
distDirty: dirty.dirty,
|
|
117
|
+
...(full && dirty.dirty ? { dirtyFiles: dirty.files } : {}),
|
|
118
|
+
};
|
|
119
|
+
if (installed) {
|
|
120
|
+
if (h.skills) {
|
|
121
|
+
const st = linkState(h.skills, path.join(dist, "skills"));
|
|
122
|
+
links.push({ harness: h.id, kind: "skills", ...st });
|
|
123
|
+
entry.skillsLink = st.state;
|
|
124
|
+
}
|
|
125
|
+
if (h.rules && h.format !== "skills-only") {
|
|
126
|
+
const st = linkState(h.rules, path.join(dist, "rules"));
|
|
127
|
+
links.push({ harness: h.id, kind: "rules", ...st });
|
|
128
|
+
entry.rulesLink = st.state;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
entry.skillsLink = "skipped_not_installed";
|
|
132
|
+
}
|
|
133
|
+
harnesses.push(entry);
|
|
134
|
+
}
|
|
105
135
|
|
|
106
136
|
const templates = collectTemplates(cfg, { full });
|
|
107
137
|
const projects = collectProjects(cfg, { full, from });
|
|
@@ -124,44 +154,67 @@ function collectStatus(opts = {}) {
|
|
|
124
154
|
const problems = [];
|
|
125
155
|
if (!exists) problems.push("ai_md_missing");
|
|
126
156
|
else if (!isGit) problems.push("ai_md_not_git");
|
|
157
|
+
if (fs.existsSync(path.join(cfg.dir, "rules")) || fs.existsSync(path.join(cfg.dir, "skills"))) {
|
|
158
|
+
problems.push("legacy_flat_layout");
|
|
159
|
+
}
|
|
127
160
|
for (const l of links) {
|
|
128
|
-
if (l.state !== "ok"
|
|
161
|
+
if (l.state !== "ok" && l.state !== "directory") {
|
|
162
|
+
problems.push(`${l.harness}_${l.kind}_${l.state}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const h of harnesses) {
|
|
166
|
+
if (h.installed && h.distDirty) problems.push(`dist_dirty_${h.distId}`);
|
|
129
167
|
}
|
|
130
168
|
if (!fs.existsSync(templatePath(cfg, "base")) && templates.length === 0) {
|
|
131
169
|
problems.push("templates_missing");
|
|
132
170
|
}
|
|
133
171
|
|
|
172
|
+
let skeletonVersion = null;
|
|
173
|
+
try {
|
|
174
|
+
skeletonVersion = Number(
|
|
175
|
+
fs.readFileSync(path.join(cfg.dir, ".ai-md-skeleton-version"), "utf8").trim()
|
|
176
|
+
);
|
|
177
|
+
} catch {
|
|
178
|
+
/* none */
|
|
179
|
+
}
|
|
180
|
+
if (skeletonVersion != null && skeletonVersion < SKELETON_VERSION) {
|
|
181
|
+
problems.push("skeleton_outdated");
|
|
182
|
+
}
|
|
183
|
+
|
|
134
184
|
return {
|
|
135
185
|
generatedAt: new Date().toISOString(),
|
|
136
186
|
dir: cfg.dir,
|
|
137
187
|
remote: remote || cfg.remote,
|
|
138
188
|
sources: cfg.sources,
|
|
189
|
+
linkMode: cfg.linkMode,
|
|
190
|
+
wsl: isWsl(),
|
|
139
191
|
machineConfigPath: cfg.machineConfigPath,
|
|
140
192
|
machineConfig: cfg.machineConfig,
|
|
141
193
|
branch,
|
|
142
194
|
dirty,
|
|
143
195
|
statusLine: aheadBehind,
|
|
144
196
|
layout: {
|
|
145
|
-
|
|
197
|
+
shared: { rules: shared.rules, skills: shared.skills },
|
|
198
|
+
agents: cfg.agentsDir,
|
|
199
|
+
dist: cfg.distDir,
|
|
146
200
|
templates: cfg.templatesDir,
|
|
147
201
|
projects: cfg.projectsDir,
|
|
148
202
|
},
|
|
149
203
|
migration,
|
|
150
204
|
counts: {
|
|
151
|
-
rules: countRules(
|
|
152
|
-
skills: countSkills(
|
|
205
|
+
rules: countRules(shared.rules),
|
|
206
|
+
skills: countSkills(shared.skills),
|
|
153
207
|
templates: templates.length,
|
|
154
208
|
projects: projects.length,
|
|
155
209
|
drifting: drifting.length,
|
|
156
210
|
problems: problems.length,
|
|
157
211
|
},
|
|
212
|
+
harnesses,
|
|
213
|
+
skipped,
|
|
214
|
+
warnings,
|
|
158
215
|
links: links.map((l) => ({
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
...(full ? { target: l.target, expected: l.expected } : {}),
|
|
162
|
-
})),
|
|
163
|
-
agentLinks: agentLinks.map((l) => ({
|
|
164
|
-
agent: l.agent,
|
|
216
|
+
harness: l.harness,
|
|
217
|
+
kind: l.kind,
|
|
165
218
|
path: l.path,
|
|
166
219
|
state: l.state,
|
|
167
220
|
...(full ? { target: l.target, expected: l.expected } : {}),
|
|
@@ -169,6 +222,8 @@ function collectStatus(opts = {}) {
|
|
|
169
222
|
templates,
|
|
170
223
|
projects,
|
|
171
224
|
driftFrom: from,
|
|
225
|
+
skeletonVersion,
|
|
226
|
+
packageSkeletonVersion: SKELETON_VERSION,
|
|
172
227
|
problems,
|
|
173
228
|
};
|
|
174
229
|
}
|
|
@@ -177,7 +232,12 @@ function statusHelp(data) {
|
|
|
177
232
|
const help = [];
|
|
178
233
|
if (data.problems.includes("ai_md_missing") || data.problems.includes("ai_md_not_git")) {
|
|
179
234
|
help.push(
|
|
180
|
-
"Run `ai-md
|
|
235
|
+
"Run `ai-md init` (no remote) or `ai-md setup --remote <git-url>`"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (data.problems.includes("legacy_flat_layout")) {
|
|
239
|
+
help.push(
|
|
240
|
+
"Migrate flat rules/skills into shared/ (and agents/<id>/); see package README"
|
|
181
241
|
);
|
|
182
242
|
}
|
|
183
243
|
if (!data.machineConfig) {
|
|
@@ -185,23 +245,38 @@ function statusHelp(data) {
|
|
|
185
245
|
"Persist remote/dir with `ai-md setup --remote <url>` or `ai-md config set --remote <url> --dir ~/.ai-md`"
|
|
186
246
|
);
|
|
187
247
|
}
|
|
188
|
-
if (data.links.some((l) => l.state !== "ok")) {
|
|
189
|
-
help.push("Run `ai-md doctor --fix` to
|
|
248
|
+
if (data.links.some((l) => l.state !== "ok" && l.state !== "directory")) {
|
|
249
|
+
help.push("Run `ai-md doctor --fix` to rebuild dist and repair links");
|
|
250
|
+
}
|
|
251
|
+
if (data.problems.some((p) => p.startsWith("dist_dirty_"))) {
|
|
252
|
+
help.push("Run `ai-md rescue --agents <id>` or `ai-md build --force`");
|
|
253
|
+
}
|
|
254
|
+
if (data.problems.includes("skeleton_outdated")) {
|
|
255
|
+
help.push("Run `ai-md seed-skeleton` to add new recommended files");
|
|
256
|
+
}
|
|
257
|
+
if (data.wsl) {
|
|
258
|
+
help.push(
|
|
259
|
+
"WSL detected: use Windows ai-md for Windows Cursor (homes differ)"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (data.skipped && data.skipped.some((s) => s.reason === "not_installed")) {
|
|
263
|
+
help.push(
|
|
264
|
+
"Some harnesses skipped (AI not installed). Install the tool or use --force-link"
|
|
265
|
+
);
|
|
190
266
|
}
|
|
191
267
|
if (data.problems.includes("templates_missing")) {
|
|
192
|
-
help.push("
|
|
268
|
+
help.push("Run `ai-md seed-skeleton` or add templates/base");
|
|
193
269
|
}
|
|
194
270
|
if (data.counts.drifting > 0) {
|
|
195
271
|
help.push(
|
|
196
|
-
`Run \`ai-md apply-template --project <name> --from ${data.driftFrom || "base"}
|
|
272
|
+
`Run \`ai-md apply-template --project <name> --from ${data.driftFrom || "base"}\``
|
|
197
273
|
);
|
|
198
274
|
}
|
|
199
275
|
if (data.dirty) {
|
|
200
|
-
help.push('Run `ai-md push -m "<why>"` to commit and push
|
|
276
|
+
help.push('Run `ai-md push -m "<why>"` to commit and push');
|
|
201
277
|
} else {
|
|
202
|
-
help.push("Edit
|
|
278
|
+
help.push("Edit shared/ or agents/<id>/ only; then `ai-md build`");
|
|
203
279
|
}
|
|
204
|
-
help.push("Run `ai-md init-project --repo <path> --from base` to seed a project");
|
|
205
280
|
help.push("Run `ai-md status --json` for machine-readable output");
|
|
206
281
|
return help;
|
|
207
282
|
}
|
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.6.0",
|
|
4
|
+
"description": "AXI-shaped CLI for private ~/.ai-md: shared+agents build to dist, multi-harness link (Cursor, Claude, agents, …)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-md": "bin/ai-md.js"
|
|
7
7
|
},
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"bin/",
|
|
10
10
|
"lib/",
|
|
11
11
|
"scripts/",
|
|
12
|
+
"skeleton/",
|
|
13
|
+
"docs/",
|
|
12
14
|
"README.md",
|
|
13
15
|
"LICENSE"
|
|
14
16
|
],
|
|
@@ -16,20 +18,22 @@
|
|
|
16
18
|
"node": ">=18"
|
|
17
19
|
},
|
|
18
20
|
"scripts": {
|
|
19
|
-
"test": "node bin/ai-md.js --help && node bin/ai-md.js status --json >/dev/null"
|
|
21
|
+
"test": "node bin/ai-md.js --help && node bin/ai-md.js harness list --json >/dev/null && node bin/ai-md.js status --json >/dev/null"
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"@toon-format/toon": "^2.3.1"
|
|
23
25
|
},
|
|
24
26
|
"keywords": [
|
|
27
|
+
"axi",
|
|
25
28
|
"cursor",
|
|
26
|
-
"
|
|
29
|
+
"claude",
|
|
30
|
+
"agents",
|
|
27
31
|
"skills",
|
|
28
32
|
"rules",
|
|
29
|
-
"
|
|
30
|
-
"grok",
|
|
33
|
+
"personal-config",
|
|
31
34
|
"cli",
|
|
32
|
-
"
|
|
35
|
+
"toon",
|
|
36
|
+
"ai-md"
|
|
33
37
|
],
|
|
34
38
|
"author": "dujavi",
|
|
35
39
|
"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() {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Edit ~/.ai-md shared/ and agents/ only — never live harness dirs or dist/
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Edit source, not dist or live harness paths
|
|
7
|
+
|
|
8
|
+
`~/.ai-md` is the source of truth. Live tool dirs (`~/.cursor`, `~/.claude`, `~/.agents`, …) and `~/.ai-md/dist/` are **build outputs**.
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Edit `~/.ai-md/shared/` for rules/skills that apply to all harnesses.
|
|
13
|
+
- Edit `~/.ai-md/agents/<harness>/` for harness-specific rules/skills.
|
|
14
|
+
- After edits: `ai-md build` (or `ai-md pull` / `install`, which rebuild).
|
|
15
|
+
|
|
16
|
+
## Do not
|
|
17
|
+
|
|
18
|
+
- Edit `~/.cursor/{rules,skills}`, `~/.claude/…`, `~/.agents/…`, or any other live harness path.
|
|
19
|
+
- Edit `~/.ai-md/dist/` directly.
|
|
20
|
+
|
|
21
|
+
## If you already edited a live path
|
|
22
|
+
|
|
23
|
+
Stop. Run `ai-md rescue --agents <harness>` to promote dirty files into `agents/<harness>/`, then review and `ai-md build`. Do not keep iterating under dist or live dirs.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Sync global personal skills and rules via ~/.ai-md and the ai-md CLI (build → dist → link)
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Personal skills and rules — ai-md
|
|
7
|
+
|
|
8
|
+
**`~/.ai-md`** is the private source of truth. Use **`ai-md`** (`@dujavi/ai-md`) — not Cursor Sync / Gist.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
| Path | Role |
|
|
13
|
+
|------|------|
|
|
14
|
+
| `shared/rules`, `shared/skills` | System base for all harnesses |
|
|
15
|
+
| `agents/<id>/` | Harness-specific overlays |
|
|
16
|
+
| `dist/<id>/` | Build output (gitignored) — do not edit |
|
|
17
|
+
| `templates/<type>/` | Project-type starters |
|
|
18
|
+
| `projects/<name>/` | Per-app overlays |
|
|
19
|
+
| `scripts/` | Private machine scripts |
|
|
20
|
+
|
|
21
|
+
See also `edit-source-not-dist.mdc` and skill `ai-md-config`.
|
|
22
|
+
|
|
23
|
+
## Sync
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ai-md pull
|
|
27
|
+
# edit shared/ or agents/<id>/
|
|
28
|
+
ai-md build
|
|
29
|
+
ai-md push -m "why"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`install` / `pull` / `doctor --fix` rebuild and link **only for installed AIs** (detect `~/.cursor`, `claude` on PATH, etc.). Use `--force-link` to override.
|
|
33
|
+
|
|
34
|
+
## Where to edit
|
|
35
|
+
|
|
36
|
+
| What | Edit here |
|
|
37
|
+
|------|-----------|
|
|
38
|
+
| Cross-harness rules/skills | `~/.ai-md/shared/` |
|
|
39
|
+
| Cursor-only (e.g. Grok CLI policy) | `~/.ai-md/agents/cursor/` |
|
|
40
|
+
| Project starters | `~/.ai-md/templates/<type>/` |
|
|
41
|
+
| App overlays | `~/.ai-md/projects/<repo>/` |
|
|
42
|
+
|
|
43
|
+
Never edit `~/.cursor/skills-cursor/`, live harness dirs, or `dist/`.
|
|
44
|
+
|
|
45
|
+
## Agent behavior
|
|
46
|
+
|
|
47
|
+
- Global changes → `shared/` or `agents/<id>/`; remind `ai-md build` then `push`.
|
|
48
|
+
- Symlink / dist issues → `ai-md doctor --fix` or `ai-md rescue`.
|
|
49
|
+
- New harness → `ai-md harness set …` then content under `agents/<id>/`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-md-config
|
|
3
|
+
description: >-
|
|
4
|
+
Manage personal AI config in ~/.ai-md: shared vs agents layers, build/dist,
|
|
5
|
+
harness registration, rescue dirty dist. Use when adding rules/skills, fixing
|
|
6
|
+
links, or supporting a new AI tool.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ai-md config playbook
|
|
10
|
+
|
|
11
|
+
## Layout
|
|
12
|
+
|
|
13
|
+
| Path | Role |
|
|
14
|
+
|------|------|
|
|
15
|
+
| `shared/rules`, `shared/skills` | All harnesses |
|
|
16
|
+
| `agents/<id>/rules`, `agents/<id>/skills` | One harness (overlay wins on name conflict) |
|
|
17
|
+
| `dist/<id>/` | Generated — never edit |
|
|
18
|
+
| `projects/<name>/` | Per-repo overlays (`.cursor` → here) |
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ai-md pull / push -m "why"
|
|
24
|
+
ai-md build [--agents cursor,claude] [--force]
|
|
25
|
+
ai-md rescue --agents cursor
|
|
26
|
+
ai-md harness list | show <id>
|
|
27
|
+
ai-md harness set <id> --skills ~/path [--rules ~/path] [--format mdc|md|skills-only]
|
|
28
|
+
ai-md harness enable|disable <id>
|
|
29
|
+
ai-md init | seed-skeleton
|
|
30
|
+
ai-md doctor --fix
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Add a rule or skill
|
|
34
|
+
|
|
35
|
+
1. Choose layer: `shared/` (all tools) vs `agents/<id>/` (one tool).
|
|
36
|
+
2. Write the file under that path (never under `dist/` or `~/.cursor/…`).
|
|
37
|
+
3. `ai-md build`
|
|
38
|
+
4. `ai-md push -m "…"` when ready.
|
|
39
|
+
|
|
40
|
+
## New harness
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ai-md harness set my-tool --skills ~/.my-tool/skills --rules ~/.my-tool/rules --format md
|
|
44
|
+
# put tool-specific content in agents/my-tool/
|
|
45
|
+
ai-md build --agents my-tool
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Unique vs shared `.agents`
|
|
49
|
+
|
|
50
|
+
- Unique live roots: `cursor`, `claude`, `gemini`, `opencode`, `copilot`
|
|
51
|
+
- Shared `~/.agents/skills`: `agents` (canonical writer); `codex` is an alias (no second emit)
|
|
File without changes
|
|
File without changes
|
|
@@ -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"
|