the-grimoire-cli 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/.agents/NAVIGATOR.md +9 -4
- package/.agents/VERSION +1 -1
- package/.agents/stack/README.md +3 -1
- package/.agents/tooling.json +2 -1
- package/bin/grimoire.mjs +48 -11
- package/package.json +1 -1
package/.agents/NAVIGATOR.md
CHANGED
|
@@ -108,10 +108,15 @@ never edit `.agents/` in a consuming project — restate it in `local/` (loads l
|
|
|
108
108
|
|
|
109
109
|
### `bootstrap` — wire plugins / MCP / skills
|
|
110
110
|
Reads base `tooling.json` ∪ `local/tooling.json` (base wins on key conflict; local adds new keys).
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
For every missing plugin it prints the **paste-in-Claude** install command (`/plugin marketplace add
|
|
112
|
+
<source> && /plugin install <name>@<marketplace>`) — the CLI can only flip the enable flag, it cannot
|
|
113
|
+
register a marketplace or run a slash command.
|
|
114
|
+
- **Interactive** (default, in a terminal): asks `enable <plugin>? [y/N]` per missing plugin, then
|
|
115
|
+
enables the chosen ones in `~/.claude/settings.json` and merges MCP servers.
|
|
116
|
+
- **Dry-run** (no TTY — CI/piped, and the `init`-embedded preview): prints the plan, writes nothing.
|
|
117
|
+
- `--apply`: non-interactive enable-all — enables every missing plugin in `~/.claude/settings.json`
|
|
118
|
+
(backs it up first, only **adds**, never disables), and merges MCP servers into `.mcp.json` (never
|
|
119
|
+
clobbers an existing server). Flags unresolved `${ENV}` placeholders.
|
|
115
120
|
|
|
116
121
|
### `index` — regenerate per-folder `INDEX.md`
|
|
117
122
|
One-line-per-file table built from each file's frontmatter `description:` or its H1 + first sentence.
|
package/.agents/VERSION
CHANGED
package/.agents/stack/README.md
CHANGED
|
@@ -60,7 +60,9 @@ simplify away" guardrail, and the `ponytail:` shortcut marker — tool-agnostic,
|
|
|
60
60
|
Codex, OpenCode, Gemini, pi); install is per-machine, optional.
|
|
61
61
|
|
|
62
62
|
- **Install** (Claude Code): `/plugin marketplace add DietrichGebert/ponytail` then
|
|
63
|
-
`/plugin install ponytail@ponytail`. Other hosts: see the ponytail README.
|
|
63
|
+
`/plugin install ponytail@ponytail`. Other hosts: see the ponytail README. ponytail is declared in
|
|
64
|
+
`tooling.json` (with a `source` field = the marketplace repo), so `grimoire bootstrap` lists it and
|
|
65
|
+
prints exactly this paste command.
|
|
64
66
|
- **`/ponytail-review`** — review the current diff for over-engineering; hands back a delete-list.
|
|
65
67
|
- **`/ponytail-audit`** — same, whole repo instead of the diff.
|
|
66
68
|
- **`/ponytail-debt`** — harvest the `ponytail:` shortcut markers into a ledger so "later" isn't
|
package/.agents/tooling.json
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
{ "name": "ui-ux-pro-max", "marketplace": "ui-ux-pro-max-skill", "scope": "user" },
|
|
7
7
|
{ "name": "andrej-karpathy-skills", "marketplace": "karpathy-skills", "scope": "user" },
|
|
8
8
|
{ "name": "pordee", "marketplace": "pordee", "scope": "user" },
|
|
9
|
-
{ "name": "caveman", "marketplace": "caveman", "scope": "user" }
|
|
9
|
+
{ "name": "caveman", "marketplace": "caveman", "scope": "user" },
|
|
10
|
+
{ "name": "ponytail", "marketplace": "ponytail", "scope": "user", "source": "DietrichGebert/ponytail" }
|
|
10
11
|
],
|
|
11
12
|
"skills": [
|
|
12
13
|
{ "name": "mattpocock", "install": "npx skills@latest add mattpocock/skills", "setup": "/setup-matt-pocock-skills", "source": "https://github.com/mattpocock/skills", "scope": "user" },
|
package/bin/grimoire.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const TEMPLATE_ROOT = path.resolve(__dirname, "..");
|
|
@@ -82,6 +83,25 @@ function applyPlugins(sp, settings, missing) {
|
|
|
82
83
|
fs.writeFileSync(sp, JSON.stringify(settings, null, 2) + "\n");
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// The Claude `/plugin …` commands the user pastes to actually install a plugin: enabling the flag
|
|
87
|
+
// in settings.json only works once its marketplace is registered, and the CLI can't run a slash
|
|
88
|
+
// command. Official-marketplace plugins need no `marketplace add`; a custom one needs its `source`
|
|
89
|
+
// repo (omit the add line when we don't know it).
|
|
90
|
+
function pluginInstallHint(pl) {
|
|
91
|
+
const key = pluginKey(pl);
|
|
92
|
+
if (pl.marketplace === "claude-plugins-official") return `/plugin install ${key}`;
|
|
93
|
+
if (pl.source) return `/plugin marketplace add ${pl.source} && /plugin install ${key}`;
|
|
94
|
+
return `/plugin install ${key} (add its marketplace first)`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ponytail: TTY-only y/N prompt; callers must guard on process.stdin.isTTY so CI never blocks here.
|
|
98
|
+
function promptYesNo(question) {
|
|
99
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
rl.question(`${question} [y/N] `, (a) => { rl.close(); resolve(/^y(es)?$/i.test(a.trim())); });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
// Collect unresolved ${ENV} placeholders anywhere in a server definition (env for stdio servers,
|
|
86
106
|
// headers/url for http servers, etc.). Recurses over strings so transport shape does not matter.
|
|
87
107
|
function unresolvedEnv(node, out) {
|
|
@@ -301,7 +321,9 @@ function init({ dir }) {
|
|
|
301
321
|
log(" project-owned (seeded if absent): codex/ journal/ local/");
|
|
302
322
|
log(" next: set the active stack profile + testing policy in local/AGENTS.local.md");
|
|
303
323
|
|
|
304
|
-
bootstrap
|
|
324
|
+
// init only previews; `grimoire bootstrap` does the interactive install. The dry-run path has no
|
|
325
|
+
// await today, but .catch keeps a future async step from becoming a silent unhandled rejection.
|
|
326
|
+
bootstrap({ dir, apply: false, prompt: false }).catch((e) => fail(e.message));
|
|
305
327
|
}
|
|
306
328
|
|
|
307
329
|
function sync({ dir }) {
|
|
@@ -336,24 +358,39 @@ function sync({ dir }) {
|
|
|
336
358
|
log(" tooling.json may have changed; run `grimoire bootstrap` to apply plugin/MCP updates.");
|
|
337
359
|
}
|
|
338
360
|
|
|
339
|
-
function bootstrap({ dir, apply }) {
|
|
361
|
+
async function bootstrap({ dir, apply, prompt = true }) {
|
|
340
362
|
const tooling = mergedTooling(dir); // base ∪ local/tooling.json
|
|
341
363
|
const sp = claudeSettingsPath();
|
|
342
364
|
const settings = readSettings(sp);
|
|
343
365
|
const missing = missingPlugins(tooling, settings);
|
|
366
|
+
// Interactive (per-plugin y/N) only in a real terminal; --apply forces enable-all; otherwise
|
|
367
|
+
// (CI / piped stdin / the init-embedded call) stay a dry-run so it never blocks on input.
|
|
368
|
+
const interactive = !apply && prompt && !!process.stdin.isTTY;
|
|
369
|
+
const writes = apply || interactive;
|
|
344
370
|
|
|
345
|
-
log(`grimoire bootstrap (${apply ? "apply" : "dry-run"})`);
|
|
371
|
+
log(`grimoire bootstrap (${apply ? "apply" : interactive ? "interactive" : "dry-run"})`);
|
|
346
372
|
|
|
347
373
|
if (missing.length === 0) {
|
|
348
374
|
log(" plugins: all required plugins already enabled.");
|
|
349
375
|
} else {
|
|
350
376
|
log(" plugins missing:");
|
|
351
377
|
for (const pl of missing) log(` - ${pluginKey(pl)}`);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
378
|
+
// The flag flip alone can't register a marketplace, so always show the paste-in-Claude commands.
|
|
379
|
+
log(" to install, paste in Claude Code:");
|
|
380
|
+
for (const pl of missing) log(` ${pluginInstallHint(pl)}`);
|
|
381
|
+
|
|
382
|
+
let chosen = missing; // --apply enables all; interactive narrows to the user's picks
|
|
383
|
+
if (interactive) {
|
|
384
|
+
chosen = [];
|
|
385
|
+
for (const pl of missing) if (await promptYesNo(` enable ${pluginKey(pl)}?`)) chosen.push(pl);
|
|
386
|
+
}
|
|
387
|
+
if (writes && chosen.length) {
|
|
388
|
+
applyPlugins(sp, settings, chosen);
|
|
389
|
+
log(` enabled ${chosen.length} plugin(s); backup at ${sp}.bak`);
|
|
390
|
+
} else if (interactive) {
|
|
391
|
+
log(" no plugins selected.");
|
|
392
|
+
} else if (!writes) {
|
|
393
|
+
log(` (dry-run) re-run with --apply to enable all, or run \`grimoire bootstrap\` in a terminal to choose`);
|
|
357
394
|
}
|
|
358
395
|
}
|
|
359
396
|
|
|
@@ -364,7 +401,7 @@ function bootstrap({ dir, apply }) {
|
|
|
364
401
|
}
|
|
365
402
|
}
|
|
366
403
|
|
|
367
|
-
if (
|
|
404
|
+
if (writes) {
|
|
368
405
|
const { added, needsEnv } = mergeMcp(dir, tooling);
|
|
369
406
|
if (added.length) log(` mcp: added ${added.join(", ")} to .mcp.json`);
|
|
370
407
|
else log(" mcp: all servers already present.");
|
|
@@ -607,7 +644,7 @@ function help() {
|
|
|
607
644
|
log("grimoire <command> [--dir <path>]\n");
|
|
608
645
|
log(" init scaffold .agents/ + CLAUDE.md + codex/ journal/ local/ (migrates an old layout; backs up first)");
|
|
609
646
|
log(" sync wholesale-replace the .agents/ contract from the template (codex/ journal/ local/ untouched)");
|
|
610
|
-
log(" bootstrap enable
|
|
647
|
+
log(" bootstrap enable plugins / MCP / skills — interactive y/N in a terminal, --apply enables all (dry-run otherwise)");
|
|
611
648
|
log(" index regenerate per-folder INDEX.md (--check fails on drift, for CI)");
|
|
612
649
|
log(" doctor health-check the project's wiring (exits non-zero on error, for CI)");
|
|
613
650
|
log(" --version print the release version + build sha (-v)");
|
|
@@ -620,7 +657,7 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith
|
|
|
620
657
|
switch (args.cmd) {
|
|
621
658
|
case "init": init(args); break;
|
|
622
659
|
case "sync": sync(args); break;
|
|
623
|
-
case "bootstrap": bootstrap(args); break;
|
|
660
|
+
case "bootstrap": await bootstrap(args); break;
|
|
624
661
|
case "index": index(args); break;
|
|
625
662
|
case "doctor": doctor(args); break;
|
|
626
663
|
case "--version": case "-v": version(); break;
|
package/package.json
CHANGED