@duro-app/cli 1.1.2 → 1.3.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 CHANGED
@@ -14,6 +14,7 @@ npx @duro-app/cli spacing # token scale + the deep import to copy
14
14
  npx @duro-app/cli rules # critical rules + what the lint plugin enforces
15
15
  npx @duro-app/cli "tags that wrap" # free text falls back to a need-search
16
16
  npx @duro-app/cli list
17
+ npx @duro-app/cli hook install # wire the Claude Code SessionStart hook
17
18
  ```
18
19
 
19
20
  Install for the short bin: `pnpm add -D @duro-app/cli` → `duro Button`.
@@ -46,3 +47,43 @@ generated regions of the repo's CLAUDE.md (`--write-docs` / `--check-docs`).
46
47
 
47
48
  The CLI and `@duro-app/ui` are released in lockstep; a version mismatch with
48
49
  a locally installed ui prints a one-line stderr warning.
50
+
51
+ ## Claude Code session hook
52
+
53
+ `duro hook session-start` prints a consult-first preamble followed by the
54
+ full catalog, designed to be injected into agent context by a Claude Code
55
+ `SessionStart` hook — so "check the design system before building UI" stops
56
+ being a remembered step an agent can skip.
57
+
58
+ Every consuming repo wires it the same way, and the CLI does the wiring:
59
+
60
+ ```
61
+ npx -y @duro-app/cli hook install # wire this repo
62
+ npx -y @duro-app/cli hook install --check # CI: exit 1 if it drifted
63
+ ```
64
+
65
+ `install` is idempotent, and touches exactly three files:
66
+
67
+ | File | What install does |
68
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
69
+ | `.claude/hooks/duro-catalog.sh` | Writes the generated hook: fetches the catalog, caches it for 7 days (npx resolution dominates session start; the catalog only changes on upgrade), stays silent when offline with no cache. |
70
+ | `.claude/settings.json` | Adds the `SessionStart` entry. Existing hooks, permissions and skill overrides are preserved; a hand-renamed duro command is migrated in place rather than duplicated. Unparseable JSON is reported, never clobbered. |
71
+ | `.gitignore` | Ignores `.claude/.duro-session.cache*` — a glob, because a hook killed mid-`npx` leaves the staging `.tmp` behind. |
72
+
73
+ Do not hand-edit the generated script — `--check` is a byte comparison, so an
74
+ edit shows up as drift and the next `install` overwrites it. **Repo-specific
75
+ caveats go in `.claude/duro-hook.local.md`**: the hook appends that file after
76
+ the catalog, and regeneration leaves it alone. Use it for the things the
77
+ generic preamble can't know — local styling conventions, what NOT to convert,
78
+ which packages are off-limits:
79
+
80
+ ```markdown
81
+ This repo specifically (see CLAUDE.md): the raw-markup convention licenses
82
+ raw div/span MARKUP around Duro components — it is NOT a license to
83
+ hand-roll widgets the DS ships.
84
+ ```
85
+
86
+ Wire `--check` into the repo's lint or CI job to catch a stale hook after a
87
+ CLI upgrade. The generated script pins `@duro-app/cli@^1.2.0` (the floor where
88
+ `hook session-start` landed; older versions treat `hook` as a lookup and cache
89
+ junk) — a floating pin, so patch releases never read as drift.
package/dist/bin.js CHANGED
@@ -3,6 +3,7 @@ import { parseArgs } from 'node:util';
3
3
  import { loadRegistry } from './registry.js';
4
4
  import { runLookup } from './commands/lookup.js';
5
5
  import { runList } from './commands/list.js';
6
+ import { runHook } from './commands/hook.js';
6
7
  import { runManifest, cliVersion } from './commands/manifest.js';
7
8
  function emit(result, json) {
8
9
  if (json) {
@@ -14,7 +15,7 @@ function emit(result, json) {
14
15
  process.exit(result.exitCode ?? 0);
15
16
  }
16
17
  function usageError(message) {
17
- process.stderr.write(`${message}\nUsage: duro <name|list|manifest|mcp> [flags] — duro manifest for details\n`);
18
+ process.stderr.write(`${message}\nUsage: duro <name|list|manifest|hook|mcp> [flags] — duro manifest for details\n`);
18
19
  process.exit(2);
19
20
  }
20
21
  async function main() {
@@ -28,6 +29,7 @@ async function main() {
28
29
  props: { type: 'boolean', default: false },
29
30
  'source-only': { type: 'boolean', default: false },
30
31
  'no-color': { type: 'boolean', default: false },
32
+ check: { type: 'boolean', default: false },
31
33
  help: { type: 'boolean', short: 'h', default: false },
32
34
  version: { type: 'boolean', short: 'v', default: false },
33
35
  },
@@ -70,6 +72,11 @@ async function main() {
70
72
  usageError('duro list takes at most one kind');
71
73
  emit(runList(registry, rest[0]), values.json);
72
74
  }
75
+ if (first === 'hook') {
76
+ if (rest.length > 1)
77
+ usageError('duro hook takes exactly one event');
78
+ emit(runHook(registry, rest[0], { check: values.check }), values.json);
79
+ }
73
80
  if (first === 'mcp') {
74
81
  const { runMcp } = await import('./commands/mcp.js');
75
82
  await runMcp(registry);
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Registry } from '../registry-types.js';
2
+ import type { CommandResult } from './lookup.js';
3
+ export interface HookOptions {
4
+ /** Report drift and exit 1 instead of writing. For CI. */
5
+ check?: boolean;
6
+ /** Repo root. Defaults to the process cwd. */
7
+ cwd?: string;
8
+ }
9
+ export declare function runHook(registry: Registry, event?: string, options?: HookOptions): CommandResult;
10
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/commands/hook.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAA;AA0B9C,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAID,wBAAgB,OAAO,CACrB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,WAAgB,GACxB,aAAa,CAcf"}
@@ -0,0 +1,170 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { runList } from './list.js';
5
+ import { GITIGNORE_BLOCK, HOOK_CACHE_IGNORE, HOOK_COMMAND, HOOK_NOTES_PATH, HOOK_SCRIPT, HOOK_SCRIPT_PATH, HOOK_SETTINGS_PATH, } from '../hook-script.js';
6
+ // Injected into agent context by a Claude Code SessionStart hook. The point
7
+ // is to turn "consult the design system before building UI" from a remembered
8
+ // step into ambient context: with the catalog already in front of it, an
9
+ // agent has nothing to remember and no excuse to hand-roll a widget the
10
+ // design system ships.
11
+ const PREAMBLE = [
12
+ 'Duro design-system session bootstrap (duro hook session-start).',
13
+ 'The catalog below is already in your context — before hand-rolling ANY',
14
+ 'interactive element (menu, badge, tag, dialog, select, tooltip, ...),',
15
+ 'pick the design-system component or recipe from this list. Details:',
16
+ 'duro <Component> (props+usage) · duro <recipe> --source-only ·',
17
+ 'duro spacing|icons|rules.',
18
+ ].join('\n');
19
+ const EVENTS = ['session-start', 'install'];
20
+ export function runHook(registry, event, options = {}) {
21
+ if (event === 'install')
22
+ return runHookInstall(options);
23
+ if (event !== 'session-start') {
24
+ return {
25
+ text: `duro hook: unknown event "${event ?? ''}" — expected ${EVENTS.join(' | ')}`,
26
+ data: { kind: 'usage-error' },
27
+ exitCode: 2,
28
+ };
29
+ }
30
+ const list = runList(registry);
31
+ return {
32
+ text: `${PREAMBLE}\n\n${list.text}`,
33
+ data: { preamble: PREAMBLE, entries: list.data },
34
+ };
35
+ }
36
+ function read(path) {
37
+ try {
38
+ return readFileSync(path, 'utf8');
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function write(path, content) {
45
+ mkdirSync(dirname(path), { recursive: true });
46
+ writeFileSync(path, content, 'utf8');
47
+ }
48
+ /**
49
+ * Add the SessionStart entry without disturbing anything else in the file:
50
+ * consuming repos keep their own hooks, permissions and skill overrides here.
51
+ * Returns null when the file is already wired correctly.
52
+ */
53
+ function nextSettings(current) {
54
+ const parsed = current === null ? {} : JSON.parse(current);
55
+ const hooks = (parsed.hooks ?? (parsed.hooks = {}));
56
+ const sessionStart = (hooks.SessionStart ?? (hooks.SessionStart = []));
57
+ const ours = sessionStart
58
+ .flatMap((group) => group.hooks ?? [])
59
+ .find((entry) => entry.command?.includes(HOOK_SCRIPT_PATH));
60
+ if (ours) {
61
+ if (ours.command === HOOK_COMMAND && ours.type === 'command')
62
+ return null;
63
+ ours.type = 'command';
64
+ ours.command = HOOK_COMMAND;
65
+ }
66
+ else {
67
+ sessionStart.push({ hooks: [{ type: 'command', command: HOOK_COMMAND }] });
68
+ }
69
+ return JSON.stringify(parsed, null, 2) + '\n';
70
+ }
71
+ /** Append the cache to .gitignore, unless some rule already covers the path. */
72
+ function nextGitignore(current) {
73
+ if (current !== null && current.split('\n').some((line) => line.trim() === HOOK_CACHE_IGNORE)) {
74
+ return null;
75
+ }
76
+ if (current === null || current === '')
77
+ return GITIGNORE_BLOCK;
78
+ return current.endsWith('\n')
79
+ ? `${current}\n${GITIGNORE_BLOCK}`
80
+ : `${current}\n\n${GITIGNORE_BLOCK}`;
81
+ }
82
+ /**
83
+ * Repos that allowlist .claude (".claude/*" plus "!" exceptions) silently
84
+ * ignore the files install just wrote — the hook then works locally and ships
85
+ * to nobody. Best-effort: no git, no warning.
86
+ */
87
+ function ignoredTrackables(root) {
88
+ const candidates = [HOOK_SCRIPT_PATH, HOOK_SETTINGS_PATH, HOOK_NOTES_PATH];
89
+ try {
90
+ const out = execFileSync('git', ['check-ignore', '--', ...candidates], {
91
+ cwd: root,
92
+ encoding: 'utf8',
93
+ stdio: ['ignore', 'pipe', 'ignore'],
94
+ });
95
+ return out.split('\n').filter(Boolean);
96
+ }
97
+ catch {
98
+ // Exit 1 means nothing matched, which is the happy path. Anything else
99
+ // (no git, not a repo yet) is not ours to report.
100
+ return [];
101
+ }
102
+ }
103
+ function runHookInstall(options) {
104
+ const root = options.cwd ?? process.cwd();
105
+ const at = (path) => join(root, path);
106
+ const changes = [];
107
+ const apply = (path, next, detail) => {
108
+ if (next === null) {
109
+ changes.push({ path, status: 'unchanged', detail: 'already up to date' });
110
+ return;
111
+ }
112
+ if (options.check) {
113
+ changes.push({ path, status: 'stale', detail });
114
+ return;
115
+ }
116
+ write(at(path), next);
117
+ changes.push({ path, status: 'written', detail });
118
+ };
119
+ const script = read(at(HOOK_SCRIPT_PATH));
120
+ apply(HOOK_SCRIPT_PATH, script === HOOK_SCRIPT ? null : HOOK_SCRIPT, script === null ? 'created' : 'regenerated');
121
+ const settingsRaw = read(at(HOOK_SETTINGS_PATH));
122
+ let settingsNext;
123
+ try {
124
+ settingsNext = nextSettings(settingsRaw);
125
+ }
126
+ catch (error) {
127
+ return {
128
+ text: `duro hook install: ${HOOK_SETTINGS_PATH} is not valid JSON (${error instanceof Error ? error.message : String(error)}) — fix or remove it, then rerun`,
129
+ data: { kind: 'usage-error' },
130
+ exitCode: 2,
131
+ };
132
+ }
133
+ apply(HOOK_SETTINGS_PATH, settingsNext, settingsRaw === null ? 'created' : 'SessionStart entry added');
134
+ const gitignoreRaw = read(at('.gitignore'));
135
+ apply('.gitignore', nextGitignore(gitignoreRaw), `ignores ${HOOK_CACHE_IGNORE}`);
136
+ const stale = changes.filter((change) => change.status === 'stale');
137
+ const lines = changes.map((change) => ` ${change.status.padEnd(9)} ${change.path} — ${change.detail}`);
138
+ if (options.check) {
139
+ return stale.length === 0
140
+ ? {
141
+ text: `duro hook install --check: up to date\n${lines.join('\n')}`,
142
+ data: { ok: true, changes },
143
+ }
144
+ : {
145
+ text: `duro hook install --check: ${stale.length} file(s) out of date\n${lines.join('\n')}\nRun: npx -y @duro-app/cli hook install`,
146
+ data: { ok: false, changes },
147
+ exitCode: 1,
148
+ };
149
+ }
150
+ const ignored = ignoredTrackables(root);
151
+ return {
152
+ text: [
153
+ 'Duro SessionStart hook installed.',
154
+ ...lines,
155
+ '',
156
+ `Repo-specific caveats: put them in ${HOOK_NOTES_PATH} — the hook appends that file`,
157
+ 'after the catalog, and regeneration leaves it alone.',
158
+ ...(ignored.length === 0
159
+ ? []
160
+ : [
161
+ '',
162
+ 'WARNING: .gitignore excludes these, so the hook would ship to nobody.',
163
+ 'Add a negation for each:',
164
+ ...ignored.map((path) => ` !${path}`),
165
+ ]),
166
+ ].join('\n'),
167
+ data: { ok: true, changes, ignored },
168
+ };
169
+ }
170
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../src/commands/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The generated Claude Code SessionStart hook, and the wiring it needs.
3
+ *
4
+ * Kept as data (not prose in a README) so `duro hook install` can write it and
5
+ * `duro hook install --check` can byte-compare it: every consuming repo runs
6
+ * the same script, and an upgrade is a regenerate rather than nine hand-edits.
7
+ */
8
+ /** Repo-relative paths the hook owns. */
9
+ export declare const HOOK_SCRIPT_PATH = ".claude/hooks/duro-catalog.sh";
10
+ export declare const HOOK_SETTINGS_PATH = ".claude/settings.json";
11
+ export declare const HOOK_CACHE_PATH = ".claude/.duro-session.cache";
12
+ /**
13
+ * Glob, not the bare path: the script stages the fetch through
14
+ * "<cache>.tmp", and a session-start hook killed mid-npx leaves that behind.
15
+ */
16
+ export declare const HOOK_CACHE_IGNORE = ".claude/.duro-session.cache*";
17
+ /** Repo-specific caveats, appended by the hook and never touched by install. */
18
+ export declare const HOOK_NOTES_PATH = ".claude/duro-hook.local.md";
19
+ /** What settings.json runs. Also the marker install matches on. */
20
+ export declare const HOOK_COMMAND = "sh .claude/hooks/duro-catalog.sh";
21
+ /**
22
+ * Floor for the npx pin. `hook session-start` landed in 1.2.0; on older
23
+ * versions the unknown "hook" positional falls through to lookup/search and
24
+ * caches junk. Raise this only when the script needs newer CLI behavior —
25
+ * a floating pin keeps patch releases from showing up as --check drift.
26
+ */
27
+ export declare const HOOK_MIN_CLI = "1.2.0";
28
+ export declare const HOOK_SCRIPT = "#!/bin/sh\n# Duro design-system session bootstrap \u2014 GENERATED by `duro hook install`.\n# Regenerate: npx -y @duro-app/cli hook install (--check verifies it in CI)\n#\n# Injects the consult-first preamble plus the component catalog into agent\n# context at SessionStart, so \"check the design system before building UI\" is\n# ambient context instead of a step an agent has to remember.\n#\n# Do not hand-edit. Repo-specific caveats go in\n# .claude/duro-hook.local.md \u2014 appended below, and left alone by regeneration.\ncache=\".claude/.duro-session.cache\"\nnotes=\".claude/duro-hook.local.md\"\n\n# npx resolution dominates session start; the catalog only changes on upgrade.\nif [ ! -s \"$cache\" ] || [ -n \"$(find \"$cache\" -mtime +7 2>/dev/null)\" ]; then\n if npx -y @duro-app/cli@^1.2.0 hook session-start >\"$cache.tmp\" 2>/dev/null; then\n mv \"$cache.tmp\" \"$cache\"\n else\n rm -f \"$cache.tmp\"\n fi\nfi\n\n# Offline with no cache yet: stay silent rather than fail the session.\n[ -s \"$cache\" ] || exit 0\ncat \"$cache\"\n\nif [ -s \"$notes\" ]; then\n echo\n cat \"$notes\"\nfi\n";
29
+ export declare const GITIGNORE_BLOCK = "# Duro session-hook cache (duro hook install)\n.claude/.duro-session.cache*\n";
30
+ //# sourceMappingURL=hook-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-script.d.ts","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,kCAAkC,CAAA;AAC/D,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AACzD,eAAO,MAAM,eAAe,gCAAgC,CAAA;AAC5D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iCAAwB,CAAA;AACtD,gFAAgF;AAChF,eAAO,MAAM,eAAe,+BAA+B,CAAA;AAE3D,mEAAmE;AACnE,eAAO,MAAM,YAAY,qCAA2B,CAAA;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,UAAU,CAAA;AAEnC,eAAO,MAAM,WAAW,omCA8BvB,CAAA;AAED,eAAO,MAAM,eAAe,kFAA0E,CAAA"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The generated Claude Code SessionStart hook, and the wiring it needs.
3
+ *
4
+ * Kept as data (not prose in a README) so `duro hook install` can write it and
5
+ * `duro hook install --check` can byte-compare it: every consuming repo runs
6
+ * the same script, and an upgrade is a regenerate rather than nine hand-edits.
7
+ */
8
+ /** Repo-relative paths the hook owns. */
9
+ export const HOOK_SCRIPT_PATH = '.claude/hooks/duro-catalog.sh';
10
+ export const HOOK_SETTINGS_PATH = '.claude/settings.json';
11
+ export const HOOK_CACHE_PATH = '.claude/.duro-session.cache';
12
+ /**
13
+ * Glob, not the bare path: the script stages the fetch through
14
+ * "<cache>.tmp", and a session-start hook killed mid-npx leaves that behind.
15
+ */
16
+ export const HOOK_CACHE_IGNORE = `${HOOK_CACHE_PATH}*`;
17
+ /** Repo-specific caveats, appended by the hook and never touched by install. */
18
+ export const HOOK_NOTES_PATH = '.claude/duro-hook.local.md';
19
+ /** What settings.json runs. Also the marker install matches on. */
20
+ export const HOOK_COMMAND = `sh ${HOOK_SCRIPT_PATH}`;
21
+ /**
22
+ * Floor for the npx pin. `hook session-start` landed in 1.2.0; on older
23
+ * versions the unknown "hook" positional falls through to lookup/search and
24
+ * caches junk. Raise this only when the script needs newer CLI behavior —
25
+ * a floating pin keeps patch releases from showing up as --check drift.
26
+ */
27
+ export const HOOK_MIN_CLI = '1.2.0';
28
+ export const HOOK_SCRIPT = `#!/bin/sh
29
+ # Duro design-system session bootstrap — GENERATED by \`duro hook install\`.
30
+ # Regenerate: npx -y @duro-app/cli hook install (--check verifies it in CI)
31
+ #
32
+ # Injects the consult-first preamble plus the component catalog into agent
33
+ # context at SessionStart, so "check the design system before building UI" is
34
+ # ambient context instead of a step an agent has to remember.
35
+ #
36
+ # Do not hand-edit. Repo-specific caveats go in
37
+ # ${HOOK_NOTES_PATH} — appended below, and left alone by regeneration.
38
+ cache="${HOOK_CACHE_PATH}"
39
+ notes="${HOOK_NOTES_PATH}"
40
+
41
+ # npx resolution dominates session start; the catalog only changes on upgrade.
42
+ if [ ! -s "$cache" ] || [ -n "$(find "$cache" -mtime +7 2>/dev/null)" ]; then
43
+ if npx -y @duro-app/cli@^${HOOK_MIN_CLI} hook session-start >"$cache.tmp" 2>/dev/null; then
44
+ mv "$cache.tmp" "$cache"
45
+ else
46
+ rm -f "$cache.tmp"
47
+ fi
48
+ fi
49
+
50
+ # Offline with no cache yet: stay silent rather than fail the session.
51
+ [ -s "$cache" ] || exit 0
52
+ cat "$cache"
53
+
54
+ if [ -s "$notes" ]; then
55
+ echo
56
+ cat "$notes"
57
+ fi
58
+ `;
59
+ export const GITIGNORE_BLOCK = `# Duro session-hook cache (duro hook install)\n${HOOK_CACHE_IGNORE}\n`;
60
+ //# sourceMappingURL=hook-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-script.js","sourceRoot":"","sources":["../src/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,+BAA+B,CAAA;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAA;AACzD,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAA;AAC5D;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,eAAe,GAAG,CAAA;AACtD,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAA;AAE3D,mEAAmE;AACnE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAA;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAA;AAEnC,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;IASvB,eAAe;SACV,eAAe;SACf,eAAe;;;;6BAIK,YAAY;;;;;;;;;;;;;;;CAexC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,kDAAkD,iBAAiB,IAAI,CAAA"}
@@ -41,6 +41,7 @@ export declare function buildManifest(registry: Registry, version: string): {
41
41
  enums: {
42
42
  names: string[];
43
43
  kinds: string[];
44
+ events: string[];
44
45
  };
45
46
  };
46
47
  //# sourceMappingURL=manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qBAAqB,CAAA;AAGjD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IACxF,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IAC7E,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAA;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,QAAQ,EAAE,WAAW,EAmEjC,CAAA;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;EAkBhE"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qBAAqB,CAAA;AAGjD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IACxF,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IAC7E,OAAO,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAAA;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,QAAQ,EAAE,WAAW,EA6FjC,CAAA;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;EAmBhE"}
package/dist/manifest.js CHANGED
@@ -53,6 +53,30 @@ export const COMMANDS = [
53
53
  examples: ['duro manifest --json'],
54
54
  mcpTool: 'duro_manifest',
55
55
  },
56
+ {
57
+ name: 'hook',
58
+ summary: 'Claude Code SessionStart hook: session-start prints the consult-first preamble + full catalog; install wires it into the current repo',
59
+ args: [
60
+ {
61
+ name: 'event',
62
+ required: true,
63
+ description: 'session-start (print the bootstrap) | install (wire it into this repo)',
64
+ valuesFrom: 'events',
65
+ },
66
+ ],
67
+ flags: [
68
+ {
69
+ name: 'check',
70
+ type: 'boolean',
71
+ description: 'install: report drift and exit 1 instead of writing (for CI)',
72
+ },
73
+ ],
74
+ returns: {
75
+ shape: '{preamble: string, entries: ListEntry[]} | {ok: boolean, changes: FileChange[]}',
76
+ description: 'session-start: the preamble plus the same entries as duro list. install: what each wired file did',
77
+ },
78
+ examples: ['duro hook session-start', 'duro hook install', 'duro hook install --check'],
79
+ },
56
80
  {
57
81
  name: 'mcp',
58
82
  summary: 'Run a stdio MCP server exposing duro_lookup / duro_list / duro_manifest',
@@ -80,6 +104,7 @@ export function buildManifest(registry, version) {
80
104
  enums: {
81
105
  names: lookupNames(registry),
82
106
  kinds: ['components', 'recipes', 'tokens'],
107
+ events: ['session-start', 'install'],
83
108
  },
84
109
  };
85
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AAgBzC,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EACL,+LAA+L;QACjM,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,IAAI;gBACd,WAAW,EACT,uGAAuG;gBACzG,UAAU,EAAE,OAAO;aACpB;SACF;QACD,KAAK,EAAE;YACL,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAC;YACtF,EAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAC;YAChE,EAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,oCAAoC,EAAC;SAC1F;QACD,OAAO,EAAE;YACP,KAAK,EACH,wFAAwF;YAC1F,WAAW,EAAE,uEAAuE;SACrF;QACD,QAAQ,EAAE;YACR,aAAa;YACb,yBAAyB;YACzB,+BAA+B;YAC/B,uBAAuB;SACxB;QACD,OAAO,EAAE,aAAa;KACvB;IACD;QACE,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,yCAAyC;QAClD,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,8CAA8C;gBAC3D,UAAU,EAAE,OAAO;aACpB;SACF;QACD,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,wCAAwC,EAAC;QACtF,QAAQ,EAAE,CAAC,WAAW,EAAE,0BAA0B,CAAC;QACnD,OAAO,EAAE,WAAW;KACrB;IACD;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,8EAA8E;QACvF,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,mDAAmD,EAAC;QAC9F,QAAQ,EAAE,CAAC,sBAAsB,CAAC;QAClC,OAAO,EAAE,eAAe;KACzB;IACD;QACE,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,yEAAyE;QAClF,IAAI,EAAE,EAAE;QACR,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,2BAA2B,EAAC;QACnE,QAAQ,EAAE;YACR,sFAAsF;SACvF;KACF;CACF,CAAA;AAED,MAAM,UAAU,aAAa,CAAC,QAAkB,EAAE,OAAe;IAC/D,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,GAAG,EAAE,MAAM;QACX,OAAO;QACP,aAAa,EAAE,QAAQ,CAAC,aAAa;QACrC,WAAW,EAAE,oDAAoD;QACjE,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EAAE;YACX,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,qBAAqB,EAAC;YACnE,EAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,sBAAsB,EAAC;SACzE;QACD,QAAQ,EAAE,QAAQ;QAClB,KAAK,EAAE;YACL,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC;YAC5B,KAAK,EAAE,CAAC,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC;SAC3C;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/cli",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Machine-queryable docs for the Duro design system: components, props, recipes, tokens and rules as CLI output, JSON, or an MCP server.",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@modelcontextprotocol/sdk": ">=1.0.0",
48
- "@duro-app/ui": "^1.1.2"
48
+ "@duro-app/ui": "^1.3.0"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@duro-app/ui": {
@@ -60,9 +60,9 @@
60
60
  "@types/node": "^24.0.0",
61
61
  "ts-morph": "^26.0.0",
62
62
  "typescript": "^5.7.0",
63
- "@duro-app/eslint-plugin": "^1.1.2",
64
- "@duro-app/tokens": "^1.1.2",
65
- "@duro-app/ui": "^1.1.2"
63
+ "@duro-app/ui": "^1.3.0",
64
+ "@duro-app/tokens": "^1.3.0",
65
+ "@duro-app/eslint-plugin": "^1.3.0"
66
66
  },
67
67
  "scripts": {
68
68
  "registry": "node scripts/build-registry.mjs",