@hacksmith/doraval 0.2.33 → 0.2.42

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/bin/doraval.js CHANGED
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
599
599
  var require_package = __commonJS((exports, module) => {
600
600
  module.exports = {
601
601
  name: "@hacksmith/doraval",
602
- version: "0.2.33",
602
+ version: "0.2.42",
603
603
  author: "Saif",
604
604
  repository: {
605
605
  type: "git",
@@ -640,7 +640,7 @@ var require_package = __commonJS((exports, module) => {
640
640
  "apps/*"
641
641
  ],
642
642
  scripts: {
643
- build: "bun build ./src/cli/index.ts --outfile ./bin/doraval.js --target bun",
643
+ build: "bun build ./src/cli/index.ts --outfile ./bin/doraval.js --target bun && rm -rf bin/ui && cp -r src/ui bin/ui",
644
644
  dev: "bun run ./src/cli/index.ts",
645
645
  test: "bun test",
646
646
  typecheck: "bunx tsc --noEmit --skipLibCheck",
@@ -906,7 +906,7 @@ var init_validate = __esm(() => {
906
906
  validate_default = defineCommand({
907
907
  meta: {
908
908
  name: "validate",
909
- description: "Validate structure and schema of a skill or plugin"
909
+ description: "Validate structure and schema of a skill or plugin (keywords in plugin.json help agent discovery)"
910
910
  },
911
911
  args: {
912
912
  path: {
@@ -1817,20 +1817,483 @@ var init_list = __esm(() => {
1817
1817
  });
1818
1818
  });
1819
1819
 
1820
+ // src/cli/commands/journal/context.ts
1821
+ var exports_context = {};
1822
+ __export(exports_context, {
1823
+ generateJournalContext: () => generateJournalContext,
1824
+ default: () => context_default
1825
+ });
1826
+ import { existsSync as existsSync5 } from "fs";
1827
+ import { join as join4, resolve as resolvePath } from "path";
1828
+ function truncate(text, max = 180) {
1829
+ const clean = text.replace(/\s+/g, " ").trim();
1830
+ if (clean.length <= max)
1831
+ return clean;
1832
+ return clean.slice(0, max - 1) + "\u2026";
1833
+ }
1834
+ function groupByPushback(entries) {
1835
+ const strong = [];
1836
+ const friction = [];
1837
+ const nudge = [];
1838
+ for (const e of entries) {
1839
+ const pb = e.pushback ?? 0;
1840
+ if (pb >= 7)
1841
+ strong.push(e);
1842
+ else if (pb >= 4)
1843
+ friction.push(e);
1844
+ else
1845
+ nudge.push(e);
1846
+ }
1847
+ const byPush = (a, b) => (b.pushback ?? 0) - (a.pushback ?? 0);
1848
+ return {
1849
+ strong: strong.sort(byPush),
1850
+ friction: friction.sort(byPush),
1851
+ nudge: nudge.sort(byPush)
1852
+ };
1853
+ }
1854
+ function generateJournalContext(entries, project, opts = {}) {
1855
+ const minPb = opts.minPushback ?? 1;
1856
+ const showFull = !!opts.full;
1857
+ const includeGlobal = !opts.noGlobal;
1858
+ let filtered = entries.filter((e) => {
1859
+ const pb = e.pushback ?? 0;
1860
+ const isActive = (e.status || "active") === "active";
1861
+ return isActive && pb >= minPb;
1862
+ });
1863
+ if (!includeGlobal) {}
1864
+ if (filtered.length === 0) {
1865
+ return "";
1866
+ }
1867
+ const groups = groupByPushback(filtered);
1868
+ let out = "";
1869
+ const scope = project ? `for ${project}` : "from your journal";
1870
+ out += `These are recorded decisions and principles ${scope}. `;
1871
+ out += `Higher pushback entries represent stronger prior commitments.
1872
+
1873
+ `;
1874
+ const renderGroup = (title, items) => {
1875
+ if (items.length === 0)
1876
+ return "";
1877
+ let g = `## ${title}
1878
+
1879
+ `;
1880
+ for (const e of items) {
1881
+ const tags = (e.tags || []).length ? `tags: ${(e.tags || []).join(", ")}` : "";
1882
+ const pb = e.pushback ?? 0;
1883
+ g += `- **${e.title}** (pushback: ${pb}${tags ? `, ${tags}` : ""})
1884
+ `;
1885
+ const body = showFull ? (e.rationale || "").trim() : truncate(e.rationale || "");
1886
+ if (body) {
1887
+ g += ` ${body}
1888
+ `;
1889
+ }
1890
+ g += `
1891
+ `;
1892
+ }
1893
+ return g;
1894
+ };
1895
+ out += renderGroup("Strong (pushback 7\u201310)", groups.strong);
1896
+ out += renderGroup("Friction (pushback 4\u20136)", groups.friction);
1897
+ out += renderGroup("Nudges (pushback 1\u20133)", groups.nudge);
1898
+ out += "If these feel out of date, run: `dora journal update` or `dora journal list`.\n";
1899
+ return out.trimEnd() + `
1900
+ `;
1901
+ }
1902
+ async function appendOrUpdateJournalBlock(target, contextText, project, useReference) {
1903
+ const absTarget = resolvePath(process.cwd(), target);
1904
+ let original = "";
1905
+ if (existsSync5(absTarget)) {
1906
+ original = await Bun.file(absTarget).text();
1907
+ }
1908
+ let blockContent;
1909
+ if (useReference) {
1910
+ const refLines = [];
1911
+ refLines.push("## Recorded decisions (from journal)");
1912
+ refLines.push("");
1913
+ refLines.push("@~/.doraval/journals/global.md");
1914
+ if (project) {
1915
+ refLines.push(`@~/.doraval/journals/${project}.md`);
1916
+ }
1917
+ refLines.push("");
1918
+ refLines.push("_These are your synced project decisions. High pushback items are strong commitments._");
1919
+ blockContent = refLines.join(`
1920
+ `);
1921
+ } else {
1922
+ blockContent = contextText.trim();
1923
+ }
1924
+ const newBlock = [
1925
+ JOURNAL_BLOCK_START,
1926
+ "",
1927
+ blockContent,
1928
+ "",
1929
+ JOURNAL_BLOCK_END
1930
+ ].join(`
1931
+ `);
1932
+ let updated;
1933
+ const startIdx = original.indexOf(JOURNAL_BLOCK_START);
1934
+ const endIdx = original.indexOf(JOURNAL_BLOCK_END);
1935
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
1936
+ const before = original.slice(0, startIdx);
1937
+ const after = original.slice(endIdx + JOURNAL_BLOCK_END.length);
1938
+ updated = before + newBlock + after;
1939
+ } else {
1940
+ const separator = original.trim().length > 0 ? `
1941
+
1942
+ ` : "";
1943
+ updated = original + separator + newBlock + `
1944
+ `;
1945
+ }
1946
+ await Bun.write(absTarget, updated);
1947
+ const action = existsSync5(absTarget) && startIdx !== -1 ? "Updated" : "Added";
1948
+ ui.write(`
1949
+ ${import_picocolors7.default.green("\u2713")} ${action} journal decisions section in ${import_picocolors7.default.white(target)}`);
1950
+ if (useReference) {
1951
+ ui.write(` ${import_picocolors7.default.dim("Using @import references (full files will be loaded by Claude).")}`);
1952
+ } else {
1953
+ ui.write(` ${import_picocolors7.default.dim("Embedded compact decisions (low noise).")}`);
1954
+ }
1955
+ }
1956
+ var import_picocolors7, JOURNAL_BLOCK_START = "<!-- doraval-journal:start -->", JOURNAL_BLOCK_END = "<!-- doraval-journal:end -->", context_default;
1957
+ var init_context = __esm(() => {
1958
+ init_dist();
1959
+ init_out();
1960
+ init_journal_config();
1961
+ init_journal_parse();
1962
+ import_picocolors7 = __toESM(require_picocolors(), 1);
1963
+ context_default = defineCommand({
1964
+ meta: {
1965
+ name: "context",
1966
+ description: "Output compact journal decisions (for hooks, CLAUDE.md, or piping)"
1967
+ },
1968
+ args: {
1969
+ project: {
1970
+ type: "string",
1971
+ alias: "p",
1972
+ description: "Project name (defaults to directory-based mapping)"
1973
+ },
1974
+ "min-pushback": {
1975
+ type: "string",
1976
+ description: "Only include entries with at least this pushback (1-10)",
1977
+ default: "1"
1978
+ },
1979
+ full: {
1980
+ type: "boolean",
1981
+ description: "Show full rationale instead of truncated",
1982
+ default: false
1983
+ },
1984
+ "no-global": {
1985
+ type: "boolean",
1986
+ description: "Exclude global entries (project only)",
1987
+ default: false
1988
+ },
1989
+ "append-to": {
1990
+ type: "string",
1991
+ description: "Append (or update) a managed section in this file (e.g. CLAUDE.md or AGENTS.md)"
1992
+ },
1993
+ reference: {
1994
+ type: "boolean",
1995
+ description: "When appending, use @import references instead of embedding compact decisions",
1996
+ default: false
1997
+ },
1998
+ "print-hook": {
1999
+ type: "boolean",
2000
+ description: "Print a ready-to-paste SessionStart hook snippet for hooks.json",
2001
+ default: false
2002
+ }
2003
+ },
2004
+ async run({ args }) {
2005
+ if (args["print-hook"]) {
2006
+ const hookCmd = "sh -c 'dora journal context 2>/dev/null || true'";
2007
+ console.log(JSON.stringify({
2008
+ SessionStart: [
2009
+ {
2010
+ hooks: [
2011
+ {
2012
+ type: "command",
2013
+ command: hookCmd
2014
+ }
2015
+ ]
2016
+ }
2017
+ ]
2018
+ }, null, 2));
2019
+ console.error("\nTip: Use `dora journal hook enable` to install the hook automatically.");
2020
+ console.error(" Use `dora journal hook disable` to remove it.");
2021
+ console.error(" (sh -c wrapper ensures shell features like redir work reliably.)");
2022
+ process.exit(0);
2023
+ }
2024
+ const config = await readConfig();
2025
+ let project = args.project;
2026
+ if (!project) {
2027
+ project = resolveProjectName(config) ?? undefined;
2028
+ }
2029
+ if (project) {
2030
+ project = sanitizeProjectName(project);
2031
+ }
2032
+ const journalsDir = getJournalsDir();
2033
+ const entries = [];
2034
+ const globalPath = join4(journalsDir, "global.md");
2035
+ if (existsSync5(globalPath)) {
2036
+ try {
2037
+ const raw = await Bun.file(globalPath).text();
2038
+ entries.push(...parseJournalEntries(raw));
2039
+ } catch {}
2040
+ }
2041
+ if (project) {
2042
+ const projectPath = join4(journalsDir, `${project}.md`);
2043
+ if (existsSync5(projectPath)) {
2044
+ try {
2045
+ const raw = await Bun.file(projectPath).text();
2046
+ entries.push(...parseJournalEntries(raw));
2047
+ } catch {}
2048
+ }
2049
+ }
2050
+ const minPb = parseInt(String(args["min-pushback"] ?? "1"), 10) || 1;
2051
+ const contextText = generateJournalContext(entries, project ?? null, {
2052
+ minPushback: minPb,
2053
+ full: !!args.full,
2054
+ noGlobal: !!args["no-global"]
2055
+ });
2056
+ const appendTarget = args["append-to"];
2057
+ if (appendTarget && contextText) {
2058
+ await appendOrUpdateJournalBlock(appendTarget, contextText, project ?? null, !!args.reference);
2059
+ }
2060
+ if (contextText && !appendTarget) {
2061
+ console.log(contextText);
2062
+ } else if (contextText && appendTarget) {}
2063
+ process.exit(0);
2064
+ }
2065
+ });
2066
+ });
2067
+
2068
+ // src/cli/commands/journal/hook.ts
2069
+ var exports_hook = {};
2070
+ __export(exports_hook, {
2071
+ writeHookConfig: () => writeJson,
2072
+ removeHook: () => removeHook,
2073
+ readHookConfig: () => readJson,
2074
+ hasHook: () => hasHook,
2075
+ getLocalHooksPath: () => getLocalHooksPath,
2076
+ getGlobalSettingsPath: () => getGlobalSettingsPath,
2077
+ default: () => hook_default,
2078
+ addHook: () => addHook
2079
+ });
2080
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, unlinkSync, rmdirSync, readdirSync as readdirSync2 } from "fs";
2081
+ import { join as join5, dirname } from "path";
2082
+ import { homedir as homedir2 } from "os";
2083
+ function getGlobalSettingsPath() {
2084
+ return join5(homedir2(), ".claude", "settings.json");
2085
+ }
2086
+ function getLocalHooksPath() {
2087
+ return join5(process.cwd(), "hooks", "hooks.json");
2088
+ }
2089
+ async function readJson(file) {
2090
+ if (!existsSync6(file))
2091
+ return {};
2092
+ try {
2093
+ const raw = await Bun.file(file).text();
2094
+ return JSON.parse(raw);
2095
+ } catch {
2096
+ return {};
2097
+ }
2098
+ }
2099
+ async function writeJson(file, data) {
2100
+ const dir = dirname(file);
2101
+ if (!existsSync6(dir)) {
2102
+ mkdirSync2(dir, { recursive: true });
2103
+ }
2104
+ await Bun.write(file, JSON.stringify(data, null, 2) + `
2105
+ `);
2106
+ }
2107
+ function hasHook(config) {
2108
+ const sessionStart = config?.hooks?.SessionStart;
2109
+ if (!Array.isArray(sessionStart))
2110
+ return false;
2111
+ return sessionStart.some((group) => Array.isArray(group?.hooks) && group.hooks.some((h) => h?.command === HOOK_COMMAND));
2112
+ }
2113
+ async function addHook(file) {
2114
+ const original = await readJson(file);
2115
+ const config = JSON.parse(JSON.stringify(original));
2116
+ if (!config.hooks)
2117
+ config.hooks = {};
2118
+ if (!Array.isArray(config.hooks.SessionStart)) {
2119
+ config.hooks.SessionStart = [];
2120
+ }
2121
+ if (hasHook(config)) {
2122
+ return { changed: false, path: file };
2123
+ }
2124
+ config.hooks.SessionStart.push(HOOK_GROUP);
2125
+ await writeJson(file, config);
2126
+ return { changed: true, path: file };
2127
+ }
2128
+ async function removeHook(file) {
2129
+ if (!existsSync6(file))
2130
+ return { changed: false, path: file };
2131
+ const original = await readJson(file);
2132
+ const config = JSON.parse(JSON.stringify(original));
2133
+ if (!config.hooks || !Array.isArray(config.hooks.SessionStart)) {
2134
+ return { changed: false, path: file };
2135
+ }
2136
+ const beforeLen = config.hooks.SessionStart.length;
2137
+ config.hooks.SessionStart = config.hooks.SessionStart.map((group) => {
2138
+ if (!group || !Array.isArray(group.hooks))
2139
+ return group;
2140
+ group.hooks = group.hooks.filter((h) => h?.command !== HOOK_COMMAND);
2141
+ return group;
2142
+ }).filter((group) => Array.isArray(group?.hooks) && group.hooks.length > 0);
2143
+ if (config.hooks.SessionStart.length === 0) {
2144
+ delete config.hooks.SessionStart;
2145
+ }
2146
+ if (config.hooks && Object.keys(config.hooks).length === 0) {
2147
+ delete config.hooks;
2148
+ }
2149
+ const changed = JSON.stringify(config) !== JSON.stringify(original);
2150
+ if (changed) {
2151
+ const isEmpty = !config || Object.keys(config).length === 0;
2152
+ if (isEmpty && existsSync6(file)) {
2153
+ try {
2154
+ unlinkSync(file);
2155
+ } catch {}
2156
+ try {
2157
+ const dir = dirname(file);
2158
+ if (existsSync6(dir) && readdirSync2(dir).length === 0)
2159
+ rmdirSync(dir);
2160
+ } catch {}
2161
+ } else {
2162
+ await writeJson(file, config);
2163
+ }
2164
+ }
2165
+ return { changed, path: file };
2166
+ }
2167
+ async function printHookStatus() {
2168
+ const localPath = getLocalHooksPath();
2169
+ const globalPath = getGlobalSettingsPath();
2170
+ const localHas = hasHook(await readJson(localPath));
2171
+ const globalHas = hasHook(await readJson(globalPath));
2172
+ if (localHas) {
2173
+ ui.success(`Enabled in project: ${localPath}`);
2174
+ }
2175
+ if (globalHas) {
2176
+ ui.success(`Enabled globally: ${globalPath}`);
2177
+ }
2178
+ if (!localHas && !globalHas) {
2179
+ ui.info("Journal hook is not installed.");
2180
+ ui.info("Run `dora journal hook enable` to install it.");
2181
+ }
2182
+ }
2183
+ var HOOK_COMMAND = "sh -c 'dora journal context 2>/dev/null || true'", HOOK_GROUP, enable, disable, status, hook_default;
2184
+ var init_hook = __esm(() => {
2185
+ init_dist();
2186
+ init_out();
2187
+ HOOK_GROUP = {
2188
+ hooks: [
2189
+ {
2190
+ type: "command",
2191
+ command: HOOK_COMMAND
2192
+ }
2193
+ ]
2194
+ };
2195
+ enable = defineCommand({
2196
+ meta: {
2197
+ name: "enable",
2198
+ description: "Install the journal decisions hook (SessionStart) so decisions are injected into Claude sessions"
2199
+ },
2200
+ args: {
2201
+ global: {
2202
+ type: "boolean",
2203
+ alias: "g",
2204
+ description: "Install to global ~/.claude/settings.json (instead of project hooks/hooks.json)",
2205
+ default: false
2206
+ }
2207
+ },
2208
+ async run({ args }) {
2209
+ const useGlobal = !!args.global;
2210
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
2211
+ const result = await addHook(target);
2212
+ if (result.changed) {
2213
+ ui.success(`Enabled journal hook in ${result.path}`);
2214
+ if (useGlobal) {
2215
+ ui.info("Installed globally \u2014 will affect all new Claude sessions (your typical setup with many plugins).");
2216
+ } else {
2217
+ ui.info("Installed locally for this project \u2014 will affect Claude sessions started from this directory.");
2218
+ ui.info("If your Claude hooks live in the global ~/.claude/settings.json (very common), re-run with -g/--global.");
2219
+ }
2220
+ ui.info("Start a new Claude session (or restart) for the hook to take effect.");
2221
+ ui.dim("The hook runs `dora journal context` on SessionStart and injects your active decisions.");
2222
+ ui.info("Preview what gets injected: dora journal context");
2223
+ ui.info("Test inside Claude: ask it to list or recall your recent journal decisions.");
2224
+ } else {
2225
+ ui.info(`Journal hook is already enabled in ${result.path}`);
2226
+ }
2227
+ }
2228
+ });
2229
+ disable = defineCommand({
2230
+ meta: {
2231
+ name: "disable",
2232
+ description: "Remove the journal decisions hook from Claude configuration"
2233
+ },
2234
+ args: {
2235
+ global: {
2236
+ type: "boolean",
2237
+ alias: "g",
2238
+ description: "Remove from global ~/.claude/settings.json",
2239
+ default: false
2240
+ }
2241
+ },
2242
+ async run({ args }) {
2243
+ const useGlobal = !!args.global;
2244
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
2245
+ const result = await removeHook(target);
2246
+ if (result.changed) {
2247
+ ui.success(`Disabled journal hook in ${result.path}`);
2248
+ ui.info("The decisions will no longer be injected on new SessionStart.");
2249
+ } else {
2250
+ ui.info(`Journal hook was not present in ${result.path}`);
2251
+ }
2252
+ }
2253
+ });
2254
+ status = defineCommand({
2255
+ meta: {
2256
+ name: "status",
2257
+ description: "Check where the journal hook is currently installed"
2258
+ },
2259
+ async run() {
2260
+ await printHookStatus();
2261
+ }
2262
+ });
2263
+ hook_default = defineCommand({
2264
+ meta: {
2265
+ name: "hook",
2266
+ description: "Manage Claude hooks for automatically injecting journal decisions"
2267
+ },
2268
+ subCommands: {
2269
+ enable: () => Promise.resolve(enable),
2270
+ disable: () => Promise.resolve(disable),
2271
+ status: () => Promise.resolve(status)
2272
+ },
2273
+ async run() {
2274
+ const cliArgs = process.argv.slice(2);
2275
+ const hookIdx = cliArgs.indexOf("hook");
2276
+ if (hookIdx !== -1 && cliArgs.length > hookIdx + 1)
2277
+ return;
2278
+ await printHookStatus();
2279
+ }
2280
+ });
2281
+ });
2282
+
1820
2283
  // src/cli/commands/journal/update.ts
1821
2284
  var exports_update = {};
1822
2285
  __export(exports_update, {
1823
2286
  default: () => update_default
1824
2287
  });
1825
- import { existsSync as existsSync5 } from "fs";
1826
- import { join as join4 } from "path";
1827
- var import_picocolors7, update_default;
2288
+ import { existsSync as existsSync7 } from "fs";
2289
+ import { join as join6 } from "path";
2290
+ var import_picocolors8, update_default;
1828
2291
  var init_update = __esm(() => {
1829
2292
  init_dist();
1830
2293
  init_out();
1831
2294
  init_journal_config();
1832
2295
  init_journal_remote();
1833
- import_picocolors7 = __toESM(require_picocolors(), 1);
2296
+ import_picocolors8 = __toESM(require_picocolors(), 1);
1834
2297
  update_default = defineCommand({
1835
2298
  meta: {
1836
2299
  name: "update",
@@ -1851,30 +2314,30 @@ var init_update = __esm(() => {
1851
2314
  async run({ args }) {
1852
2315
  const ghCheck = ensureGhCli();
1853
2316
  if (!ghCheck.ok) {
1854
- ui.write(` ${import_picocolors7.default.red("\u2717")} ${import_picocolors7.default.white("The GitHub CLI (")}${import_picocolors7.default.bold("gh")}${import_picocolors7.default.white(") is not installed.")}
2317
+ ui.write(` ${import_picocolors8.default.red("\u2717")} ${import_picocolors8.default.white("The GitHub CLI (")}${import_picocolors8.default.bold("gh")}${import_picocolors8.default.white(") is not installed.")}
1855
2318
  `);
1856
- ui.write(` doraval uses ${import_picocolors7.default.bold("gh")} to fetch and sync journal files with GitHub.
2319
+ ui.write(` doraval uses ${import_picocolors8.default.bold("gh")} to fetch and sync journal files with GitHub.
1857
2320
  `);
1858
2321
  ui.write(` Install it:
1859
2322
  `);
1860
- ui.write(` macOS: ${import_picocolors7.default.dim("brew install gh")}`);
1861
- ui.write(` Linux: ${import_picocolors7.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
1862
- ui.write(` Windows: ${import_picocolors7.default.dim("winget install --id GitHub.cli")}
2323
+ ui.write(` macOS: ${import_picocolors8.default.dim("brew install gh")}`);
2324
+ ui.write(` Linux: ${import_picocolors8.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
2325
+ ui.write(` Windows: ${import_picocolors8.default.dim("winget install --id GitHub.cli")}
1863
2326
  `);
1864
- ui.write(` Then authenticate: ${import_picocolors7.default.dim("gh auth login")}
2327
+ ui.write(` Then authenticate: ${import_picocolors8.default.dim("gh auth login")}
1865
2328
  `);
1866
2329
  process.exit(1);
1867
2330
  }
1868
2331
  const config = await readConfig();
1869
2332
  if (!config?.journal.repo) {
1870
- ui.write(`${import_picocolors7.default.red("\u2717")} No journal repo configured. Run ${import_picocolors7.default.dim("dora init")} (or ${import_picocolors7.default.dim("doraval journal init")}) first.`);
2333
+ ui.write(`${import_picocolors8.default.red("\u2717")} No journal repo configured. Run ${import_picocolors8.default.dim("dora init")} (or ${import_picocolors8.default.dim("doraval journal init")}) first.`);
1871
2334
  process.exit(1);
1872
2335
  }
1873
2336
  const journalRepo = config.journal.repo;
1874
2337
  ensureDoravalDirs();
1875
2338
  const journalsDir = getJournalsDir();
1876
2339
  ui.write(`
1877
- ${import_picocolors7.default.bold(import_picocolors7.default.white("dora journal update"))} \u2014 ${import_picocolors7.default.dim(import_picocolors7.default.gray(journalRepo))}
2340
+ ${import_picocolors8.default.bold(import_picocolors8.default.white("dora journal update"))} \u2014 ${import_picocolors8.default.dim(import_picocolors8.default.gray(journalRepo))}
1878
2341
  `);
1879
2342
  const projectsToUpdate = [];
1880
2343
  if (args.all) {
@@ -1892,19 +2355,19 @@ var init_update = __esm(() => {
1892
2355
  try {
1893
2356
  projectsToUpdate.push(sanitizeProjectName(project));
1894
2357
  } catch {
1895
- ui.write(`${import_picocolors7.default.red("\u2717")} Invalid project name: ${project}`);
2358
+ ui.write(`${import_picocolors8.default.red("\u2717")} Invalid project name: ${project}`);
1896
2359
  process.exit(1);
1897
2360
  }
1898
2361
  }
1899
2362
  }
1900
- const globalLocal = join4(journalsDir, "global.md");
2363
+ const globalLocal = join6(journalsDir, "global.md");
1901
2364
  const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md", globalLocal);
1902
2365
  let gotGlobal;
1903
2366
  if (!refreshGlobalRes.ok) {
1904
2367
  if (refreshGlobalRes.isNotFound) {
1905
2368
  gotGlobal = false;
1906
2369
  } else {
1907
- ui.write(`${import_picocolors7.default.red("\u2717")} Failed to fetch global.md from ${journalRepo}:`);
2370
+ ui.write(`${import_picocolors8.default.red("\u2717")} Failed to fetch global.md from ${journalRepo}:`);
1908
2371
  ui.write(refreshGlobalRes.error);
1909
2372
  process.exit(1);
1910
2373
  }
@@ -1912,33 +2375,33 @@ var init_update = __esm(() => {
1912
2375
  gotGlobal = refreshGlobalRes.value;
1913
2376
  }
1914
2377
  if (gotGlobal) {
1915
- ui.write(` ${import_picocolors7.default.green("\u2713")} global.md`);
2378
+ ui.write(` ${import_picocolors8.default.green("\u2713")} global.md`);
1916
2379
  } else {
1917
- ui.write(` ${import_picocolors7.default.dim("\xB7")} global.md ${import_picocolors7.default.dim("(not present on remote)")}`);
2380
+ ui.write(` ${import_picocolors8.default.dim("\xB7")} global.md ${import_picocolors8.default.dim("(not present on remote)")}`);
1918
2381
  }
1919
2382
  if (projectsToUpdate.length === 0) {
1920
2383
  if (args.all) {
1921
2384
  ui.write(`
1922
- ${import_picocolors7.default.dim(import_picocolors7.default.gray("No projects registered."))}
2385
+ ${import_picocolors8.default.dim(import_picocolors8.default.gray("No projects registered."))}
1923
2386
  `);
1924
2387
  } else {
1925
2388
  ui.write(`
1926
- ${import_picocolors7.default.yellow("\u26A0")} No project mapping found.
1927
- ` + ` Run ${import_picocolors7.default.dim("dora init")} or pass ${import_picocolors7.default.dim("--project <name>")} / ${import_picocolors7.default.dim("--all")}.
2389
+ ${import_picocolors8.default.yellow("\u26A0")} No project mapping found.
2390
+ ` + ` Run ${import_picocolors8.default.dim("dora init")} or pass ${import_picocolors8.default.dim("--project <name>")} / ${import_picocolors8.default.dim("--all")}.
1928
2391
  `);
1929
2392
  }
1930
2393
  return;
1931
2394
  }
1932
2395
  for (const project of projectsToUpdate) {
1933
2396
  const remotePath = `projects/${project}.md`;
1934
- const localPath = join4(journalsDir, `${project}.md`);
2397
+ const localPath = join6(journalsDir, `${project}.md`);
1935
2398
  const refreshRes = await refreshLocalJournalFile(journalRepo, remotePath, localPath);
1936
2399
  let got;
1937
2400
  if (!refreshRes.ok) {
1938
2401
  if (refreshRes.isNotFound) {
1939
2402
  got = false;
1940
2403
  } else {
1941
- ui.write(`${import_picocolors7.default.red("\u2717")} Failed to fetch ${remotePath} from ${journalRepo}:`);
2404
+ ui.write(`${import_picocolors8.default.red("\u2717")} Failed to fetch ${remotePath} from ${journalRepo}:`);
1942
2405
  ui.write(refreshRes.error);
1943
2406
  process.exit(1);
1944
2407
  }
@@ -1946,10 +2409,10 @@ var init_update = __esm(() => {
1946
2409
  got = refreshRes.value;
1947
2410
  }
1948
2411
  if (got) {
1949
- ui.write(` ${import_picocolors7.default.green("\u2713")} ${remotePath}`);
2412
+ ui.write(` ${import_picocolors8.default.green("\u2713")} ${remotePath}`);
1950
2413
  } else {
1951
- ui.write(` ${import_picocolors7.default.dim("\xB7")} ${remotePath} ${import_picocolors7.default.dim("(not present on remote \u2014 will be created on first sync)")}`);
1952
- if (!existsSync5(localPath)) {
2414
+ ui.write(` ${import_picocolors8.default.dim("\xB7")} ${remotePath} ${import_picocolors8.default.dim("(not present on remote \u2014 will be created on first sync)")}`);
2415
+ if (!existsSync7(localPath)) {
1953
2416
  await Bun.write(localPath, `# ${project} Journal
1954
2417
 
1955
2418
  Project-specific decisions.
@@ -1959,7 +2422,7 @@ Project-specific decisions.
1959
2422
  }
1960
2423
  const summary = args.all && projectsToUpdate.length > 1 ? `${projectsToUpdate.length} projects + global` : projectsToUpdate.length === 1 ? projectsToUpdate[0] : "journals";
1961
2424
  ui.write(`
1962
- ${import_picocolors7.default.dim(import_picocolors7.default.gray("Local cache refreshed for"))} ${import_picocolors7.default.bold(import_picocolors7.default.white(summary))}.
2425
+ ${import_picocolors8.default.dim(import_picocolors8.default.gray("Local cache refreshed for"))} ${import_picocolors8.default.bold(import_picocolors8.default.white(summary))}.
1963
2426
  `);
1964
2427
  }
1965
2428
  });
@@ -2026,8 +2489,8 @@ __export(exports_add, {
2026
2489
  default: () => add_default,
2027
2490
  buildAgentArgv: () => buildAgentArgv
2028
2491
  });
2029
- import { existsSync as existsSync6 } from "fs";
2030
- import { join as join5 } from "path";
2492
+ import { existsSync as existsSync8 } from "fs";
2493
+ import { join as join7 } from "path";
2031
2494
  var {spawnSync: spawnSync2 } = globalThis.Bun;
2032
2495
  function buildAgentArgv(template, promptText) {
2033
2496
  const marker = "__DORA_PROMPT__";
@@ -2067,7 +2530,7 @@ If you cannot produce exactly this, output the JSON with the best you can and se
2067
2530
  const template = agentCfg.prompt_template || '-p "{{prompt}}" --output-format json';
2068
2531
  const extraArgs = buildAgentArgv(template, scaffold);
2069
2532
  const shortTemplate = (agentCfg.prompt_template || '-p "{{prompt}}" --output-format json').slice(0, 80);
2070
- ui.write(` ${import_picocolors8.default.dim(`\u2192 ${agentCfg.command} ${shortTemplate}...`)}`);
2533
+ ui.write(` ${import_picocolors9.default.dim(`\u2192 ${agentCfg.command} ${shortTemplate}...`)}`);
2071
2534
  try {
2072
2535
  const result = spawnSync2([agentCfg.command, ...extraArgs], {
2073
2536
  stdout: "pipe",
@@ -2076,12 +2539,12 @@ If you cannot produce exactly this, output the JSON with the best you can and se
2076
2539
  const stdout = result.stdout.toString().trim();
2077
2540
  const stderr = result.stderr.toString().trim();
2078
2541
  if (result.exitCode !== 0) {
2079
- ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Configured agent (${agentCfg.command}) exited with code ${result.exitCode}. Falling back to defaults.`);
2542
+ ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Configured agent (${agentCfg.command}) exited with code ${result.exitCode}. Falling back to defaults.`);
2080
2543
  if (stderr)
2081
- ui.write(` ${import_picocolors8.default.dim("stderr:")}
2544
+ ui.write(` ${import_picocolors9.default.dim("stderr:")}
2082
2545
  ${stderr.slice(0, 800)}`);
2083
2546
  if (stdout)
2084
- ui.write(` ${import_picocolors8.default.dim("stdout:")}
2547
+ ui.write(` ${import_picocolors9.default.dim("stdout:")}
2085
2548
  ${stdout.slice(0, 400)}`);
2086
2549
  return null;
2087
2550
  }
@@ -2126,37 +2589,37 @@ ${stdout.slice(0, 400)}`);
2126
2589
  parsed = candidates[0] || null;
2127
2590
  }
2128
2591
  if (!parsed || typeof parsed !== "object") {
2129
- ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Agent produced output but no usable JSON was found. Falling back.`);
2130
- ui.write(` ${import_picocolors8.default.dim("stdout (first 700 chars):")}
2592
+ ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Agent produced output but no usable JSON was found. Falling back.`);
2593
+ ui.write(` ${import_picocolors9.default.dim("stdout (first 700 chars):")}
2131
2594
  ${stdout.slice(0, 700)}`);
2132
2595
  if (stderr)
2133
- ui.write(` ${import_picocolors8.default.dim("stderr:")}
2596
+ ui.write(` ${import_picocolors9.default.dim("stderr:")}
2134
2597
  ${stderr.slice(0, 500)}`);
2135
2598
  return null;
2136
2599
  }
2137
2600
  if (!parsed.title && !parsed.rationale) {
2138
- ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Agent returned JSON without expected fields (title/rationale). Using defaults.`);
2139
- ui.write(` ${import_picocolors8.default.dim("parsed keys:")} ${Object.keys(parsed).join(", ")}`);
2140
- ui.write(` ${import_picocolors8.default.dim("stdout (truncated):")}
2601
+ ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Agent returned JSON without expected fields (title/rationale). Using defaults.`);
2602
+ ui.write(` ${import_picocolors9.default.dim("parsed keys:")} ${Object.keys(parsed).join(", ")}`);
2603
+ ui.write(` ${import_picocolors9.default.dim("stdout (truncated):")}
2141
2604
  ${stdout.slice(0, 600)}`);
2142
2605
  return null;
2143
2606
  }
2144
2607
  return parsed;
2145
2608
  } catch (e) {
2146
- ui.write(` ${import_picocolors8.default.yellow("\u26A0")} Failed to invoke configured agent (${agentCfg.command}): ${e.message}. Using defaults.`);
2609
+ ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Failed to invoke configured agent (${agentCfg.command}): ${e.message}. Using defaults.`);
2147
2610
  return null;
2148
2611
  }
2149
2612
  }
2150
2613
  function slugify(title) {
2151
2614
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "untitled";
2152
2615
  }
2153
- var import_picocolors8, add_default;
2616
+ var import_picocolors9, add_default;
2154
2617
  var init_add = __esm(() => {
2155
2618
  init_dist();
2156
2619
  init_out();
2157
2620
  init_journal_config();
2158
2621
  init_journal_validate();
2159
- import_picocolors8 = __toESM(require_picocolors(), 1);
2622
+ import_picocolors9 = __toESM(require_picocolors(), 1);
2160
2623
  add_default = defineCommand({
2161
2624
  meta: {
2162
2625
  name: "add",
@@ -2231,16 +2694,16 @@ var init_add = __esm(() => {
2231
2694
  project = sanitizeProjectName(project);
2232
2695
  }
2233
2696
  if (!project) {
2234
- ui.write(`${import_picocolors8.default.yellow("\u26A0")} No project mapping found.
2697
+ ui.write(`${import_picocolors9.default.yellow("\u26A0")} No project mapping found.
2235
2698
 
2236
- ` + `Run ${import_picocolors8.default.dim("dora init")} (or ${import_picocolors8.default.dim("doraval journal init")}) first, or pass ${import_picocolors8.default.dim("--project <name>")}.`);
2699
+ ` + `Run ${import_picocolors9.default.dim("dora init")} (or ${import_picocolors9.default.dim("doraval journal init")}) first, or pass ${import_picocolors9.default.dim("--project <name>")}.`);
2237
2700
  process.exit(1);
2238
2701
  }
2239
2702
  let title;
2240
2703
  let pushback;
2241
2704
  let tags = [];
2242
2705
  let author = String(args.author || "human");
2243
- let status = args.status || "active";
2706
+ let status2 = args.status || "active";
2244
2707
  let rationale;
2245
2708
  let date = new Date().toISOString().split("T")[0];
2246
2709
  const jsonInput = args.json;
@@ -2267,11 +2730,11 @@ var init_add = __esm(() => {
2267
2730
  if (parsed.author)
2268
2731
  author = String(parsed.author);
2269
2732
  if (parsed.status)
2270
- status = parsed.status;
2733
+ status2 = parsed.status;
2271
2734
  if (parsed.date)
2272
2735
  date = String(parsed.date);
2273
2736
  } catch (e) {
2274
- ui.write(`${import_picocolors8.default.red("\u2717")} Failed to parse --json input: ${e.message}`);
2737
+ ui.write(`${import_picocolors9.default.red("\u2717")} Failed to parse --json input: ${e.message}`);
2275
2738
  process.exit(1);
2276
2739
  }
2277
2740
  }
@@ -2280,7 +2743,7 @@ var init_add = __esm(() => {
2280
2743
  if (rawMdArg && !jsonInput) {
2281
2744
  if (rawMdArg === "-" || rawMdArg === "") {
2282
2745
  rawBody = (await new Response(Bun.stdin.stream()).text()).trim();
2283
- } else if (existsSync6(rawMdArg)) {
2746
+ } else if (existsSync8(rawMdArg)) {
2284
2747
  rawBody = (await Bun.file(rawMdArg).text()).trim();
2285
2748
  } else {
2286
2749
  rawBody = rawMdArg.trim();
@@ -2295,7 +2758,7 @@ var init_add = __esm(() => {
2295
2758
  title = (headingMatch[1] ?? "").trim();
2296
2759
  rawBody = rawBody.replace(/^#+\s+(.+?)(?:\r?\n|$)/m, "").trimStart();
2297
2760
  } else {
2298
- ui.write(`${import_picocolors8.default.red("\u2717")} --raw-markdown provided without a TITLE and without a leading '# Heading' in the markdown.`);
2761
+ ui.write(`${import_picocolors9.default.red("\u2717")} --raw-markdown provided without a TITLE and without a leading '# Heading' in the markdown.`);
2299
2762
  process.exit(1);
2300
2763
  }
2301
2764
  }
@@ -2335,7 +2798,7 @@ var init_add = __esm(() => {
2335
2798
  agentCfg = fullConfigForAgent?.agent;
2336
2799
  if (agentCfg) {
2337
2800
  attemptedAgent = true;
2338
- ui.write(` ${import_picocolors8.default.dim(import_picocolors8.default.gray("(querying your configured coding agent...)"))}`);
2801
+ ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("(querying your configured coding agent...)"))}`);
2339
2802
  const agentResult = await invokeConfiguredAgentForEntry(title, agentCfg);
2340
2803
  if (agentResult) {
2341
2804
  if (agentResult.title)
@@ -2352,7 +2815,7 @@ var init_add = __esm(() => {
2352
2815
  if (agentResult.author)
2353
2816
  author = String(agentResult.author);
2354
2817
  if (agentResult.status)
2355
- status = agentResult.status;
2818
+ status2 = agentResult.status;
2356
2819
  if (agentResult.date)
2357
2820
  date = String(agentResult.date);
2358
2821
  }
@@ -2364,22 +2827,22 @@ var init_add = __esm(() => {
2364
2827
  tags,
2365
2828
  author,
2366
2829
  date,
2367
- status
2830
+ status: status2
2368
2831
  };
2369
2832
  const validation = validateEntry(entry);
2370
2833
  if (!validation.valid) {
2371
- ui.write(`${import_picocolors8.default.red("\u2717")} Invalid entry:
2834
+ ui.write(`${import_picocolors9.default.red("\u2717")} Invalid entry:
2372
2835
  `);
2373
2836
  for (const err of validation.errors) {
2374
- ui.write(` ${import_picocolors8.default.red("\u2022")} ${err}`);
2837
+ ui.write(` ${import_picocolors9.default.red("\u2022")} ${err}`);
2375
2838
  }
2376
2839
  process.exit(1);
2377
2840
  }
2378
2841
  for (const warn of validation.warnings) {
2379
2842
  if ((warn.includes("not supplied") || warn.includes("empty")) && attemptedAgent) {} else if (warn.includes("not supplied") || warn.includes("empty")) {
2380
- ui.write(`${import_picocolors8.default.dim("\xB7")} ${warn}`);
2843
+ ui.write(`${import_picocolors9.default.dim("\xB7")} ${warn}`);
2381
2844
  } else {
2382
- ui.write(`${import_picocolors8.default.yellow("\u26A0")} ${warn}`);
2845
+ ui.write(`${import_picocolors9.default.yellow("\u26A0")} ${warn}`);
2383
2846
  }
2384
2847
  }
2385
2848
  if (!rationale) {
@@ -2392,38 +2855,38 @@ pushback: ${pushback}
2392
2855
  tags: [${tags.join(", ")}]
2393
2856
  author: ${author}
2394
2857
  date: ${date}
2395
- status: ${status}
2858
+ status: ${status2}
2396
2859
  \`\`\`
2397
2860
 
2398
2861
  ${rationale}
2399
2862
  `;
2400
2863
  ensureDoravalDirs();
2401
2864
  const pendingDir = getPendingProjectDir(project);
2402
- if (!existsSync6(pendingDir)) {
2403
- await Bun.write(join5(pendingDir, ".gitkeep"), "");
2865
+ if (!existsSync8(pendingDir)) {
2866
+ await Bun.write(join7(pendingDir, ".gitkeep"), "");
2404
2867
  }
2405
2868
  const slug = slugify(title);
2406
2869
  const filename = `${date}-${slug}.md`;
2407
- const filePath = join5(pendingDir, filename);
2870
+ const filePath = join7(pendingDir, filename);
2408
2871
  await Bun.write(filePath, content);
2409
2872
  ui.write(`
2410
- ${import_picocolors8.default.green("\u2713")} ${import_picocolors8.default.bold(import_picocolors8.default.white(title))}`);
2411
- ui.write(` Project: ${import_picocolors8.default.white(project)} \xB7 run ${import_picocolors8.default.dim(import_picocolors8.default.gray("dora journal sync"))} to publish
2873
+ ${import_picocolors9.default.green("\u2713")} ${import_picocolors9.default.bold(import_picocolors9.default.white(title))}`);
2874
+ ui.write(` Project: ${import_picocolors9.default.white(project)} \xB7 run ${import_picocolors9.default.dim(import_picocolors9.default.gray("dora journal sync"))} to publish
2412
2875
  `);
2413
2876
  if (args.verbose) {
2414
- const authorDisplay = author.startsWith("agent:") ? import_picocolors8.default.cyan(author) : author;
2415
- ui.write(` Pushback: ${import_picocolors8.default.white(String(pushback))}`);
2416
- ui.write(` Tags: ${import_picocolors8.default.gray(tags.join(", ") || import_picocolors8.default.dim("(none)"))}`);
2877
+ const authorDisplay = author.startsWith("agent:") ? import_picocolors9.default.cyan(author) : author;
2878
+ ui.write(` Pushback: ${import_picocolors9.default.white(String(pushback))}`);
2879
+ ui.write(` Tags: ${import_picocolors9.default.gray(tags.join(", ") || import_picocolors9.default.dim("(none)"))}`);
2417
2880
  ui.write(` Author: ${authorDisplay}`);
2418
- ui.write(` File: ${import_picocolors8.default.dim(import_picocolors8.default.gray(filePath))}
2881
+ ui.write(` File: ${import_picocolors9.default.dim(import_picocolors9.default.gray(filePath))}
2419
2882
  `);
2420
2883
  }
2421
2884
  if (isThinInput && !author.startsWith("agent:")) {
2422
2885
  if (attemptedAgent) {
2423
- ui.write(` ${import_picocolors8.default.dim(import_picocolors8.default.gray("Note: agent was called but returned no usable enrichment. Edit the pending file or re-run dora init."))}
2886
+ ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("Note: agent was called but returned no usable enrichment. Edit the pending file or re-run dora init."))}
2424
2887
  `);
2425
2888
  } else {
2426
- ui.write(` ${import_picocolors8.default.dim(import_picocolors8.default.gray("Tip: run dora init to configure an agent for auto-enrichment."))}
2889
+ ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("Tip: run dora init to configure an agent for auto-enrichment."))}
2427
2890
  `);
2428
2891
  }
2429
2892
  }
@@ -2437,8 +2900,8 @@ var exports_sync = {};
2437
2900
  __export(exports_sync, {
2438
2901
  default: () => sync_default
2439
2902
  });
2440
- import { readdirSync as readdirSync2, existsSync as existsSync7 } from "fs";
2441
- import { join as join6 } from "path";
2903
+ import { readdirSync as readdirSync3, existsSync as existsSync9 } from "fs";
2904
+ import { join as join8 } from "path";
2442
2905
  var {spawnSync: spawnSync3 } = globalThis.Bun;
2443
2906
  function updateGitHubFile(repo, path, content, message, sha) {
2444
2907
  const payload = {
@@ -2467,18 +2930,18 @@ function updateGitHubFile(repo, path, content, message, sha) {
2467
2930
  stderr: "pipe"
2468
2931
  });
2469
2932
  if (result.exitCode !== 0) {
2470
- ui.write(import_picocolors9.default.red(`Failed to update ${path} on ${repo}:`));
2933
+ ui.write(import_picocolors10.default.red(`Failed to update ${path} on ${repo}:`));
2471
2934
  ui.write(result.stderr.toString());
2472
2935
  process.exit(1);
2473
2936
  }
2474
2937
  }
2475
- var import_picocolors9, sync_default;
2938
+ var import_picocolors10, sync_default;
2476
2939
  var init_sync = __esm(() => {
2477
2940
  init_dist();
2478
2941
  init_out();
2479
2942
  init_journal_config();
2480
2943
  init_journal_remote();
2481
- import_picocolors9 = __toESM(require_picocolors(), 1);
2944
+ import_picocolors10 = __toESM(require_picocolors(), 1);
2482
2945
  sync_default = defineCommand({
2483
2946
  meta: {
2484
2947
  name: "sync",
@@ -2512,70 +2975,70 @@ var init_sync = __esm(() => {
2512
2975
  project = sanitizeProjectName(project);
2513
2976
  }
2514
2977
  if (!project) {
2515
- ui.write(`${import_picocolors9.default.yellow("\u26A0")} No project mapping found.
2978
+ ui.write(`${import_picocolors10.default.yellow("\u26A0")} No project mapping found.
2516
2979
 
2517
- ` + `Run ${import_picocolors9.default.dim("dora init")} (or ${import_picocolors9.default.dim("doraval journal init")}) first, or pass ${import_picocolors9.default.dim("--project <name>")}.`);
2980
+ ` + `Run ${import_picocolors10.default.dim("dora init")} (or ${import_picocolors10.default.dim("doraval journal init")}) first, or pass ${import_picocolors10.default.dim("--project <name>")}.`);
2518
2981
  process.exit(1);
2519
2982
  }
2520
2983
  if (!config?.journal.repo) {
2521
- ui.write(`${import_picocolors9.default.red("\u2717")} No journal repo configured. Run ${import_picocolors9.default.dim("dora init")} (or ${import_picocolors9.default.dim("doraval journal init")}) first.`);
2984
+ ui.write(`${import_picocolors10.default.red("\u2717")} No journal repo configured. Run ${import_picocolors10.default.dim("dora init")} (or ${import_picocolors10.default.dim("doraval journal init")}) first.`);
2522
2985
  process.exit(1);
2523
2986
  }
2524
2987
  const ghCheck = ensureGhCli();
2525
2988
  if (!ghCheck.ok) {
2526
- ui.write(` ${import_picocolors9.default.red("\u2717")} ${import_picocolors9.default.white("The GitHub CLI (")}${import_picocolors9.default.bold("gh")}${import_picocolors9.default.white(") is not installed.")}
2989
+ ui.write(` ${import_picocolors10.default.red("\u2717")} ${import_picocolors10.default.white("The GitHub CLI (")}${import_picocolors10.default.bold("gh")}${import_picocolors10.default.white(") is not installed.")}
2527
2990
  `);
2528
- ui.write(` doraval uses ${import_picocolors9.default.bold("gh")} to fetch and sync journal files with GitHub.
2991
+ ui.write(` doraval uses ${import_picocolors10.default.bold("gh")} to fetch and sync journal files with GitHub.
2529
2992
  `);
2530
2993
  ui.write(` Install it:
2531
2994
  `);
2532
- ui.write(` macOS: ${import_picocolors9.default.dim("brew install gh")}`);
2533
- ui.write(` Linux: ${import_picocolors9.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
2534
- ui.write(` Windows: ${import_picocolors9.default.dim("winget install --id GitHub.cli")}
2995
+ ui.write(` macOS: ${import_picocolors10.default.dim("brew install gh")}`);
2996
+ ui.write(` Linux: ${import_picocolors10.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
2997
+ ui.write(` Windows: ${import_picocolors10.default.dim("winget install --id GitHub.cli")}
2535
2998
  `);
2536
- ui.write(` Then authenticate: ${import_picocolors9.default.dim("gh auth login")}
2999
+ ui.write(` Then authenticate: ${import_picocolors10.default.dim("gh auth login")}
2537
3000
  `);
2538
3001
  process.exit(1);
2539
3002
  }
2540
3003
  const journalRepo = config.journal.repo;
2541
3004
  const pendingDir = getPendingProjectDir(project);
2542
3005
  ui.write(`
2543
- ${import_picocolors9.default.bold(import_picocolors9.default.white("dora journal sync"))} \u2014 ${import_picocolors9.default.white(project)}
3006
+ ${import_picocolors10.default.bold(import_picocolors10.default.white("dora journal sync"))} \u2014 ${import_picocolors10.default.white(project)}
2544
3007
  `);
2545
- ui.write(` Journal repo: ${import_picocolors9.default.dim(import_picocolors9.default.gray(journalRepo))}`);
3008
+ ui.write(` Journal repo: ${import_picocolors10.default.dim(import_picocolors10.default.gray(journalRepo))}`);
2546
3009
  ensureDoravalDirs();
2547
3010
  const journalsDir = getJournalsDir();
2548
3011
  const remoteProjectPath = `projects/${project}.md`;
2549
- const localProjectPath = join6(journalsDir, `${project}.md`);
2550
- ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("Refreshing local cache from remote..."))}`);
2551
- const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md", join6(journalsDir, "global.md"));
3012
+ const localProjectPath = join8(journalsDir, `${project}.md`);
3013
+ ui.write(` ${import_picocolors10.default.dim(import_picocolors10.default.gray("Refreshing local cache from remote..."))}`);
3014
+ const refreshGlobalRes = await refreshLocalJournalFile(journalRepo, "global.md", join8(journalsDir, "global.md"));
2552
3015
  if (!refreshGlobalRes.ok) {
2553
3016
  if (!refreshGlobalRes.isNotFound) {
2554
- ui.write(import_picocolors9.default.red(`Failed to fetch global.md from ${journalRepo}:`));
3017
+ ui.write(import_picocolors10.default.red(`Failed to fetch global.md from ${journalRepo}:`));
2555
3018
  ui.write(refreshGlobalRes.error);
2556
3019
  process.exit(1);
2557
3020
  }
2558
3021
  }
2559
3022
  const gotGlobal = refreshGlobalRes.ok && refreshGlobalRes.value;
2560
3023
  if (gotGlobal) {
2561
- ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("\u2713 global.md"))}`);
3024
+ ui.write(` ${import_picocolors10.default.dim(import_picocolors10.default.gray("\u2713 global.md"))}`);
2562
3025
  }
2563
3026
  const refreshProjectCacheRes = await refreshLocalJournalFile(journalRepo, remoteProjectPath, localProjectPath);
2564
3027
  if (!refreshProjectCacheRes.ok) {
2565
3028
  if (!refreshProjectCacheRes.isNotFound) {
2566
- ui.write(import_picocolors9.default.red(`Failed to fetch ${remoteProjectPath} from ${journalRepo}:`));
3029
+ ui.write(import_picocolors10.default.red(`Failed to fetch ${remoteProjectPath} from ${journalRepo}:`));
2567
3030
  ui.write(refreshProjectCacheRes.error);
2568
3031
  process.exit(1);
2569
3032
  }
2570
3033
  }
2571
3034
  const gotProjectCache = refreshProjectCacheRes.ok && refreshProjectCacheRes.value;
2572
3035
  if (gotProjectCache) {
2573
- ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray(`\u2713 ${remoteProjectPath}`))}`);
3036
+ ui.write(` ${import_picocolors10.default.dim(import_picocolors10.default.gray(`\u2713 ${remoteProjectPath}`))}`);
2574
3037
  }
2575
- const pendingFiles = existsSync7(pendingDir) ? readdirSync2(pendingDir).filter((f) => f.endsWith(".md") && f !== ".gitkeep").sort() : [];
3038
+ const pendingFiles = existsSync9(pendingDir) ? readdirSync3(pendingDir).filter((f) => f.endsWith(".md") && f !== ".gitkeep").sort() : [];
2576
3039
  if (pendingFiles.length === 0) {
2577
3040
  ui.write(`
2578
- ${import_picocolors9.default.yellow("\u26A0")} No pending entries. Local cache is now up to date.
3041
+ ${import_picocolors10.default.yellow("\u26A0")} No pending entries. Local cache is now up to date.
2579
3042
  `);
2580
3043
  process.exit(0);
2581
3044
  }
@@ -2588,7 +3051,7 @@ var init_sync = __esm(() => {
2588
3051
  let currentFile = null;
2589
3052
  if (!metaRes.ok) {
2590
3053
  if (!metaRes.isNotFound) {
2591
- ui.write(import_picocolors9.default.red(`Failed to fetch ${remotePath} from ${journalRepo}:`));
3054
+ ui.write(import_picocolors10.default.red(`Failed to fetch ${remotePath} from ${journalRepo}:`));
2592
3055
  ui.write(metaRes.error);
2593
3056
  process.exit(1);
2594
3057
  }
@@ -2599,14 +3062,14 @@ var init_sync = __esm(() => {
2599
3062
  existingContent = Buffer.from(currentFile.content, "base64").toString("utf8");
2600
3063
  currentSha = currentFile.sha;
2601
3064
  if (args.verbose)
2602
- ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("Found existing remote file (sha: " + (currentSha?.slice(0, 7) ?? "") + "...)"))}`);
3065
+ ui.write(` ${import_picocolors10.default.dim(import_picocolors10.default.gray("Found existing remote file (sha: " + (currentSha?.slice(0, 7) ?? "") + "...)"))}`);
2603
3066
  } else {
2604
3067
  if (args.verbose)
2605
- ui.write(` ${import_picocolors9.default.dim(import_picocolors9.default.gray("No existing file on remote \u2014 will create it"))}`);
3068
+ ui.write(` ${import_picocolors10.default.dim(import_picocolors10.default.gray("No existing file on remote \u2014 will create it"))}`);
2606
3069
  }
2607
3070
  let newEntries = "";
2608
3071
  for (const file of pendingFiles) {
2609
- const fullPath = join6(pendingDir, file);
3072
+ const fullPath = join8(pendingDir, file);
2610
3073
  const entryContent = await Bun.file(fullPath).text();
2611
3074
  newEntries += `
2612
3075
  ` + entryContent.trim() + `
@@ -2626,36 +3089,36 @@ var init_sync = __esm(() => {
2626
3089
  const commitMessage = args.message || `journal: add ${pendingFiles.length} entr${pendingFiles.length === 1 ? "y" : "ies"} for ${project}`;
2627
3090
  if (args.verbose)
2628
3091
  ui.write(`
2629
- ${import_picocolors9.default.dim(import_picocolors9.default.gray("Pushing to remote..."))}`);
3092
+ ${import_picocolors10.default.dim(import_picocolors10.default.gray("Pushing to remote..."))}`);
2630
3093
  try {
2631
3094
  updateGitHubFile(journalRepo, remotePath, newContent, commitMessage, currentSha);
2632
- ui.write(` ${import_picocolors9.default.green("\u2713")} ${import_picocolors9.default.white("Successfully pushed to")} ${import_picocolors9.default.white(remotePath)}`);
3095
+ ui.write(` ${import_picocolors10.default.green("\u2713")} ${import_picocolors10.default.white("Successfully pushed to")} ${import_picocolors10.default.white(remotePath)}`);
2633
3096
  } catch (err) {
2634
- ui.write(`${import_picocolors9.default.red("\u2717")} ${import_picocolors9.default.white("Failed to push to GitHub.")}`);
3097
+ ui.write(`${import_picocolors10.default.red("\u2717")} ${import_picocolors10.default.white("Failed to push to GitHub.")}`);
2635
3098
  process.exit(1);
2636
3099
  }
2637
3100
  for (const file of pendingFiles) {
2638
- const fullPath = join6(pendingDir, file);
3101
+ const fullPath = join8(pendingDir, file);
2639
3102
  try {
2640
3103
  await Bun.file(fullPath).unlink();
2641
3104
  } catch {}
2642
3105
  }
2643
- ui.write(` ${import_picocolors9.default.green("\u2713")} ${import_picocolors9.default.white("Cleared local pending entries")}`);
3106
+ ui.write(` ${import_picocolors10.default.green("\u2713")} ${import_picocolors10.default.white("Cleared local pending entries")}`);
2644
3107
  try {
2645
3108
  const refreshRes = await refreshLocalJournalFile(journalRepo, remotePath, localProjectPath);
2646
3109
  if (!refreshRes.ok) {
2647
3110
  if (!refreshRes.isNotFound) {
2648
- ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
3111
+ ui.write(` ${import_picocolors10.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
2649
3112
  }
2650
3113
  } else if (refreshRes.value) {
2651
3114
  if (args.verbose)
2652
- ui.write(` ${import_picocolors9.default.green("\u2713")} ${import_picocolors9.default.white("Re-fetched")} ${import_picocolors9.default.white(project)}.md ${import_picocolors9.default.white("into local cache")}`);
3115
+ ui.write(` ${import_picocolors10.default.green("\u2713")} ${import_picocolors10.default.white("Re-fetched")} ${import_picocolors10.default.white(project)}.md ${import_picocolors10.default.white("into local cache")}`);
2653
3116
  }
2654
3117
  } catch {
2655
- ui.write(` ${import_picocolors9.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
3118
+ ui.write(` ${import_picocolors10.default.yellow("\u26A0")} Could not re-fetch updated file (you can run sync again later)`);
2656
3119
  }
2657
3120
  ui.write(`
2658
- ${import_picocolors9.default.green("Done!")} ${import_picocolors9.default.white(pendingFiles.length + " entr" + (pendingFiles.length === 1 ? "y" : "ies") + " published.")}
3121
+ ${import_picocolors10.default.green("Done!")} ${import_picocolors10.default.white(pendingFiles.length + " entr" + (pendingFiles.length === 1 ? "y" : "ies") + " published.")}
2659
3122
  `);
2660
3123
  process.exit(0);
2661
3124
  }
@@ -2714,15 +3177,15 @@ var init_spec = __esm(() => {
2714
3177
  });
2715
3178
 
2716
3179
  // src/cli/commands/claude/context.ts
2717
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
2718
- import { join as join7 } from "path";
3180
+ import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
3181
+ import { join as join9 } from "path";
2719
3182
  function detectContext(cwd = process.cwd()) {
2720
3183
  const claudeSpec = getProviderSpec("claude");
2721
- const hasClaudeDir = existsSync8(join7(cwd, ".claude"));
2722
- const hasPluginManifest = existsSync8(join7(cwd, claudeSpec.manifestPath));
3184
+ const hasClaudeDir = existsSync10(join9(cwd, ".claude"));
3185
+ const hasPluginManifest = existsSync10(join9(cwd, claudeSpec.manifestPath));
2723
3186
  let looseSkillFiles = [];
2724
3187
  try {
2725
- const files = readdirSync3(cwd);
3188
+ const files = readdirSync4(cwd);
2726
3189
  looseSkillFiles = files.filter((f) => {
2727
3190
  if (!f.endsWith(".md") || f.startsWith("."))
2728
3191
  return false;
@@ -2741,7 +3204,7 @@ function detectContext(cwd = process.cwd()) {
2741
3204
  isEmpty
2742
3205
  };
2743
3206
  }
2744
- var init_context = __esm(() => {
3207
+ var init_context2 = __esm(() => {
2745
3208
  init_spec();
2746
3209
  });
2747
3210
 
@@ -2752,8 +3215,8 @@ __export(exports_new, {
2752
3215
  default: () => new_default,
2753
3216
  decidePath: () => decidePath
2754
3217
  });
2755
- import { join as join8, basename as basename2, dirname } from "path";
2756
- import { mkdirSync as mkdirSync2, writeFileSync, existsSync as existsSync9 } from "fs";
3218
+ import { join as join10, basename as basename2, dirname as dirname2 } from "path";
3219
+ import { mkdirSync as mkdirSync3, writeFileSync, existsSync as existsSync11 } from "fs";
2757
3220
  function decidePath(ctx, intent, providedName) {
2758
3221
  const rawName = providedName || "";
2759
3222
  let decisionPath = "standalone";
@@ -2767,7 +3230,7 @@ function decidePath(ctx, intent, providedName) {
2767
3230
  targetDir = ctx.cwd;
2768
3231
  shouldCreateDir = false;
2769
3232
  } else {
2770
- targetDir = join8(ctx.cwd, rawName);
3233
+ targetDir = join10(ctx.cwd, rawName);
2771
3234
  shouldCreateDir = true;
2772
3235
  }
2773
3236
  migrateExisting = ctx.looseSkillFiles.length > 0;
@@ -2777,7 +3240,7 @@ function decidePath(ctx, intent, providedName) {
2777
3240
  targetDir = ctx.cwd;
2778
3241
  shouldCreateDir = false;
2779
3242
  } else {
2780
- targetDir = join8(ctx.cwd, rawName);
3243
+ targetDir = join10(ctx.cwd, rawName);
2781
3244
  shouldCreateDir = true;
2782
3245
  }
2783
3246
  } else if (decisionPath === "standalone") {
@@ -2785,7 +3248,7 @@ function decidePath(ctx, intent, providedName) {
2785
3248
  targetDir = ctx.cwd;
2786
3249
  shouldCreateDir = false;
2787
3250
  } else {
2788
- targetDir = join8(ctx.cwd, rawName);
3251
+ targetDir = join10(ctx.cwd, rawName);
2789
3252
  shouldCreateDir = true;
2790
3253
  }
2791
3254
  }
@@ -2793,24 +3256,25 @@ function decidePath(ctx, intent, providedName) {
2793
3256
  }
2794
3257
  function scaffold(decision, ctx, migrateContent) {
2795
3258
  const { targetDir, path, shouldCreateDir } = decision;
2796
- if (existsSync9(targetDir) && shouldCreateDir) {
3259
+ if (existsSync11(targetDir) && shouldCreateDir) {
2797
3260
  ui.fail("Target already exists");
2798
3261
  process.exit(1);
2799
3262
  }
2800
3263
  if (shouldCreateDir) {
2801
- mkdirSync2(targetDir, { recursive: true });
3264
+ mkdirSync3(targetDir, { recursive: true });
2802
3265
  }
2803
3266
  if (path === "plugin") {
2804
3267
  const pluginName = basename2(targetDir);
2805
3268
  const claudeSpec = getProviderSpec("claude");
2806
- const claudeManifestDir = dirname(claudeSpec.manifestPath);
3269
+ const claudeManifestDir = dirname2(claudeSpec.manifestPath);
2807
3270
  const pluginJson = {
2808
3271
  name: pluginName,
2809
3272
  description: "Scaffolded by doraval claude new",
2810
- version: "0.1.0"
3273
+ version: "0.1.0",
3274
+ keywords: ["example-keyword", "another-keyword"]
2811
3275
  };
2812
- mkdirSync2(join8(targetDir, claudeManifestDir), { recursive: true });
2813
- writeFileSync(join8(targetDir, claudeSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
3276
+ mkdirSync3(join10(targetDir, claudeManifestDir), { recursive: true });
3277
+ writeFileSync(join10(targetDir, claudeSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
2814
3278
  const marketplaceJson = {
2815
3279
  name: pluginName,
2816
3280
  version: "0.1.0",
@@ -2821,9 +3285,9 @@ function scaffold(decision, ctx, migrateContent) {
2821
3285
  license: "MIT",
2822
3286
  keywords: ["claude-code", "skills", "plugin"]
2823
3287
  };
2824
- writeFileSync(join8(targetDir, "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
3288
+ writeFileSync(join10(targetDir, "marketplace.json"), JSON.stringify(marketplaceJson, null, 2));
2825
3289
  const demoSkillName = "doraval";
2826
- mkdirSync2(join8(targetDir, "skills", demoSkillName), { recursive: true });
3290
+ mkdirSync3(join10(targetDir, "skills", demoSkillName), { recursive: true });
2827
3291
  let skillContent;
2828
3292
  if (migrateContent) {
2829
3293
  skillContent = migrateContent;
@@ -2847,19 +3311,19 @@ When you need to check a skill or plugin:
2847
3311
 
2848
3312
  Always run \`doraval validate\` before sharing or publishing a plugin. This skill demonstrates a complete, self-referential example of using doraval inside a generated plugin.`;
2849
3313
  }
2850
- writeFileSync(join8(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
2851
- const readmePath = join8(targetDir, "README.md");
2852
- if (!existsSync9(readmePath)) {
3314
+ writeFileSync(join10(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
3315
+ const readmePath = join10(targetDir, "README.md");
3316
+ if (!existsSync11(readmePath)) {
2853
3317
  writeFileSync(readmePath, "# " + pluginName + `
2854
3318
 
2855
3319
  Claude Code plugin scaffolded by doraval.`);
2856
3320
  }
2857
3321
  } else {
2858
- mkdirSync2(join8(targetDir, ".claude", "skills", "my-skill"), { recursive: true });
3322
+ mkdirSync3(join10(targetDir, ".claude", "skills", "my-skill"), { recursive: true });
2859
3323
  const skillBody = migrateContent || `# My Skill
2860
3324
 
2861
3325
  Basic starter.`;
2862
- writeFileSync(join8(targetDir, ".claude", "skills", "my-skill", "SKILL.md"), `---
3326
+ writeFileSync(join10(targetDir, ".claude", "skills", "my-skill", "SKILL.md"), `---
2863
3327
  name: my-skill
2864
3328
  description: Starter
2865
3329
  ---
@@ -2867,14 +3331,14 @@ description: Starter
2867
3331
  ${skillBody}`);
2868
3332
  }
2869
3333
  }
2870
- var import_picocolors10, new_default;
3334
+ var import_picocolors11, new_default;
2871
3335
  var init_new = __esm(() => {
2872
3336
  init_dist();
2873
3337
  init_out();
2874
- init_context();
3338
+ init_context2();
2875
3339
  init_prompt();
2876
3340
  init_spec();
2877
- import_picocolors10 = __toESM(require_picocolors(), 1);
3341
+ import_picocolors11 = __toESM(require_picocolors(), 1);
2878
3342
  new_default = defineCommand({
2879
3343
  meta: {
2880
3344
  name: "new",
@@ -2913,7 +3377,7 @@ var init_new = __esm(() => {
2913
3377
  }
2914
3378
  scaffold(decision, ctx, migrateContent);
2915
3379
  ui.write(`
2916
- ${import_picocolors10.default.green("\u2713")} Created ${decision.path} at ${import_picocolors10.default.bold(decision.targetDir)}`);
3380
+ ${import_picocolors11.default.green("\u2713")} Created ${decision.path} at ${import_picocolors11.default.bold(decision.targetDir)}`);
2917
3381
  const cmdName = decision.path === "plugin" ? `/${basename2(decision.targetDir)}:doraval` : "/my-skill";
2918
3382
  ui.info(` Command: ${cmdName}`);
2919
3383
  if (decision.path === "plugin") {
@@ -2923,6 +3387,9 @@ var init_new = __esm(() => {
2923
3387
  }
2924
3388
  ui.info(` Test: claude --plugin-dir ${decision.targetDir} (or use normally for standalone)`);
2925
3389
  ui.info(` Validate: doraval validate ${decision.targetDir}`);
3390
+ if (decision.path === "plugin") {
3391
+ ui.info(` Keywords: keywords array added for discovery \u2014 run validate to see "If users mention any of these keywords, your plugin will get triggered"`);
3392
+ }
2926
3393
  if (decision.path === "plugin" && decision.migrateExisting) {
2927
3394
  ui.info(" (Existing content migrated where confirmed.)");
2928
3395
  }
@@ -2936,8 +3403,8 @@ var exports_bump = {};
2936
3403
  __export(exports_bump, {
2937
3404
  default: () => bump_default
2938
3405
  });
2939
- import { resolve as resolve4, join as join9, dirname as dirname2, relative } from "path";
2940
- import { existsSync as existsSync10, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync4, statSync } from "fs";
3406
+ import { resolve as resolve4, join as join11, dirname as dirname3, relative } from "path";
3407
+ import { existsSync as existsSync12, readFileSync, writeFileSync as writeFileSync2, readdirSync as readdirSync5, statSync } from "fs";
2941
3408
  function bumpVersion(current, type) {
2942
3409
  if (/^\d+\.\d+\.\d+$/.test(type))
2943
3410
  return type;
@@ -2955,7 +3422,7 @@ function bumpVersion(current, type) {
2955
3422
  throw new Error(`Invalid bump type "${type}". Use patch, minor, major, or an exact version like 1.2.3`);
2956
3423
  }
2957
3424
  }
2958
- function readJson(p) {
3425
+ function readJson2(p) {
2959
3426
  try {
2960
3427
  const content = readFileSync(p, "utf8");
2961
3428
  return JSON.parse(content);
@@ -2963,7 +3430,7 @@ function readJson(p) {
2963
3430
  return null;
2964
3431
  }
2965
3432
  }
2966
- function writeJson(p, data) {
3433
+ function writeJson2(p, data) {
2967
3434
  writeFileSync2(p, JSON.stringify(data, null, 2) + `
2968
3435
  `, "utf8");
2969
3436
  }
@@ -3015,12 +3482,12 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
3015
3482
  return results;
3016
3483
  let entries;
3017
3484
  try {
3018
- entries = readdirSync4(dir);
3485
+ entries = readdirSync5(dir);
3019
3486
  } catch {
3020
3487
  return results;
3021
3488
  }
3022
3489
  for (const entry of entries) {
3023
- const full = join9(dir, entry);
3490
+ const full = join11(dir, entry);
3024
3491
  let st;
3025
3492
  try {
3026
3493
  st = statSync(full);
@@ -3032,7 +3499,7 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
3032
3499
  results.push(...sub);
3033
3500
  } else if (st.isFile()) {
3034
3501
  if (entry === "plugin.json") {
3035
- const parentDir = dirname2(full);
3502
+ const parentDir = dirname3(full);
3036
3503
  const parentName = parentDir.split(/[/\\]/).pop();
3037
3504
  if (parentName === ".claude-plugin" || parentName === ".codex-plugin" || parentName === ".cursor-plugin" || parentName === ".github") {
3038
3505
  results.push({
@@ -3042,7 +3509,7 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
3042
3509
  });
3043
3510
  }
3044
3511
  } else if (entry === "marketplace.json") {
3045
- const json = readJson(full);
3512
+ const json = readJson2(full);
3046
3513
  if (json && getVersion(json)) {
3047
3514
  results.push({
3048
3515
  file: full,
@@ -3055,11 +3522,11 @@ function walkForTargets(dir, maxDepth = 6, currentDepth = 0) {
3055
3522
  }
3056
3523
  return results;
3057
3524
  }
3058
- var import_picocolors11, bump_default;
3525
+ var import_picocolors12, bump_default;
3059
3526
  var init_bump = __esm(() => {
3060
3527
  init_dist();
3061
3528
  init_out();
3062
- import_picocolors11 = __toESM(require_picocolors(), 1);
3529
+ import_picocolors12 = __toESM(require_picocolors(), 1);
3063
3530
  bump_default = defineCommand({
3064
3531
  meta: {
3065
3532
  name: "bump",
@@ -3093,7 +3560,7 @@ var init_bump = __esm(() => {
3093
3560
  }
3094
3561
  const isKnownType = ["patch", "minor", "major"].includes(rawType) || /^\d+\.\d+\.\d+$/.test(rawType);
3095
3562
  const maybePath = resolve4(rawType);
3096
- const looksLikeDir = existsSync10(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
3563
+ const looksLikeDir = existsSync12(maybePath) || rawType === "." || rawType.startsWith("./") || rawType.startsWith("../");
3097
3564
  if (!isKnownType && looksLikeDir) {
3098
3565
  targetPath = rawType;
3099
3566
  rawType = "patch";
@@ -3102,7 +3569,7 @@ var init_bump = __esm(() => {
3102
3569
  process.exit(1);
3103
3570
  }
3104
3571
  const root = resolve4(targetPath);
3105
- if (!existsSync10(root)) {
3572
+ if (!existsSync12(root)) {
3106
3573
  ui.fail(`Path does not exist: ${root}`);
3107
3574
  process.exit(1);
3108
3575
  }
@@ -3137,7 +3604,7 @@ var init_bump = __esm(() => {
3137
3604
  ui.info(` matched ${targets.length} file(s)`);
3138
3605
  let bumpedCount = 0;
3139
3606
  for (const t of targets) {
3140
- const json = readJson(t.file);
3607
+ const json = readJson2(t.file);
3141
3608
  if (!json || typeof json !== "object") {
3142
3609
  ui.warnItem(`skipped (invalid JSON): ${relative(root, t.file)}`);
3143
3610
  continue;
@@ -3166,11 +3633,11 @@ var init_bump = __esm(() => {
3166
3633
  ui.warnItem(`skipped (could not locate version field to update): ${relPath}`);
3167
3634
  continue;
3168
3635
  }
3169
- writeJson(t.file, json);
3636
+ writeJson2(t.file, json);
3170
3637
  if (didRootUpdate && current) {
3171
- ui.success(`${t.label}: ${import_picocolors11.default.dim(current)} \u2192 ${import_picocolors11.default.green(next)}`);
3638
+ ui.success(`${t.label}: ${import_picocolors12.default.dim(current)} \u2192 ${import_picocolors12.default.green(next)}`);
3172
3639
  } else if (didRootUpdate) {
3173
- ui.success(`${t.label}: ${import_picocolors11.default.green(next)}`);
3640
+ ui.success(`${t.label}: ${import_picocolors12.default.green(next)}`);
3174
3641
  } else {
3175
3642
  ui.success(`${t.label} (no root version)`);
3176
3643
  }
@@ -3193,16 +3660,16 @@ var init_bump = __esm(() => {
3193
3660
  });
3194
3661
 
3195
3662
  // src/cli/commands/codex/context.ts
3196
- import { existsSync as existsSync11, readdirSync as readdirSync5 } from "fs";
3197
- import { join as join10 } from "path";
3663
+ import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
3664
+ import { join as join12 } from "path";
3198
3665
  function detectContext2(cwd = process.cwd()) {
3199
3666
  const codexSpec = getProviderSpec("codex");
3200
- const hasCodexDir = existsSync11(join10(cwd, ".codex"));
3201
- const hasPluginManifest = existsSync11(join10(cwd, codexSpec.manifestPath));
3202
- const hasMarketplace = existsSync11(join10(cwd, ".agents", "plugins", "marketplace.json")) || existsSync11(join10(cwd, codexSpec.manifestPath));
3667
+ const hasCodexDir = existsSync13(join12(cwd, ".codex"));
3668
+ const hasPluginManifest = existsSync13(join12(cwd, codexSpec.manifestPath));
3669
+ const hasMarketplace = existsSync13(join12(cwd, ".agents", "plugins", "marketplace.json")) || existsSync13(join12(cwd, codexSpec.manifestPath));
3203
3670
  let looseSkillFiles = [];
3204
3671
  try {
3205
- const files = readdirSync5(cwd);
3672
+ const files = readdirSync6(cwd);
3206
3673
  looseSkillFiles = files.filter((f) => {
3207
3674
  if (!f.endsWith(".md") || f.startsWith("."))
3208
3675
  return false;
@@ -3222,7 +3689,7 @@ function detectContext2(cwd = process.cwd()) {
3222
3689
  isEmpty
3223
3690
  };
3224
3691
  }
3225
- var init_context2 = __esm(() => {
3692
+ var init_context3 = __esm(() => {
3226
3693
  init_spec();
3227
3694
  });
3228
3695
 
@@ -3233,8 +3700,8 @@ __export(exports_new2, {
3233
3700
  default: () => new_default2,
3234
3701
  decidePath: () => decidePath2
3235
3702
  });
3236
- import { join as join11, basename as basename3, dirname as dirname3 } from "path";
3237
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync12 } from "fs";
3703
+ import { join as join13, basename as basename3, dirname as dirname4 } from "path";
3704
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, existsSync as existsSync14 } from "fs";
3238
3705
  function decidePath2(ctx, intent, providedName) {
3239
3706
  const rawName = providedName || "";
3240
3707
  let decisionPath = "standalone";
@@ -3248,7 +3715,7 @@ function decidePath2(ctx, intent, providedName) {
3248
3715
  targetDir = ctx.cwd;
3249
3716
  shouldCreateDir = false;
3250
3717
  } else {
3251
- targetDir = join11(ctx.cwd, rawName);
3718
+ targetDir = join13(ctx.cwd, rawName);
3252
3719
  shouldCreateDir = true;
3253
3720
  }
3254
3721
  migrateExisting = ctx.looseSkillFiles.length > 0;
@@ -3258,7 +3725,7 @@ function decidePath2(ctx, intent, providedName) {
3258
3725
  targetDir = ctx.cwd;
3259
3726
  shouldCreateDir = false;
3260
3727
  } else {
3261
- targetDir = join11(ctx.cwd, rawName);
3728
+ targetDir = join13(ctx.cwd, rawName);
3262
3729
  shouldCreateDir = true;
3263
3730
  }
3264
3731
  } else if (decisionPath === "standalone") {
@@ -3266,7 +3733,7 @@ function decidePath2(ctx, intent, providedName) {
3266
3733
  targetDir = ctx.cwd;
3267
3734
  shouldCreateDir = false;
3268
3735
  } else {
3269
- targetDir = join11(ctx.cwd, rawName);
3736
+ targetDir = join13(ctx.cwd, rawName);
3270
3737
  shouldCreateDir = true;
3271
3738
  }
3272
3739
  }
@@ -3274,17 +3741,17 @@ function decidePath2(ctx, intent, providedName) {
3274
3741
  }
3275
3742
  function scaffold2(decision, ctx, migrateContent) {
3276
3743
  const { targetDir, path, shouldCreateDir } = decision;
3277
- if (existsSync12(targetDir) && shouldCreateDir) {
3744
+ if (existsSync14(targetDir) && shouldCreateDir) {
3278
3745
  ui.fail("Target already exists");
3279
3746
  process.exit(1);
3280
3747
  }
3281
3748
  if (shouldCreateDir) {
3282
- mkdirSync3(targetDir, { recursive: true });
3749
+ mkdirSync4(targetDir, { recursive: true });
3283
3750
  }
3284
3751
  if (path === "plugin") {
3285
3752
  const pluginName = basename3(targetDir);
3286
3753
  const codexSpec = getProviderSpec("codex");
3287
- const codexManifestDir = dirname3(codexSpec.manifestPath);
3754
+ const codexManifestDir = dirname4(codexSpec.manifestPath);
3288
3755
  const pluginJson = {
3289
3756
  name: pluginName,
3290
3757
  version: "0.1.0",
@@ -3294,12 +3761,13 @@ function scaffold2(decision, ctx, migrateContent) {
3294
3761
  displayName: pluginName,
3295
3762
  shortDescription: "Scaffolded starter plugin",
3296
3763
  category: "Productivity"
3297
- }
3764
+ },
3765
+ keywords: ["example-keyword", "another-keyword"]
3298
3766
  };
3299
- mkdirSync3(join11(targetDir, codexManifestDir), { recursive: true });
3300
- writeFileSync3(join11(targetDir, codexSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
3301
- const marketplaceDir = dirname3(codexSpec.marketplacePath);
3302
- mkdirSync3(join11(targetDir, marketplaceDir), { recursive: true });
3767
+ mkdirSync4(join13(targetDir, codexManifestDir), { recursive: true });
3768
+ writeFileSync3(join13(targetDir, codexSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
3769
+ const marketplaceDir = dirname4(codexSpec.marketplacePath);
3770
+ mkdirSync4(join13(targetDir, marketplaceDir), { recursive: true });
3303
3771
  const marketplaceJson = {
3304
3772
  name: "local",
3305
3773
  interface: {
@@ -3320,9 +3788,9 @@ function scaffold2(decision, ctx, migrateContent) {
3320
3788
  }
3321
3789
  ]
3322
3790
  };
3323
- writeFileSync3(join11(targetDir, codexSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
3791
+ writeFileSync3(join13(targetDir, codexSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
3324
3792
  const demoSkillName = "doraval";
3325
- mkdirSync3(join11(targetDir, "skills", demoSkillName), { recursive: true });
3793
+ mkdirSync4(join13(targetDir, "skills", demoSkillName), { recursive: true });
3326
3794
  let skillContent;
3327
3795
  if (migrateContent) {
3328
3796
  skillContent = migrateContent;
@@ -3353,19 +3821,19 @@ To test in Codex:
3353
3821
  3. Open the plugin directory, select your local marketplace, and enable the plugin.
3354
3822
  4. Invoke the demo with /${pluginName}:doraval`;
3355
3823
  }
3356
- writeFileSync3(join11(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
3357
- const readmePath = join11(targetDir, "README.md");
3358
- if (!existsSync12(readmePath)) {
3824
+ writeFileSync3(join13(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
3825
+ const readmePath = join13(targetDir, "README.md");
3826
+ if (!existsSync14(readmePath)) {
3359
3827
  writeFileSync3(readmePath, "# " + pluginName + `
3360
3828
 
3361
3829
  Codex plugin scaffolded by doraval.`);
3362
3830
  }
3363
3831
  } else {
3364
- mkdirSync3(join11(targetDir, "skills", "doraval"), { recursive: true });
3832
+ mkdirSync4(join13(targetDir, "skills", "doraval"), { recursive: true });
3365
3833
  const skillBody = migrateContent || `# My Skill
3366
3834
 
3367
3835
  Basic starter for Codex.`;
3368
- writeFileSync3(join11(targetDir, "skills", "doraval", "SKILL.md"), `---
3836
+ writeFileSync3(join13(targetDir, "skills", "doraval", "SKILL.md"), `---
3369
3837
  name: doraval
3370
3838
  description: Starter (local skill)
3371
3839
  ---
@@ -3373,14 +3841,14 @@ description: Starter (local skill)
3373
3841
  ${skillBody}`);
3374
3842
  }
3375
3843
  }
3376
- var import_picocolors12, new_default2;
3844
+ var import_picocolors13, new_default2;
3377
3845
  var init_new2 = __esm(() => {
3378
3846
  init_dist();
3379
3847
  init_out();
3380
- init_context2();
3848
+ init_context3();
3381
3849
  init_prompt();
3382
3850
  init_spec();
3383
- import_picocolors12 = __toESM(require_picocolors(), 1);
3851
+ import_picocolors13 = __toESM(require_picocolors(), 1);
3384
3852
  new_default2 = defineCommand({
3385
3853
  meta: {
3386
3854
  name: "new",
@@ -3419,7 +3887,7 @@ var init_new2 = __esm(() => {
3419
3887
  }
3420
3888
  scaffold2(decision, ctx, migrateContent);
3421
3889
  ui.write(`
3422
- ${import_picocolors12.default.green("\u2713")} Created ${decision.path} at ${import_picocolors12.default.bold(decision.targetDir)}`);
3890
+ ${import_picocolors13.default.green("\u2713")} Created ${decision.path} at ${import_picocolors13.default.bold(decision.targetDir)}`);
3423
3891
  const cmdName = decision.path === "plugin" ? `/${basename3(decision.targetDir)}:doraval` : "/doraval (local skill)";
3424
3892
  ui.info(` Command: ${cmdName}`);
3425
3893
  if (decision.path === "plugin") {
@@ -3429,6 +3897,9 @@ var init_new2 = __esm(() => {
3429
3897
  }
3430
3898
  ui.info(` Test (local): restart Codex, select your marketplace in the plugin directory`);
3431
3899
  ui.info(` Validate: doraval validate ${decision.targetDir}`);
3900
+ if (decision.path === "plugin") {
3901
+ ui.info(` Keywords: keywords array added for discovery \u2014 run validate to see "If users mention any of these keywords, your plugin will get triggered"`);
3902
+ }
3432
3903
  if (decision.path === "plugin" && decision.migrateExisting) {
3433
3904
  ui.info(" (Existing content migrated where confirmed.)");
3434
3905
  }
@@ -3438,14 +3909,14 @@ var init_new2 = __esm(() => {
3438
3909
  });
3439
3910
 
3440
3911
  // src/cli/commands/cursor/context.ts
3441
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
3442
- import { join as join12 } from "path";
3912
+ import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
3913
+ import { join as join14 } from "path";
3443
3914
  function detectContext3(cwd = process.cwd()) {
3444
- const hasCursorDir = existsSync13(join12(cwd, ".cursor"));
3445
- const hasPluginManifest = existsSync13(join12(cwd, ".cursor-plugin", "plugin.json"));
3915
+ const hasCursorDir = existsSync15(join14(cwd, ".cursor"));
3916
+ const hasPluginManifest = existsSync15(join14(cwd, ".cursor-plugin", "plugin.json"));
3446
3917
  let looseSkillFiles = [];
3447
3918
  try {
3448
- const files = readdirSync6(cwd);
3919
+ const files = readdirSync7(cwd);
3449
3920
  looseSkillFiles = files.filter((f) => {
3450
3921
  if (!f.endsWith(".md") || f.startsWith("."))
3451
3922
  return false;
@@ -3464,7 +3935,7 @@ function detectContext3(cwd = process.cwd()) {
3464
3935
  isEmpty
3465
3936
  };
3466
3937
  }
3467
- var init_context3 = () => {};
3938
+ var init_context4 = () => {};
3468
3939
 
3469
3940
  // src/cli/commands/cursor/new.ts
3470
3941
  var exports_new3 = {};
@@ -3473,8 +3944,8 @@ __export(exports_new3, {
3473
3944
  default: () => new_default3,
3474
3945
  decidePath: () => decidePath3
3475
3946
  });
3476
- import { join as join13, basename as basename4, dirname as dirname4 } from "path";
3477
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync14 } from "fs";
3947
+ import { join as join15, basename as basename4, dirname as dirname5 } from "path";
3948
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, existsSync as existsSync16 } from "fs";
3478
3949
  function decidePath3(ctx, intent, providedName) {
3479
3950
  const rawName = providedName || "";
3480
3951
  let decisionPath = "standalone";
@@ -3488,7 +3959,7 @@ function decidePath3(ctx, intent, providedName) {
3488
3959
  targetDir = ctx.cwd;
3489
3960
  shouldCreateDir = false;
3490
3961
  } else {
3491
- targetDir = join13(ctx.cwd, rawName);
3962
+ targetDir = join15(ctx.cwd, rawName);
3492
3963
  shouldCreateDir = true;
3493
3964
  }
3494
3965
  migrateExisting = ctx.looseSkillFiles.length > 0;
@@ -3498,7 +3969,7 @@ function decidePath3(ctx, intent, providedName) {
3498
3969
  targetDir = ctx.cwd;
3499
3970
  shouldCreateDir = false;
3500
3971
  } else {
3501
- targetDir = join13(ctx.cwd, rawName);
3972
+ targetDir = join15(ctx.cwd, rawName);
3502
3973
  shouldCreateDir = true;
3503
3974
  }
3504
3975
  } else if (decisionPath === "standalone") {
@@ -3506,7 +3977,7 @@ function decidePath3(ctx, intent, providedName) {
3506
3977
  targetDir = ctx.cwd;
3507
3978
  shouldCreateDir = false;
3508
3979
  } else {
3509
- targetDir = join13(ctx.cwd, rawName);
3980
+ targetDir = join15(ctx.cwd, rawName);
3510
3981
  shouldCreateDir = true;
3511
3982
  }
3512
3983
  }
@@ -3514,28 +3985,29 @@ function decidePath3(ctx, intent, providedName) {
3514
3985
  }
3515
3986
  function scaffold3(decision, ctx, migrateContent) {
3516
3987
  const { targetDir, path, shouldCreateDir } = decision;
3517
- if (existsSync14(targetDir) && shouldCreateDir) {
3988
+ if (existsSync16(targetDir) && shouldCreateDir) {
3518
3989
  ui.fail("Target already exists");
3519
3990
  process.exit(1);
3520
3991
  }
3521
3992
  if (shouldCreateDir) {
3522
- mkdirSync4(targetDir, { recursive: true });
3993
+ mkdirSync5(targetDir, { recursive: true });
3523
3994
  }
3524
3995
  if (path === "plugin") {
3525
3996
  const pluginName = basename4(targetDir);
3526
3997
  const cursorSpec = getProviderSpec("cursor");
3527
- const cursorManifestDir = dirname4(cursorSpec.manifestPath);
3998
+ const cursorManifestDir = dirname5(cursorSpec.manifestPath);
3528
3999
  const pluginJson = {
3529
4000
  name: pluginName,
3530
4001
  version: "0.1.0",
3531
4002
  description: "Scaffolded by doraval cursor new",
3532
4003
  skills: "./skills/",
3533
- displayName: pluginName
4004
+ displayName: pluginName,
4005
+ keywords: ["example-keyword", "another-keyword"]
3534
4006
  };
3535
- mkdirSync4(join13(targetDir, cursorManifestDir), { recursive: true });
3536
- writeFileSync4(join13(targetDir, cursorSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
3537
- const marketplaceDir = dirname4(cursorSpec.marketplacePath);
3538
- mkdirSync4(join13(targetDir, marketplaceDir), { recursive: true });
4007
+ mkdirSync5(join15(targetDir, cursorManifestDir), { recursive: true });
4008
+ writeFileSync4(join15(targetDir, cursorSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
4009
+ const marketplaceDir = dirname5(cursorSpec.marketplacePath);
4010
+ mkdirSync5(join15(targetDir, marketplaceDir), { recursive: true });
3539
4011
  const marketplaceJson = {
3540
4012
  name: pluginName,
3541
4013
  version: "0.1.0",
@@ -3546,9 +4018,9 @@ function scaffold3(decision, ctx, migrateContent) {
3546
4018
  license: "MIT",
3547
4019
  keywords: ["cursor", "skills", "plugin"]
3548
4020
  };
3549
- writeFileSync4(join13(targetDir, cursorSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
4021
+ writeFileSync4(join15(targetDir, cursorSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
3550
4022
  const demoSkillName = "doraval";
3551
- mkdirSync4(join13(targetDir, "skills", demoSkillName), { recursive: true });
4023
+ mkdirSync5(join15(targetDir, "skills", demoSkillName), { recursive: true });
3552
4024
  let skillContent;
3553
4025
  if (migrateContent) {
3554
4026
  skillContent = migrateContent;
@@ -3577,19 +4049,19 @@ To test in Cursor:
3577
4049
  1. Open the plugin directory or add via marketplace.
3578
4050
  2. The demo skill will be available.`;
3579
4051
  }
3580
- writeFileSync4(join13(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
3581
- const readmePath = join13(targetDir, "README.md");
3582
- if (!existsSync14(readmePath)) {
4052
+ writeFileSync4(join15(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
4053
+ const readmePath = join15(targetDir, "README.md");
4054
+ if (!existsSync16(readmePath)) {
3583
4055
  writeFileSync4(readmePath, "# " + pluginName + `
3584
4056
 
3585
4057
  Cursor plugin scaffolded by doraval.`);
3586
4058
  }
3587
4059
  } else {
3588
- mkdirSync4(join13(targetDir, "skills", "doraval"), { recursive: true });
4060
+ mkdirSync5(join15(targetDir, "skills", "doraval"), { recursive: true });
3589
4061
  const skillBody = migrateContent || `# My Skill
3590
4062
 
3591
4063
  Basic starter for Cursor.`;
3592
- writeFileSync4(join13(targetDir, "skills", "doraval", "SKILL.md"), `---
4064
+ writeFileSync4(join15(targetDir, "skills", "doraval", "SKILL.md"), `---
3593
4065
  name: doraval
3594
4066
  description: Starter (local skill)
3595
4067
  ---
@@ -3597,14 +4069,14 @@ description: Starter (local skill)
3597
4069
  ${skillBody}`);
3598
4070
  }
3599
4071
  }
3600
- var import_picocolors13, new_default3;
4072
+ var import_picocolors14, new_default3;
3601
4073
  var init_new3 = __esm(() => {
3602
4074
  init_dist();
3603
4075
  init_out();
3604
- init_context3();
4076
+ init_context4();
3605
4077
  init_prompt();
3606
4078
  init_spec();
3607
- import_picocolors13 = __toESM(require_picocolors(), 1);
4079
+ import_picocolors14 = __toESM(require_picocolors(), 1);
3608
4080
  new_default3 = defineCommand({
3609
4081
  meta: {
3610
4082
  name: "new",
@@ -3643,7 +4115,7 @@ var init_new3 = __esm(() => {
3643
4115
  }
3644
4116
  scaffold3(decision, ctx, migrateContent);
3645
4117
  ui.write(`
3646
- ${import_picocolors13.default.green("\u2713")} Created ${decision.path} at ${import_picocolors13.default.bold(decision.targetDir)}`);
4118
+ ${import_picocolors14.default.green("\u2713")} Created ${decision.path} at ${import_picocolors14.default.bold(decision.targetDir)}`);
3647
4119
  const cmdName = decision.path === "plugin" ? `/${basename4(decision.targetDir)}:doraval` : "/doraval (local skill)";
3648
4120
  ui.info(` Command: ${cmdName}`);
3649
4121
  if (decision.path === "plugin") {
@@ -3652,6 +4124,9 @@ var init_new3 = __esm(() => {
3652
4124
  }
3653
4125
  ui.info(` Test (local): add the plugin dir in Cursor settings or use local skills`);
3654
4126
  ui.info(` Validate: doraval validate ${decision.targetDir}`);
4127
+ if (decision.path === "plugin") {
4128
+ ui.info(` Keywords: keywords array added for discovery \u2014 run validate to see "If users mention any of these keywords, your plugin will get triggered"`);
4129
+ }
3655
4130
  if (decision.path === "plugin" && decision.migrateExisting) {
3656
4131
  ui.info(" (Existing content migrated where confirmed.)");
3657
4132
  }
@@ -3661,14 +4136,14 @@ var init_new3 = __esm(() => {
3661
4136
  });
3662
4137
 
3663
4138
  // src/cli/commands/copilot/context.ts
3664
- import { existsSync as existsSync15, readdirSync as readdirSync7 } from "fs";
3665
- import { join as join14 } from "path";
4139
+ import { existsSync as existsSync17, readdirSync as readdirSync8 } from "fs";
4140
+ import { join as join16 } from "path";
3666
4141
  function detectContext4(cwd = process.cwd()) {
3667
- const hasGithubDir = existsSync15(join14(cwd, ".github"));
3668
- const hasPluginManifest = existsSync15(join14(cwd, ".github", "plugin", "plugin.json"));
4142
+ const hasGithubDir = existsSync17(join16(cwd, ".github"));
4143
+ const hasPluginManifest = existsSync17(join16(cwd, ".github", "plugin", "plugin.json"));
3669
4144
  let looseSkillFiles = [];
3670
4145
  try {
3671
- const files = readdirSync7(cwd);
4146
+ const files = readdirSync8(cwd);
3672
4147
  looseSkillFiles = files.filter((f) => {
3673
4148
  if (!f.endsWith(".md") || f.startsWith("."))
3674
4149
  return false;
@@ -3687,7 +4162,7 @@ function detectContext4(cwd = process.cwd()) {
3687
4162
  isEmpty
3688
4163
  };
3689
4164
  }
3690
- var init_context4 = () => {};
4165
+ var init_context5 = () => {};
3691
4166
 
3692
4167
  // src/cli/commands/copilot/new.ts
3693
4168
  var exports_new4 = {};
@@ -3696,8 +4171,8 @@ __export(exports_new4, {
3696
4171
  default: () => new_default4,
3697
4172
  decidePath: () => decidePath4
3698
4173
  });
3699
- import { join as join15, basename as basename5, dirname as dirname5 } from "path";
3700
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, existsSync as existsSync16 } from "fs";
4174
+ import { join as join17, basename as basename5, dirname as dirname6 } from "path";
4175
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5, existsSync as existsSync18 } from "fs";
3701
4176
  function decidePath4(ctx, intent, providedName) {
3702
4177
  const rawName = providedName || "";
3703
4178
  let decisionPath = "standalone";
@@ -3711,7 +4186,7 @@ function decidePath4(ctx, intent, providedName) {
3711
4186
  targetDir = ctx.cwd;
3712
4187
  shouldCreateDir = false;
3713
4188
  } else {
3714
- targetDir = join15(ctx.cwd, rawName);
4189
+ targetDir = join17(ctx.cwd, rawName);
3715
4190
  shouldCreateDir = true;
3716
4191
  }
3717
4192
  migrateExisting = ctx.looseSkillFiles.length > 0;
@@ -3721,7 +4196,7 @@ function decidePath4(ctx, intent, providedName) {
3721
4196
  targetDir = ctx.cwd;
3722
4197
  shouldCreateDir = false;
3723
4198
  } else {
3724
- targetDir = join15(ctx.cwd, rawName);
4199
+ targetDir = join17(ctx.cwd, rawName);
3725
4200
  shouldCreateDir = true;
3726
4201
  }
3727
4202
  } else if (decisionPath === "standalone") {
@@ -3729,7 +4204,7 @@ function decidePath4(ctx, intent, providedName) {
3729
4204
  targetDir = ctx.cwd;
3730
4205
  shouldCreateDir = false;
3731
4206
  } else {
3732
- targetDir = join15(ctx.cwd, rawName);
4207
+ targetDir = join17(ctx.cwd, rawName);
3733
4208
  shouldCreateDir = true;
3734
4209
  }
3735
4210
  }
@@ -3737,28 +4212,29 @@ function decidePath4(ctx, intent, providedName) {
3737
4212
  }
3738
4213
  function scaffold4(decision, ctx, migrateContent) {
3739
4214
  const { targetDir, path, shouldCreateDir } = decision;
3740
- if (existsSync16(targetDir) && shouldCreateDir) {
4215
+ if (existsSync18(targetDir) && shouldCreateDir) {
3741
4216
  ui.fail("Target already exists");
3742
4217
  process.exit(1);
3743
4218
  }
3744
4219
  if (shouldCreateDir) {
3745
- mkdirSync5(targetDir, { recursive: true });
4220
+ mkdirSync6(targetDir, { recursive: true });
3746
4221
  }
3747
4222
  if (path === "plugin") {
3748
4223
  const pluginName = basename5(targetDir);
3749
4224
  const copilotSpec = getProviderSpec("copilot");
3750
- const copilotManifestDir = dirname5(copilotSpec.manifestPath);
4225
+ const copilotManifestDir = dirname6(copilotSpec.manifestPath);
3751
4226
  const pluginJson = {
3752
4227
  name: pluginName,
3753
4228
  version: "0.1.0",
3754
4229
  description: "Scaffolded by doraval copilot new",
3755
4230
  skills: ["./skills/doraval"],
3756
- displayName: pluginName
4231
+ displayName: pluginName,
4232
+ keywords: ["example-keyword", "another-keyword"]
3757
4233
  };
3758
- mkdirSync5(join15(targetDir, copilotManifestDir), { recursive: true });
3759
- writeFileSync5(join15(targetDir, copilotSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
3760
- const marketplaceDir = dirname5(copilotSpec.marketplacePath);
3761
- mkdirSync5(join15(targetDir, marketplaceDir), { recursive: true });
4234
+ mkdirSync6(join17(targetDir, copilotManifestDir), { recursive: true });
4235
+ writeFileSync5(join17(targetDir, copilotSpec.manifestPath), JSON.stringify(pluginJson, null, 2));
4236
+ const marketplaceDir = dirname6(copilotSpec.marketplacePath);
4237
+ mkdirSync6(join17(targetDir, marketplaceDir), { recursive: true });
3762
4238
  const marketplaceJson = {
3763
4239
  name: "local",
3764
4240
  plugins: [
@@ -3771,9 +4247,9 @@ function scaffold4(decision, ctx, migrateContent) {
3771
4247
  }
3772
4248
  ]
3773
4249
  };
3774
- writeFileSync5(join15(targetDir, copilotSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
4250
+ writeFileSync5(join17(targetDir, copilotSpec.marketplacePath), JSON.stringify(marketplaceJson, null, 2));
3775
4251
  const demoSkillName = "doraval";
3776
- mkdirSync5(join15(targetDir, "skills", demoSkillName), { recursive: true });
4252
+ mkdirSync6(join17(targetDir, "skills", demoSkillName), { recursive: true });
3777
4253
  let skillContent;
3778
4254
  if (migrateContent) {
3779
4255
  skillContent = migrateContent;
@@ -3802,19 +4278,19 @@ To test in Copilot:
3802
4278
  1. Configure the .github/plugin as local source.
3803
4279
  2. Restart/reload and invoke the skill.`;
3804
4280
  }
3805
- writeFileSync5(join15(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
3806
- const readmePath = join15(targetDir, "README.md");
3807
- if (!existsSync16(readmePath)) {
4281
+ writeFileSync5(join17(targetDir, "skills", demoSkillName, "SKILL.md"), skillContent);
4282
+ const readmePath = join17(targetDir, "README.md");
4283
+ if (!existsSync18(readmePath)) {
3808
4284
  writeFileSync5(readmePath, "# " + pluginName + `
3809
4285
 
3810
4286
  Copilot plugin scaffolded by doraval.`);
3811
4287
  }
3812
4288
  } else {
3813
- mkdirSync5(join15(targetDir, "skills", "doraval"), { recursive: true });
4289
+ mkdirSync6(join17(targetDir, "skills", "doraval"), { recursive: true });
3814
4290
  const skillBody = migrateContent || `# My Skill
3815
4291
 
3816
4292
  Basic starter for Copilot.`;
3817
- writeFileSync5(join15(targetDir, "skills", "doraval", "SKILL.md"), `---
4293
+ writeFileSync5(join17(targetDir, "skills", "doraval", "SKILL.md"), `---
3818
4294
  name: doraval
3819
4295
  description: Starter (local skill)
3820
4296
  ---
@@ -3822,14 +4298,14 @@ description: Starter (local skill)
3822
4298
  ${skillBody}`);
3823
4299
  }
3824
4300
  }
3825
- var import_picocolors14, new_default4;
4301
+ var import_picocolors15, new_default4;
3826
4302
  var init_new4 = __esm(() => {
3827
4303
  init_dist();
3828
4304
  init_out();
3829
- init_context4();
4305
+ init_context5();
3830
4306
  init_prompt();
3831
4307
  init_spec();
3832
- import_picocolors14 = __toESM(require_picocolors(), 1);
4308
+ import_picocolors15 = __toESM(require_picocolors(), 1);
3833
4309
  new_default4 = defineCommand({
3834
4310
  meta: {
3835
4311
  name: "new",
@@ -3868,7 +4344,7 @@ var init_new4 = __esm(() => {
3868
4344
  }
3869
4345
  scaffold4(decision, ctx, migrateContent);
3870
4346
  ui.write(`
3871
- ${import_picocolors14.default.green("\u2713")} Created ${decision.path} at ${import_picocolors14.default.bold(decision.targetDir)}`);
4347
+ ${import_picocolors15.default.green("\u2713")} Created ${decision.path} at ${import_picocolors15.default.bold(decision.targetDir)}`);
3872
4348
  const cmdName = decision.path === "plugin" ? `/${basename5(decision.targetDir)}:doraval` : "/doraval (local skill)";
3873
4349
  ui.info(` Command: ${cmdName}`);
3874
4350
  if (decision.path === "plugin") {
@@ -3877,6 +4353,9 @@ var init_new4 = __esm(() => {
3877
4353
  }
3878
4354
  ui.info(` Test (local): configure local plugin source in Copilot and reload`);
3879
4355
  ui.info(` Validate: doraval validate ${decision.targetDir}`);
4356
+ if (decision.path === "plugin") {
4357
+ ui.info(` Keywords: keywords array added for discovery \u2014 run validate to see "If users mention any of these keywords, your plugin will get triggered"`);
4358
+ }
3880
4359
  if (decision.path === "plugin" && decision.migrateExisting) {
3881
4360
  ui.info(" (Existing content migrated where confirmed.)");
3882
4361
  }
@@ -3885,8 +4364,240 @@ var init_new4 = __esm(() => {
3885
4364
  });
3886
4365
  });
3887
4366
 
4367
+ // src/cli/commands/ui.ts
4368
+ var exports_ui = {};
4369
+ __export(exports_ui, {
4370
+ default: () => ui_default
4371
+ });
4372
+ import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
4373
+ import { join as join18 } from "path";
4374
+ import { spawn } from "child_process";
4375
+ function slugify2(title) {
4376
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "untitled";
4377
+ }
4378
+ async function loadAllEntries(project) {
4379
+ const journalsDir = getJournalsDir();
4380
+ const entries = [];
4381
+ const globalPath = join18(journalsDir, "global.md");
4382
+ if (existsSync19(globalPath)) {
4383
+ try {
4384
+ const raw = await Bun.file(globalPath).text();
4385
+ const parsed = parseJournalEntries(raw);
4386
+ parsed.forEach((e) => entries.push({ ...e, _source: "global" }));
4387
+ } catch {}
4388
+ }
4389
+ if (project) {
4390
+ const projPath = join18(journalsDir, `${project}.md`);
4391
+ if (existsSync19(projPath)) {
4392
+ try {
4393
+ const raw = await Bun.file(projPath).text();
4394
+ const parsed = parseJournalEntries(raw);
4395
+ parsed.forEach((e) => entries.push({ ...e, _source: "project" }));
4396
+ } catch {}
4397
+ }
4398
+ }
4399
+ const staged = [];
4400
+ try {
4401
+ const pdir = project ? getPendingProjectDir(project) : null;
4402
+ if (pdir && existsSync19(pdir)) {
4403
+ const files = readdirSync9(pdir).filter((f) => f.endsWith(".md") && f !== ".gitkeep");
4404
+ for (const f of files) {
4405
+ const txt = await Bun.file(join18(pdir, f)).text();
4406
+ const parsed = parseJournalEntries(txt);
4407
+ parsed.forEach((e) => {
4408
+ e._staged = true;
4409
+ e._source = "staged";
4410
+ staged.push(e);
4411
+ });
4412
+ }
4413
+ }
4414
+ } catch {}
4415
+ return { committed: entries, staged };
4416
+ }
4417
+ async function writePendingEntry(project, input) {
4418
+ ensureDoravalDirs();
4419
+ const pendingDir = getPendingProjectDir(project);
4420
+ if (!existsSync19(pendingDir)) {
4421
+ await Bun.write(join18(pendingDir, ".gitkeep"), "");
4422
+ }
4423
+ const date = new Date().toISOString().split("T")[0];
4424
+ const slug = slugify2(input.title);
4425
+ const filename = `${date}-${slug}.md`;
4426
+ const filePath = join18(pendingDir, filename);
4427
+ const content = `## ${input.title}
4428
+
4429
+ \`\`\`yaml
4430
+ pushback: ${input.pushback}
4431
+ tags: [${input.tags.join(", ")}]
4432
+ author: ${input.author || "human"}
4433
+ date: ${date}
4434
+ status: active
4435
+ \`\`\`
4436
+
4437
+ ${input.rationale}
4438
+ `;
4439
+ await Bun.write(filePath, content);
4440
+ return { filePath, filename };
4441
+ }
4442
+ async function killPort(port) {
4443
+ if (process.platform === "win32") {
4444
+ return;
4445
+ }
4446
+ try {
4447
+ const proc = Bun.spawn(["lsof", "-ti", `tcp:${port}`, "-sTCP:LISTEN"], { stdout: "pipe", stderr: "ignore" });
4448
+ const output = (await new Response(proc.stdout).text()).trim();
4449
+ if (!output)
4450
+ return;
4451
+ const pids = output.split(`
4452
+ `).map((p) => p.trim()).filter(Boolean);
4453
+ console.error(` Killing previous doraval ui on port ${port}...`);
4454
+ for (const pid of pids) {
4455
+ console.error(` \u2192 kill -9 ${pid}`);
4456
+ Bun.spawn(["kill", "-9", pid], { stdout: "ignore", stderr: "ignore" });
4457
+ }
4458
+ await new Promise((r) => setTimeout(r, 400));
4459
+ } catch {}
4460
+ }
4461
+ async function getDashboardHtml() {
4462
+ const isSource = import.meta.url.includes("/src/");
4463
+ const htmlPath = isSource ? new URL("../../ui/index.html", import.meta.url) : new URL("./ui/index.html", import.meta.url);
4464
+ try {
4465
+ return await Bun.file(htmlPath).text();
4466
+ } catch (err) {
4467
+ console.error(`[doraval ui] Failed to load HTML from ${htmlPath}`);
4468
+ return `<!doctype html><meta charset="utf-8"><body style="font-family:monospace;background:#111;color:#ddd;padding:2rem"><h1>doraval ui</h1><p>Dashboard HTML missing.</p><pre>${String(err)}</pre></body>`;
4469
+ }
4470
+ }
4471
+ var import_picocolors16, DEFAULT_PORT = 3737, ui_default;
4472
+ var init_ui = __esm(() => {
4473
+ init_journal_config();
4474
+ init_journal_parse();
4475
+ init_context();
4476
+ init_hook();
4477
+ import_picocolors16 = __toESM(require_picocolors(), 1);
4478
+ ui_default = {
4479
+ async run({ args }) {
4480
+ const port = Number(args.port) || DEFAULT_PORT;
4481
+ const host = args.host || "127.0.0.1";
4482
+ const shouldOpen = args.open !== false;
4483
+ await killPort(port);
4484
+ const config = await readConfig();
4485
+ let project = resolveProjectName(config) ?? undefined;
4486
+ if (project) {
4487
+ try {
4488
+ project = sanitizeProjectName(project);
4489
+ } catch {
4490
+ project = undefined;
4491
+ }
4492
+ }
4493
+ const server = Bun.serve({
4494
+ port,
4495
+ hostname: host,
4496
+ async fetch(req) {
4497
+ const url2 = new URL(req.url);
4498
+ if (url2.pathname === "/" || url2.pathname === "/index.html") {
4499
+ const html = await getDashboardHtml();
4500
+ return new Response(html, {
4501
+ headers: { "content-type": "text/html; charset=utf-8" }
4502
+ });
4503
+ }
4504
+ if (url2.pathname === "/api/status") {
4505
+ return Response.json({
4506
+ project: project || null,
4507
+ doravalDir: getJournalsDir(),
4508
+ hasConfig: !!config,
4509
+ repo: config?.journal?.repo ?? null
4510
+ });
4511
+ }
4512
+ if (url2.pathname === "/api/entries") {
4513
+ const { committed, staged } = await loadAllEntries(project || null);
4514
+ return Response.json({ project, committed, staged });
4515
+ }
4516
+ if (url2.pathname === "/api/context") {
4517
+ const { committed, staged } = await loadAllEntries(project || null);
4518
+ const all = [...staged, ...committed].filter((e) => (e.status || "active") === "active");
4519
+ const text = generateJournalContext(all, project || null, { minPushback: 1 });
4520
+ return Response.json({ text, project });
4521
+ }
4522
+ if (url2.pathname === "/api/hooks/status" && req.method === "GET") {
4523
+ const localPath = getLocalHooksPath();
4524
+ const globalPath = getGlobalSettingsPath();
4525
+ const localHas = hasHook(await readJson(localPath));
4526
+ const globalHas = hasHook(await readJson(globalPath));
4527
+ return Response.json({
4528
+ local: { enabled: localHas, path: localPath },
4529
+ global: { enabled: globalHas, path: globalPath }
4530
+ });
4531
+ }
4532
+ if (url2.pathname === "/api/hooks/enable" && req.method === "POST") {
4533
+ const body = await req.json().catch(() => ({}));
4534
+ const useGlobal = !!body.global;
4535
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4536
+ const res = await addHook(target);
4537
+ return Response.json(res);
4538
+ }
4539
+ if (url2.pathname === "/api/hooks/disable" && req.method === "POST") {
4540
+ const body = await req.json().catch(() => ({}));
4541
+ const useGlobal = !!body.global;
4542
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4543
+ const res = await removeHook(target);
4544
+ return Response.json(res);
4545
+ }
4546
+ if (url2.pathname === "/api/add" && req.method === "POST") {
4547
+ if (!project) {
4548
+ return Response.json({ error: "No project configured. Run dora init or dora journal init first." }, { status: 400 });
4549
+ }
4550
+ const body = await req.json();
4551
+ const title = String(body.title || "Untitled decision").trim();
4552
+ const pushback = Number(body.pushback ?? 4);
4553
+ const tags = Array.isArray(body.tags) ? body.tags.map((t) => String(t).trim()).filter(Boolean) : [];
4554
+ const rationale = String(body.rationale || title).trim();
4555
+ try {
4556
+ const result = await writePendingEntry(project, { title, pushback, tags, rationale });
4557
+ return Response.json({ ok: true, ...result });
4558
+ } catch (e) {
4559
+ return Response.json({ error: e.message }, { status: 500 });
4560
+ }
4561
+ }
4562
+ if (url2.pathname === "/api/refresh" && req.method === "POST") {
4563
+ const { committed, staged } = await loadAllEntries(project || null);
4564
+ return Response.json({ ok: true, committed, staged });
4565
+ }
4566
+ if (url2.pathname.startsWith("/api/")) {
4567
+ return Response.json({ error: "Not found" }, { status: 404 });
4568
+ }
4569
+ return new Response("Not found", { status: 404 });
4570
+ }
4571
+ });
4572
+ const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`;
4573
+ const msg = `
4574
+ ${import_picocolors16.default.blue("\u25C9")} doraval local dashboard
4575
+ ${import_picocolors16.default.dim("Project:")} ${project ? import_picocolors16.default.white(project) : import_picocolors16.default.yellow("none (run dora init)")}
4576
+ ${import_picocolors16.default.dim("URL:")} ${import_picocolors16.default.underline(import_picocolors16.default.cyan(url))}
4577
+
4578
+ ${import_picocolors16.default.dim("Press Ctrl+C to stop")}
4579
+ `;
4580
+ console.error(msg);
4581
+ if (shouldOpen && process.stdout.isTTY) {
4582
+ try {
4583
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
4584
+ spawn(opener, [url], { stdio: "ignore", detached: true }).unref();
4585
+ } catch {
4586
+ console.error(import_picocolors16.default.dim(` Could not auto-open. Visit ${url}`));
4587
+ }
4588
+ }
4589
+ process.on("SIGINT", () => {
4590
+ console.error(`
4591
+ Stopping dashboard...`);
4592
+ server.stop();
4593
+ process.exit(0);
4594
+ });
4595
+ }
4596
+ };
4597
+ });
4598
+
3888
4599
  // src/validators/claude/skill.ts
3889
- import { existsSync as existsSync17 } from "fs";
4600
+ import { existsSync as existsSync20 } from "fs";
3890
4601
  import { resolve as resolve5 } from "path";
3891
4602
  var claudeSkillValidator;
3892
4603
  var init_skill = __esm(() => {
@@ -3897,7 +4608,7 @@ var init_skill = __esm(() => {
3897
4608
  name: "Claude Skill",
3898
4609
  description: "Validates SKILL.md per current Claude Code spec: frontmatter (name/description relaxed to recommended; directory name usually provides the /command), body, supporting files, dynamic injection (!`cmd`), substitutions ($ARGUMENTS, ${CLAUDE_*}), and advanced fields (allowed-tools, context, disable-model-invocation, when_to_use, etc.)",
3899
4610
  detect(dir) {
3900
- return existsSync17(resolve5(dir, "SKILL.md"));
4611
+ return existsSync20(resolve5(dir, "SKILL.md"));
3901
4612
  },
3902
4613
  async validate(dir, _opts) {
3903
4614
  const loaded = await loadSkill(dir);
@@ -3915,8 +4626,8 @@ var init_skill = __esm(() => {
3915
4626
  });
3916
4627
 
3917
4628
  // src/validators/claude/plugin.ts
3918
- import { existsSync as existsSync18, readdirSync as readdirSync8 } from "fs";
3919
- import { resolve as resolve6, join as join16 } from "path";
4629
+ import { existsSync as existsSync21, readdirSync as readdirSync10 } from "fs";
4630
+ import { resolve as resolve6, join as join19 } from "path";
3920
4631
  function levenshtein(a, b) {
3921
4632
  if (a === b)
3922
4633
  return 0;
@@ -4008,7 +4719,7 @@ var init_plugin = __esm(() => {
4008
4719
  name: "Claude Plugin",
4009
4720
  description: "Validates .claude-plugin/plugin.json manifest (complete schema per Plugins reference), component path rules (replace vs augment), .claude-plugin/ purity, default dirs, single-root-skill layout, unrecognized fields + suggestions, and structure",
4010
4721
  detect(dir) {
4011
- return existsSync18(resolve6(dir, ".claude-plugin", "plugin.json"));
4722
+ return existsSync21(resolve6(dir, ".claude-plugin", "plugin.json"));
4012
4723
  },
4013
4724
  async validate(dir, _opts) {
4014
4725
  const errors = [];
@@ -4021,12 +4732,17 @@ var init_plugin = __esm(() => {
4021
4732
  const raw = await Bun.file(manifestPath).text();
4022
4733
  manifest = JSON.parse(raw);
4023
4734
  passes.push(".claude-plugin/plugin.json is valid JSON");
4024
- } catch {
4025
- errors.push(".claude-plugin/plugin.json is missing or invalid JSON");
4735
+ } catch (err) {
4736
+ if (!existsSync21(manifestPath)) {
4737
+ errors.push(`.claude-plugin/plugin.json is missing (looked for ${manifestPath})`);
4738
+ warnings.push("Hint: Run `doraval claude new` (or `dora claude new`) to scaffold a new Claude plugin in this directory.");
4739
+ } else {
4740
+ errors.push(`.claude-plugin/plugin.json is invalid JSON (${err.message})`);
4741
+ }
4026
4742
  return { errors, warnings, passes };
4027
4743
  }
4028
4744
  try {
4029
- const entries = readdirSync8(dotClaudePluginDir);
4745
+ const entries = readdirSync10(dotClaudePluginDir);
4030
4746
  const unexpected = entries.filter((e) => e !== "plugin.json");
4031
4747
  if (unexpected.length > 0) {
4032
4748
  for (const e of unexpected) {
@@ -4082,10 +4798,12 @@ var init_plugin = __esm(() => {
4082
4798
  }
4083
4799
  if (manifest.keywords !== undefined) {
4084
4800
  if (Array.isArray(manifest.keywords)) {
4085
- passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
4801
+ passes.push(`keywords: [${manifest.keywords.join(", ")}] \u2014 If users mention any of these keywords, your plugin will get triggered in Claude Code`);
4086
4802
  } else {
4087
4803
  errors.push("keywords must be an array of strings");
4088
4804
  }
4805
+ } else {
4806
+ warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Claude Code)');
4089
4807
  }
4090
4808
  if (manifest.defaultEnabled !== undefined) {
4091
4809
  passes.push(`defaultEnabled: ${manifest.defaultEnabled}`);
@@ -4111,7 +4829,7 @@ var init_plugin = __esm(() => {
4111
4829
  errors.push(`${field}: path "${s}" must start with "./"`);
4112
4830
  } else if (s.includes("..")) {
4113
4831
  errors.push(`${field}: path "${s}" must not use ".." (paths are confined to the plugin tree after cache copy)`);
4114
- } else if (existsSync18(resolve6(dir, s))) {
4832
+ } else if (existsSync21(resolve6(dir, s))) {
4115
4833
  passes.push(`${field}: path "${s}" exists`);
4116
4834
  } else {
4117
4835
  warnings.push(`${field}: path "${s}" does not exist on disk`);
@@ -4161,11 +4879,11 @@ var init_plugin = __esm(() => {
4161
4879
  passes.push(`dependencies: declares ${manifest.dependencies.length} plugin dependency/ies`);
4162
4880
  }
4163
4881
  const skillsDir = resolve6(dir, "skills");
4164
- if (existsSync18(skillsDir)) {
4165
- const entries = readdirSync8(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4882
+ if (existsSync21(skillsDir)) {
4883
+ const entries = readdirSync10(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4166
4884
  for (const e of entries) {
4167
- const md = join16(skillsDir, e.name, "SKILL.md");
4168
- if (existsSync18(md)) {
4885
+ const md = join19(skillsDir, e.name, "SKILL.md");
4886
+ if (existsSync21(md)) {
4169
4887
  passes.push(`skills/${e.name}/SKILL.md exists`);
4170
4888
  } else {
4171
4889
  errors.push(`skills/${e.name}/ is missing SKILL.md`);
@@ -4176,8 +4894,8 @@ var init_plugin = __esm(() => {
4176
4894
  }
4177
4895
  }
4178
4896
  const commandsDir = resolve6(dir, "commands");
4179
- if (existsSync18(commandsDir)) {
4180
- const mds = readdirSync8(commandsDir).filter((f) => f.endsWith(".md"));
4897
+ if (existsSync21(commandsDir)) {
4898
+ const mds = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
4181
4899
  if (mds.length) {
4182
4900
  passes.push(`commands/ has ${mds.length} .md file(s)`);
4183
4901
  }
@@ -4186,8 +4904,8 @@ var init_plugin = __esm(() => {
4186
4904
  }
4187
4905
  }
4188
4906
  const agentsDir = resolve6(dir, "agents");
4189
- if (existsSync18(agentsDir)) {
4190
- const mds = readdirSync8(agentsDir).filter((f) => f.endsWith(".md"));
4907
+ if (existsSync21(agentsDir)) {
4908
+ const mds = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
4191
4909
  if (mds.length) {
4192
4910
  passes.push(`agents/ has ${mds.length} .md file(s)`);
4193
4911
  }
@@ -4195,30 +4913,30 @@ var init_plugin = __esm(() => {
4195
4913
  warnings.push('agents/ co-exists with manifest "agents" \u2014 manifest replaces default (dir ignored)');
4196
4914
  }
4197
4915
  }
4198
- if (existsSync18(resolve6(dir, "output-styles"))) {
4916
+ if (existsSync21(resolve6(dir, "output-styles"))) {
4199
4917
  passes.push("output-styles/ directory present");
4200
4918
  if (manifest.outputStyles)
4201
4919
  warnings.push("output-styles/ co-exists with manifest outputStyles \u2014 manifest wins");
4202
4920
  }
4203
- if (existsSync18(resolve6(dir, "themes")))
4921
+ if (existsSync21(resolve6(dir, "themes")))
4204
4922
  passes.push("themes/ present (experimental)");
4205
- if (existsSync18(resolve6(dir, "monitors")) || manifest.experimental?.monitors) {
4923
+ if (existsSync21(resolve6(dir, "monitors")) || manifest.experimental?.monitors) {
4206
4924
  passes.push("monitors config present (experimental)");
4207
4925
  }
4208
- if (existsSync18(resolve6(dir, "bin")))
4926
+ if (existsSync21(resolve6(dir, "bin")))
4209
4927
  passes.push("bin/ present (adds executables to Bash tool $PATH)");
4210
- if (existsSync18(resolve6(dir, "settings.json")))
4928
+ if (existsSync21(resolve6(dir, "settings.json")))
4211
4929
  passes.push("settings.json present (plugin defaults for agent/statusline)");
4212
- if (existsSync18(resolve6(dir, "README.md")))
4930
+ if (existsSync21(resolve6(dir, "README.md")))
4213
4931
  passes.push("README.md present");
4214
- if (existsSync18(resolve6(dir, ".mcp.json")))
4932
+ if (existsSync21(resolve6(dir, ".mcp.json")))
4215
4933
  passes.push(".mcp.json present (validated by claude:mcp)");
4216
- if (existsSync18(resolve6(dir, ".lsp.json")))
4934
+ if (existsSync21(resolve6(dir, ".lsp.json")))
4217
4935
  passes.push(".lsp.json present (validated by claude:lsp when registered)");
4218
- if (existsSync18(resolve6(dir, "hooks/hooks.json")) || existsSync18(resolve6(dir, "hooks.json"))) {
4936
+ if (existsSync21(resolve6(dir, "hooks/hooks.json")) || existsSync21(resolve6(dir, "hooks.json"))) {
4219
4937
  passes.push("hooks config present (validated by claude:hooks)");
4220
4938
  }
4221
- if (existsSync18(resolve6(dir, "SKILL.md")) && !existsSync18(skillsDir) && manifest.skills === undefined) {
4939
+ if (existsSync21(resolve6(dir, "SKILL.md")) && !existsSync21(skillsDir) && manifest.skills === undefined) {
4222
4940
  passes.push('Root SKILL.md detected \u2014 plugin will be treated as a single-skill plugin (prefer frontmatter "name" for stable /command)');
4223
4941
  }
4224
4942
  return { errors, warnings, passes };
@@ -4227,8 +4945,8 @@ var init_plugin = __esm(() => {
4227
4945
  });
4228
4946
 
4229
4947
  // src/validators/claude/marketplace.ts
4230
- import { existsSync as existsSync19, readdirSync as readdirSync9 } from "fs";
4231
- import { resolve as resolve7, join as join17 } from "path";
4948
+ import { existsSync as existsSync22, readdirSync as readdirSync11 } from "fs";
4949
+ import { resolve as resolve7, join as join20 } from "path";
4232
4950
  var claudeMarketplaceValidator;
4233
4951
  var init_marketplace = __esm(() => {
4234
4952
  claudeMarketplaceValidator = {
@@ -4237,18 +4955,18 @@ var init_marketplace = __esm(() => {
4237
4955
  name: "Claude Plugin Marketplace",
4238
4956
  description: "Validates .claude-plugin/marketplace.json or plugins/ marketplace layouts (plugins array with sources)",
4239
4957
  detect(dir) {
4240
- if (existsSync19(resolve7(dir, ".claude-plugin", "marketplace.json")))
4958
+ if (existsSync22(resolve7(dir, ".claude-plugin", "marketplace.json")))
4241
4959
  return true;
4242
4960
  const pluginsDir = resolve7(dir, "plugins");
4243
- if (!existsSync19(pluginsDir))
4961
+ if (!existsSync22(pluginsDir))
4244
4962
  return false;
4245
4963
  try {
4246
- const entries = readdirSync9(pluginsDir, { withFileTypes: true });
4964
+ const entries = readdirSync11(pluginsDir, { withFileTypes: true });
4247
4965
  for (const entry of entries) {
4248
4966
  if (!entry.isDirectory())
4249
4967
  continue;
4250
- const hasSkills = existsSync19(join17(pluginsDir, entry.name, "skills"));
4251
- const hasManifest = existsSync19(join17(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
4968
+ const hasSkills = existsSync22(join20(pluginsDir, entry.name, "skills"));
4969
+ const hasManifest = existsSync22(join20(pluginsDir, entry.name, ".claude-plugin", "plugin.json"));
4252
4970
  if (hasSkills || hasManifest)
4253
4971
  return true;
4254
4972
  }
@@ -4260,9 +4978,9 @@ var init_marketplace = __esm(() => {
4260
4978
  const warnings = [];
4261
4979
  const passes = [];
4262
4980
  const claudeMktPath = resolve7(dir, ".claude-plugin", "marketplace.json");
4263
- const hasClaudeMkt = existsSync19(claudeMktPath);
4981
+ const hasClaudeMkt = existsSync22(claudeMktPath);
4264
4982
  const pluginsDir = resolve7(dir, "plugins");
4265
- const hasPluginsDirLayout = existsSync19(pluginsDir);
4983
+ const hasPluginsDirLayout = existsSync22(pluginsDir);
4266
4984
  if (!hasClaudeMkt && !hasPluginsDirLayout) {
4267
4985
  errors.push("Missing .claude-plugin/marketplace.json or plugins/ directory");
4268
4986
  return { errors, warnings, passes };
@@ -4307,9 +5025,9 @@ var init_marketplace = __esm(() => {
4307
5025
  const src = String(p.source);
4308
5026
  passes.push(`plugins[${i}].source: "${src}"`);
4309
5027
  const srcDir = resolve7(dir, src);
4310
- if (existsSync19(srcDir)) {
4311
- const hasManifest = existsSync19(resolve7(srcDir, ".claude-plugin", "plugin.json"));
4312
- const hasSkills = existsSync19(resolve7(srcDir, "skills"));
5028
+ if (existsSync22(srcDir)) {
5029
+ const hasManifest = existsSync22(resolve7(srcDir, ".claude-plugin", "plugin.json"));
5030
+ const hasSkills = existsSync22(resolve7(srcDir, "skills"));
4313
5031
  if (hasManifest || hasSkills) {
4314
5032
  passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
4315
5033
  } else {
@@ -4325,12 +5043,12 @@ var init_marketplace = __esm(() => {
4325
5043
  passes.push(`plugins[${i}].category: "${p.category}"`);
4326
5044
  }
4327
5045
  }
4328
- if (existsSync19(resolve7(dir, "README.md"))) {
5046
+ if (existsSync22(resolve7(dir, "README.md"))) {
4329
5047
  passes.push("README.md exists at marketplace root");
4330
5048
  } else {
4331
5049
  warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
4332
5050
  }
4333
- if (existsSync19(resolve7(dir, "LICENSE"))) {
5051
+ if (existsSync22(resolve7(dir, "LICENSE"))) {
4334
5052
  passes.push("LICENSE exists at marketplace root");
4335
5053
  } else {
4336
5054
  warnings.push("No LICENSE at marketplace root \u2014 recommended");
@@ -4339,27 +5057,27 @@ var init_marketplace = __esm(() => {
4339
5057
  }
4340
5058
  if (hasPluginsDirLayout) {
4341
5059
  passes.push("plugins/ directory exists");
4342
- const pluginEntries = readdirSync9(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5060
+ const pluginEntries = readdirSync11(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
4343
5061
  if (pluginEntries.length === 0) {
4344
5062
  errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
4345
5063
  return { errors, warnings, passes };
4346
5064
  }
4347
5065
  passes.push(`${pluginEntries.length} plugin(s) found`);
4348
- if (existsSync19(resolve7(dir, "README.md"))) {
5066
+ if (existsSync22(resolve7(dir, "README.md"))) {
4349
5067
  passes.push("README.md exists at marketplace root");
4350
5068
  } else {
4351
5069
  warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
4352
5070
  }
4353
- if (existsSync19(resolve7(dir, "LICENSE"))) {
5071
+ if (existsSync22(resolve7(dir, "LICENSE"))) {
4354
5072
  passes.push("LICENSE exists at marketplace root");
4355
5073
  } else {
4356
5074
  warnings.push("No LICENSE at marketplace root \u2014 recommended");
4357
5075
  }
4358
5076
  for (const plugin of pluginEntries) {
4359
- const pluginPath = join17(pluginsDir, plugin.name);
4360
- const hasSkills = existsSync19(join17(pluginPath, "skills"));
4361
- const hasManifest = existsSync19(join17(pluginPath, ".claude-plugin", "plugin.json"));
4362
- const hasReadme = existsSync19(join17(pluginPath, "README.md"));
5077
+ const pluginPath = join20(pluginsDir, plugin.name);
5078
+ const hasSkills = existsSync22(join20(pluginPath, "skills"));
5079
+ const hasManifest = existsSync22(join20(pluginPath, ".claude-plugin", "plugin.json"));
5080
+ const hasReadme = existsSync22(join20(pluginPath, "README.md"));
4363
5081
  if (hasManifest || hasSkills) {
4364
5082
  passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
4365
5083
  } else {
@@ -4377,7 +5095,7 @@ var init_marketplace = __esm(() => {
4377
5095
  });
4378
5096
 
4379
5097
  // src/validators/claude/hooks.ts
4380
- import { existsSync as existsSync20 } from "fs";
5098
+ import { existsSync as existsSync23 } from "fs";
4381
5099
  import { resolve as resolve8 } from "path";
4382
5100
  var KNOWN_EVENTS, claudeHooksValidator;
4383
5101
  var init_hooks = __esm(() => {
@@ -4419,13 +5137,13 @@ var init_hooks = __esm(() => {
4419
5137
  name: "Claude Hooks",
4420
5138
  description: "Validates hooks/hooks.json (or root hooks.json): all lifecycle events per Plugins reference, hook group structure (matcher + hooks[]), supported hook types (command, http, mcp_tool, prompt, agent)",
4421
5139
  detect(dir) {
4422
- return existsSync20(resolve8(dir, "hooks", "hooks.json")) || existsSync20(resolve8(dir, "hooks.json"));
5140
+ return existsSync23(resolve8(dir, "hooks", "hooks.json")) || existsSync23(resolve8(dir, "hooks.json"));
4423
5141
  },
4424
5142
  async validate(dir, _opts) {
4425
5143
  const errors = [];
4426
5144
  const warnings = [];
4427
5145
  const passes = [];
4428
- const hooksPath = existsSync20(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
5146
+ const hooksPath = existsSync23(resolve8(dir, "hooks", "hooks.json")) ? resolve8(dir, "hooks", "hooks.json") : resolve8(dir, "hooks.json");
4429
5147
  let config;
4430
5148
  try {
4431
5149
  const raw = await Bun.file(hooksPath).text();
@@ -4491,7 +5209,7 @@ var init_hooks = __esm(() => {
4491
5209
  });
4492
5210
 
4493
5211
  // src/validators/claude/mcp.ts
4494
- import { existsSync as existsSync21 } from "fs";
5212
+ import { existsSync as existsSync24 } from "fs";
4495
5213
  import { resolve as resolve9 } from "path";
4496
5214
  var claudeMcpValidator;
4497
5215
  var init_mcp = __esm(() => {
@@ -4501,7 +5219,7 @@ var init_mcp = __esm(() => {
4501
5219
  name: "Claude MCP Config",
4502
5220
  description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, ${CLAUDE_PLUGIN_ROOT} etc. substitutions per Plugins reference",
4503
5221
  detect(dir) {
4504
- return existsSync21(resolve9(dir, ".mcp.json"));
5222
+ return existsSync24(resolve9(dir, ".mcp.json"));
4505
5223
  },
4506
5224
  async validate(dir, _opts) {
4507
5225
  const errors = [];
@@ -4561,8 +5279,8 @@ var init_mcp = __esm(() => {
4561
5279
  });
4562
5280
 
4563
5281
  // src/validators/claude/subagent.ts
4564
- import { existsSync as existsSync22, readdirSync as readdirSync10 } from "fs";
4565
- import { resolve as resolve10, join as join18 } from "path";
5282
+ import { existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
5283
+ import { resolve as resolve10, join as join21 } from "path";
4566
5284
  var claudeSubagentValidator;
4567
5285
  var init_subagent = __esm(() => {
4568
5286
  init_frontmatter();
@@ -4573,10 +5291,10 @@ var init_subagent = __esm(() => {
4573
5291
  description: "Validates agents/*.md (plugin subagents): frontmatter per spec (name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, isolation=worktree), body; warns on disallowed fields (hooks, mcpServers, permissionMode) for security",
4574
5292
  detect(dir) {
4575
5293
  const agentsDir = resolve10(dir, "agents");
4576
- if (!existsSync22(agentsDir))
5294
+ if (!existsSync25(agentsDir))
4577
5295
  return false;
4578
5296
  try {
4579
- return readdirSync10(agentsDir).some((f) => f.endsWith(".md"));
5297
+ return readdirSync12(agentsDir).some((f) => f.endsWith(".md"));
4580
5298
  } catch {
4581
5299
  return false;
4582
5300
  }
@@ -4586,7 +5304,7 @@ var init_subagent = __esm(() => {
4586
5304
  const warnings = [];
4587
5305
  const passes = [];
4588
5306
  const agentsDir = resolve10(dir, "agents");
4589
- const mdFiles = readdirSync10(agentsDir).filter((f) => f.endsWith(".md"));
5307
+ const mdFiles = readdirSync12(agentsDir).filter((f) => f.endsWith(".md"));
4590
5308
  if (mdFiles.length === 0) {
4591
5309
  errors.push("agents/ directory has no .md files");
4592
5310
  return { errors, warnings, passes };
@@ -4607,7 +5325,7 @@ var init_subagent = __esm(() => {
4607
5325
  ]);
4608
5326
  const DISALLOWED = new Set(["hooks", "mcpServers", "permissionMode"]);
4609
5327
  for (const file of mdFiles) {
4610
- const filePath = join18(agentsDir, file);
5328
+ const filePath = join21(agentsDir, file);
4611
5329
  const raw = await Bun.file(filePath).text();
4612
5330
  try {
4613
5331
  const parsed = parseFrontmatter(raw);
@@ -4653,8 +5371,8 @@ var init_subagent = __esm(() => {
4653
5371
  });
4654
5372
 
4655
5373
  // src/validators/claude/command.ts
4656
- import { existsSync as existsSync23, readdirSync as readdirSync11 } from "fs";
4657
- import { resolve as resolve11, join as join19 } from "path";
5374
+ import { existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
5375
+ import { resolve as resolve11, join as join22 } from "path";
4658
5376
  var claudeCommandValidator;
4659
5377
  var init_command = __esm(() => {
4660
5378
  init_frontmatter();
@@ -4665,10 +5383,10 @@ var init_command = __esm(() => {
4665
5383
  description: "Validates commands/ (or legacy .claude/commands/) .md files: frontmatter (including rich skill fields), description, body",
4666
5384
  detect(dir) {
4667
5385
  const commandsDir = resolve11(dir, "commands");
4668
- if (!existsSync23(commandsDir))
5386
+ if (!existsSync26(commandsDir))
4669
5387
  return false;
4670
5388
  try {
4671
- return readdirSync11(commandsDir).some((f) => f.endsWith(".md"));
5389
+ return readdirSync13(commandsDir).some((f) => f.endsWith(".md"));
4672
5390
  } catch {
4673
5391
  return false;
4674
5392
  }
@@ -4678,14 +5396,14 @@ var init_command = __esm(() => {
4678
5396
  const warnings = [];
4679
5397
  const passes = [];
4680
5398
  const commandsDir = resolve11(dir, "commands");
4681
- const mdFiles = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
5399
+ const mdFiles = readdirSync13(commandsDir).filter((f) => f.endsWith(".md"));
4682
5400
  if (mdFiles.length === 0) {
4683
5401
  errors.push("commands/ directory has no .md files");
4684
5402
  return { errors, warnings, passes };
4685
5403
  }
4686
5404
  passes.push(`${mdFiles.length} command definition(s) found`);
4687
5405
  for (const file of mdFiles) {
4688
- const filePath = join19(commandsDir, file);
5406
+ const filePath = join22(commandsDir, file);
4689
5407
  const raw = await Bun.file(filePath).text();
4690
5408
  try {
4691
5409
  const parsed = parseFrontmatter(raw);
@@ -4714,7 +5432,7 @@ var init_command = __esm(() => {
4714
5432
  });
4715
5433
 
4716
5434
  // src/validators/claude/memory.ts
4717
- import { existsSync as existsSync24 } from "fs";
5435
+ import { existsSync as existsSync27 } from "fs";
4718
5436
  import { resolve as resolve12 } from "path";
4719
5437
  var claudeMemoryValidator;
4720
5438
  var init_memory = __esm(() => {
@@ -4724,7 +5442,7 @@ var init_memory = __esm(() => {
4724
5442
  name: "Claude CLAUDE.md",
4725
5443
  description: "Validates CLAUDE.md: non-empty, length recommendations, @path imports",
4726
5444
  detect(dir) {
4727
- return existsSync24(resolve12(dir, "CLAUDE.md"));
5445
+ return existsSync27(resolve12(dir, "CLAUDE.md"));
4728
5446
  },
4729
5447
  async validate(dir, _opts) {
4730
5448
  const errors = [];
@@ -4749,7 +5467,7 @@ var init_memory = __esm(() => {
4749
5467
  while ((match = importRegex.exec(raw)) !== null) {
4750
5468
  const importPath = match[1];
4751
5469
  const resolvedImport = resolve12(dir, importPath);
4752
- if (existsSync24(resolvedImport)) {
5470
+ if (existsSync27(resolvedImport)) {
4753
5471
  passes.push(`@import "${importPath}" exists`);
4754
5472
  } else {
4755
5473
  warnings.push(`@import "${importPath}" \u2014 file not found at ${resolvedImport}`);
@@ -4761,7 +5479,7 @@ var init_memory = __esm(() => {
4761
5479
  });
4762
5480
 
4763
5481
  // src/validators/claude/lsp.ts
4764
- import { existsSync as existsSync25 } from "fs";
5482
+ import { existsSync as existsSync28 } from "fs";
4765
5483
  import { resolve as resolve13 } from "path";
4766
5484
  var claudeLspValidator;
4767
5485
  var init_lsp = __esm(() => {
@@ -4771,7 +5489,7 @@ var init_lsp = __esm(() => {
4771
5489
  name: "Claude LSP Servers",
4772
5490
  description: "Validates .lsp.json (or plugin.json lspServers): language server configs with required command + extensionToLanguage; optional transport, env, settings, diagnostics etc. (binaries installed separately)",
4773
5491
  detect(dir) {
4774
- return existsSync25(resolve13(dir, ".lsp.json")) || existsSync25(resolve13(dir, ".claude-plugin", "plugin.json"));
5492
+ return existsSync28(resolve13(dir, ".lsp.json")) || existsSync28(resolve13(dir, ".claude-plugin", "plugin.json"));
4775
5493
  },
4776
5494
  async validate(dir, _opts) {
4777
5495
  const errors = [];
@@ -4779,7 +5497,7 @@ var init_lsp = __esm(() => {
4779
5497
  const passes = [];
4780
5498
  let cfg = null;
4781
5499
  const lspPath = resolve13(dir, ".lsp.json");
4782
- if (existsSync25(lspPath)) {
5500
+ if (existsSync28(lspPath)) {
4783
5501
  try {
4784
5502
  cfg = JSON.parse(await Bun.file(lspPath).text());
4785
5503
  passes.push(".lsp.json is valid JSON");
@@ -4789,7 +5507,7 @@ var init_lsp = __esm(() => {
4789
5507
  }
4790
5508
  } else {
4791
5509
  const manifestPath = resolve13(dir, ".claude-plugin", "plugin.json");
4792
- if (existsSync25(manifestPath)) {
5510
+ if (existsSync28(manifestPath)) {
4793
5511
  try {
4794
5512
  const m = JSON.parse(await Bun.file(manifestPath).text());
4795
5513
  if (m && m.lspServers && typeof m.lspServers === "object") {
@@ -4800,7 +5518,7 @@ var init_lsp = __esm(() => {
4800
5518
  }
4801
5519
  }
4802
5520
  if (!cfg) {
4803
- if (!existsSync25(lspPath)) {
5521
+ if (!existsSync28(lspPath)) {
4804
5522
  return { errors, warnings, passes };
4805
5523
  }
4806
5524
  }
@@ -4829,7 +5547,7 @@ var init_lsp = __esm(() => {
4829
5547
  });
4830
5548
 
4831
5549
  // src/validators/claude/monitors.ts
4832
- import { existsSync as existsSync26 } from "fs";
5550
+ import { existsSync as existsSync29 } from "fs";
4833
5551
  import { resolve as resolve14 } from "path";
4834
5552
  var claudeMonitorsValidator;
4835
5553
  var init_monitors = __esm(() => {
@@ -4839,7 +5557,7 @@ var init_monitors = __esm(() => {
4839
5557
  name: "Claude Monitors (experimental)",
4840
5558
  description: "Validates monitors/monitors.json (or experimental.monitors): array of {name, command, description, when?}; commands support ${CLAUDE_PLUGIN_*} subs. Monitors run only in interactive CLI sessions.",
4841
5559
  detect(dir) {
4842
- return existsSync26(resolve14(dir, "monitors", "monitors.json")) || existsSync26(resolve14(dir, "monitors.json")) || existsSync26(resolve14(dir, ".claude-plugin", "plugin.json"));
5560
+ return existsSync29(resolve14(dir, "monitors", "monitors.json")) || existsSync29(resolve14(dir, "monitors.json")) || existsSync29(resolve14(dir, ".claude-plugin", "plugin.json"));
4843
5561
  },
4844
5562
  async validate(dir, _opts) {
4845
5563
  const errors = [];
@@ -4851,7 +5569,7 @@ var init_monitors = __esm(() => {
4851
5569
  resolve14(dir, "monitors.json")
4852
5570
  ];
4853
5571
  for (const p of candidates) {
4854
- if (existsSync26(p)) {
5572
+ if (existsSync29(p)) {
4855
5573
  try {
4856
5574
  const parsed = JSON.parse(await Bun.file(p).text());
4857
5575
  if (Array.isArray(parsed)) {
@@ -4867,7 +5585,7 @@ var init_monitors = __esm(() => {
4867
5585
  }
4868
5586
  if (!arr) {
4869
5587
  const mp = resolve14(dir, ".claude-plugin", "plugin.json");
4870
- if (existsSync26(mp)) {
5588
+ if (existsSync29(mp)) {
4871
5589
  try {
4872
5590
  const m = JSON.parse(await Bun.file(mp).text());
4873
5591
  const exp = m?.experimental;
@@ -4920,8 +5638,8 @@ var init_monitors = __esm(() => {
4920
5638
  });
4921
5639
 
4922
5640
  // src/validators/codex/plugin.ts
4923
- import { existsSync as existsSync27, readdirSync as readdirSync12 } from "fs";
4924
- import { resolve as resolve15, join as join20 } from "path";
5641
+ import { existsSync as existsSync30, readdirSync as readdirSync14 } from "fs";
5642
+ import { resolve as resolve15, join as join23 } from "path";
4925
5643
  var NAME_REGEX3, codexPluginValidator;
4926
5644
  var init_plugin2 = __esm(() => {
4927
5645
  NAME_REGEX3 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -4931,7 +5649,7 @@ var init_plugin2 = __esm(() => {
4931
5649
  name: "Codex Plugin",
4932
5650
  description: "Validates .codex-plugin/plugin.json manifest (requires interface block and skills as directory string per Codex packaging)",
4933
5651
  detect(dir) {
4934
- return existsSync27(resolve15(dir, ".codex-plugin", "plugin.json"));
5652
+ return existsSync30(resolve15(dir, ".codex-plugin", "plugin.json"));
4935
5653
  },
4936
5654
  async validate(dir, _opts) {
4937
5655
  const errors = [];
@@ -5002,12 +5720,21 @@ var init_plugin2 = __esm(() => {
5002
5720
  } else {
5003
5721
  warnings.push('Missing "description" (recommended)');
5004
5722
  }
5723
+ if (manifest.keywords !== undefined) {
5724
+ if (Array.isArray(manifest.keywords)) {
5725
+ passes.push(`keywords: [${manifest.keywords.join(", ")}] \u2014 If users mention any of these keywords, your plugin will get triggered in Codex`);
5726
+ } else {
5727
+ errors.push("keywords must be an array of strings");
5728
+ }
5729
+ } else {
5730
+ warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Codex)');
5731
+ }
5005
5732
  const skillsDir = resolve15(dir, "skills");
5006
- if (existsSync27(skillsDir)) {
5007
- const entries = readdirSync12(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5733
+ if (existsSync30(skillsDir)) {
5734
+ const entries = readdirSync14(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5008
5735
  for (const e of entries) {
5009
- const md = join20(skillsDir, e.name, "SKILL.md");
5010
- if (existsSync27(md)) {
5736
+ const md = join23(skillsDir, e.name, "SKILL.md");
5737
+ if (existsSync30(md)) {
5011
5738
  passes.push(`skills/${e.name}/SKILL.md exists`);
5012
5739
  } else {
5013
5740
  errors.push(`skills/${e.name}/ is missing SKILL.md`);
@@ -5025,7 +5752,7 @@ var init_plugin2 = __esm(() => {
5025
5752
  });
5026
5753
 
5027
5754
  // src/validators/codex/marketplace.ts
5028
- import { existsSync as existsSync28 } from "fs";
5755
+ import { existsSync as existsSync31 } from "fs";
5029
5756
  import { resolve as resolve16 } from "path";
5030
5757
  var codexMarketplaceValidator;
5031
5758
  var init_marketplace2 = __esm(() => {
@@ -5035,9 +5762,9 @@ var init_marketplace2 = __esm(() => {
5035
5762
  name: "Codex Plugin Marketplace",
5036
5763
  description: "Validates .agents/plugins/marketplace.json (Codex convention: object source + policy blocks)",
5037
5764
  detect(dir) {
5038
- if (existsSync28(resolve16(dir, ".agents", "plugins", "marketplace.json")))
5765
+ if (existsSync31(resolve16(dir, ".agents", "plugins", "marketplace.json")))
5039
5766
  return true;
5040
- if (existsSync28(resolve16(dir, ".agents", "plugins", "marketplace.json")))
5767
+ if (existsSync31(resolve16(dir, ".agents", "plugins", "marketplace.json")))
5041
5768
  return true;
5042
5769
  return false;
5043
5770
  },
@@ -5046,7 +5773,7 @@ var init_marketplace2 = __esm(() => {
5046
5773
  const warnings = [];
5047
5774
  const passes = [];
5048
5775
  const marketplacePath = resolve16(dir, ".agents", "plugins", "marketplace.json");
5049
- if (!existsSync28(marketplacePath)) {
5776
+ if (!existsSync31(marketplacePath)) {
5050
5777
  errors.push("Missing .agents/plugins/marketplace.json");
5051
5778
  return { errors, warnings, passes };
5052
5779
  }
@@ -5121,12 +5848,12 @@ var init_marketplace2 = __esm(() => {
5121
5848
  passes.push(`plugins[${i}].category: "${p.category}"`);
5122
5849
  }
5123
5850
  }
5124
- if (existsSync28(resolve16(dir, "README.md"))) {
5851
+ if (existsSync31(resolve16(dir, "README.md"))) {
5125
5852
  passes.push("README.md exists at marketplace root");
5126
5853
  } else {
5127
5854
  warnings.push("No README.md at marketplace root \u2014 recommended");
5128
5855
  }
5129
- if (existsSync28(resolve16(dir, "LICENSE"))) {
5856
+ if (existsSync31(resolve16(dir, "LICENSE"))) {
5130
5857
  passes.push("LICENSE exists at marketplace root");
5131
5858
  } else {
5132
5859
  warnings.push("No LICENSE at marketplace root \u2014 recommended");
@@ -5137,7 +5864,7 @@ var init_marketplace2 = __esm(() => {
5137
5864
  });
5138
5865
 
5139
5866
  // src/validators/codex/mcp.ts
5140
- import { existsSync as existsSync29 } from "fs";
5867
+ import { existsSync as existsSync32 } from "fs";
5141
5868
  import { resolve as resolve17 } from "path";
5142
5869
  var codexMcpValidator;
5143
5870
  var init_mcp2 = __esm(() => {
@@ -5147,7 +5874,7 @@ var init_mcp2 = __esm(() => {
5147
5874
  name: "Codex MCP Config",
5148
5875
  description: "Validates .mcp.json (or inline via plugin.json mcpServers): server entries (stdio: command+args, or url), env, cwd, substitutions per Codex MCP support",
5149
5876
  detect(dir) {
5150
- return existsSync29(resolve17(dir, ".mcp.json"));
5877
+ return existsSync32(resolve17(dir, ".mcp.json"));
5151
5878
  },
5152
5879
  async validate(dir, _opts) {
5153
5880
  const errors = [];
@@ -5207,7 +5934,7 @@ var init_mcp2 = __esm(() => {
5207
5934
  });
5208
5935
 
5209
5936
  // src/validators/codex/skill.ts
5210
- import { existsSync as existsSync30 } from "fs";
5937
+ import { existsSync as existsSync33 } from "fs";
5211
5938
  import { resolve as resolve18 } from "path";
5212
5939
  var codexSkillValidator;
5213
5940
  var init_skill2 = __esm(() => {
@@ -5218,7 +5945,7 @@ var init_skill2 = __esm(() => {
5218
5945
  name: "Codex Skill",
5219
5946
  description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Codex uses the same SKILL.md spec as other providers.",
5220
5947
  detect(dir) {
5221
- return existsSync30(resolve18(dir, "SKILL.md"));
5948
+ return existsSync33(resolve18(dir, "SKILL.md"));
5222
5949
  },
5223
5950
  async validate(dir, _opts) {
5224
5951
  const loaded = await loadSkill(dir);
@@ -5236,8 +5963,8 @@ var init_skill2 = __esm(() => {
5236
5963
  });
5237
5964
 
5238
5965
  // src/validators/cursor/plugin.ts
5239
- import { existsSync as existsSync31, readdirSync as readdirSync14 } from "fs";
5240
- import { resolve as resolve19, join as join22 } from "path";
5966
+ import { existsSync as existsSync34, readdirSync as readdirSync16 } from "fs";
5967
+ import { resolve as resolve19, join as join25 } from "path";
5241
5968
  var NAME_REGEX4, cursorPluginValidator;
5242
5969
  var init_plugin3 = __esm(() => {
5243
5970
  NAME_REGEX4 = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
@@ -5247,7 +5974,7 @@ var init_plugin3 = __esm(() => {
5247
5974
  name: "Cursor Plugin",
5248
5975
  description: "Validates .cursor-plugin/plugin.json manifest (skills as directory string; mcpServers support)",
5249
5976
  detect(dir) {
5250
- return existsSync31(resolve19(dir, ".cursor-plugin", "plugin.json"));
5977
+ return existsSync34(resolve19(dir, ".cursor-plugin", "plugin.json"));
5251
5978
  },
5252
5979
  async validate(dir, _opts) {
5253
5980
  const errors = [];
@@ -5324,15 +6051,21 @@ var init_plugin3 = __esm(() => {
5324
6051
  passes.push("homepage present");
5325
6052
  if (manifest.repository)
5326
6053
  passes.push("repository present");
5327
- if (Array.isArray(manifest.keywords)) {
5328
- passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
6054
+ if (manifest.keywords !== undefined) {
6055
+ if (Array.isArray(manifest.keywords)) {
6056
+ passes.push(`keywords: [${manifest.keywords.join(", ")}] \u2014 If users mention any of these keywords, your plugin will get triggered in Cursor`);
6057
+ } else {
6058
+ errors.push("keywords must be an array of strings");
6059
+ }
6060
+ } else {
6061
+ warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Cursor)');
5329
6062
  }
5330
6063
  const skillsDir = resolve19(dir, "skills");
5331
- if (existsSync31(skillsDir)) {
5332
- const entries = readdirSync14(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
6064
+ if (existsSync34(skillsDir)) {
6065
+ const entries = readdirSync16(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5333
6066
  for (const e of entries) {
5334
- const md = join22(skillsDir, e.name, "SKILL.md");
5335
- if (existsSync31(md)) {
6067
+ const md = join25(skillsDir, e.name, "SKILL.md");
6068
+ if (existsSync34(md)) {
5336
6069
  passes.push(`skills/${e.name}/SKILL.md exists`);
5337
6070
  } else {
5338
6071
  errors.push(`skills/${e.name}/ is missing SKILL.md`);
@@ -5343,7 +6076,7 @@ var init_plugin3 = __esm(() => {
5343
6076
  const mcpRef = manifest.mcpServers;
5344
6077
  if (mcpRef.startsWith("./") || mcpRef.startsWith("../")) {
5345
6078
  const mcpPath = resolve19(dir, mcpRef);
5346
- if (existsSync31(mcpPath)) {
6079
+ if (existsSync34(mcpPath)) {
5347
6080
  passes.push(`mcpServers file exists at ${mcpRef}`);
5348
6081
  } else {
5349
6082
  warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
@@ -5373,8 +6106,8 @@ var init_plugin3 = __esm(() => {
5373
6106
  });
5374
6107
 
5375
6108
  // src/validators/cursor/marketplace.ts
5376
- import { existsSync as existsSync32, readdirSync as readdirSync15 } from "fs";
5377
- import { resolve as resolve20, join as join23 } from "path";
6109
+ import { existsSync as existsSync35, readdirSync as readdirSync17 } from "fs";
6110
+ import { resolve as resolve20, join as join26 } from "path";
5378
6111
  var cursorMarketplaceValidator;
5379
6112
  var init_marketplace3 = __esm(() => {
5380
6113
  cursorMarketplaceValidator = {
@@ -5383,18 +6116,18 @@ var init_marketplace3 = __esm(() => {
5383
6116
  name: "Cursor Plugin Marketplace",
5384
6117
  description: "Validates .cursor-plugin/marketplace.json (string sources + metadata.pluginRoot)",
5385
6118
  detect(dir) {
5386
- if (existsSync32(resolve20(dir, ".cursor-plugin", "marketplace.json")))
6119
+ if (existsSync35(resolve20(dir, ".cursor-plugin", "marketplace.json")))
5387
6120
  return true;
5388
6121
  const pluginsDir = resolve20(dir, "plugins");
5389
- if (!existsSync32(pluginsDir))
6122
+ if (!existsSync35(pluginsDir))
5390
6123
  return false;
5391
6124
  try {
5392
- const entries = readdirSync15(pluginsDir, { withFileTypes: true });
6125
+ const entries = readdirSync17(pluginsDir, { withFileTypes: true });
5393
6126
  for (const entry of entries) {
5394
6127
  if (!entry.isDirectory())
5395
6128
  continue;
5396
- const hasSkills = existsSync32(join23(pluginsDir, entry.name, "skills"));
5397
- const hasManifest = existsSync32(join23(pluginsDir, entry.name, ".cursor-plugin", "plugin.json"));
6129
+ const hasSkills = existsSync35(join26(pluginsDir, entry.name, "skills"));
6130
+ const hasManifest = existsSync35(join26(pluginsDir, entry.name, ".cursor-plugin", "plugin.json"));
5398
6131
  if (hasSkills || hasManifest)
5399
6132
  return true;
5400
6133
  }
@@ -5406,9 +6139,9 @@ var init_marketplace3 = __esm(() => {
5406
6139
  const warnings = [];
5407
6140
  const passes = [];
5408
6141
  const cursorMktPath = resolve20(dir, ".cursor-plugin", "marketplace.json");
5409
- const hasCursorMkt = existsSync32(cursorMktPath);
6142
+ const hasCursorMkt = existsSync35(cursorMktPath);
5410
6143
  const pluginsDir = resolve20(dir, "plugins");
5411
- const hasPluginsDirLayout = existsSync32(pluginsDir);
6144
+ const hasPluginsDirLayout = existsSync35(pluginsDir);
5412
6145
  if (!hasCursorMkt && !hasPluginsDirLayout) {
5413
6146
  errors.push("Missing .cursor-plugin/marketplace.json or plugins/ directory");
5414
6147
  return { errors, warnings, passes };
@@ -5462,9 +6195,9 @@ var init_marketplace3 = __esm(() => {
5462
6195
  const src = String(p.source);
5463
6196
  passes.push(`plugins[${i}].source: "${src}"`);
5464
6197
  const srcDir = resolve20(dir, pluginRoot, src);
5465
- if (existsSync32(srcDir)) {
5466
- const hasManifest = existsSync32(resolve20(srcDir, ".cursor-plugin", "plugin.json"));
5467
- const hasSkills = existsSync32(resolve20(srcDir, "skills"));
6198
+ if (existsSync35(srcDir)) {
6199
+ const hasManifest = existsSync35(resolve20(srcDir, ".cursor-plugin", "plugin.json"));
6200
+ const hasSkills = existsSync35(resolve20(srcDir, "skills"));
5468
6201
  if (hasManifest || hasSkills) {
5469
6202
  passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
5470
6203
  } else {
@@ -5475,7 +6208,7 @@ var init_marketplace3 = __esm(() => {
5475
6208
  }
5476
6209
  } else {
5477
6210
  const implicitSrc = resolve20(dir, pluginRoot, p.name || "");
5478
- if (p.name && existsSync32(implicitSrc)) {
6211
+ if (p.name && existsSync35(implicitSrc)) {
5479
6212
  passes.push(`plugins[${i}]: implicit source via name under ${pluginRoot}`);
5480
6213
  } else {
5481
6214
  warnings.push(`plugins[${i}]: missing "source" (and no implicit dir)`);
@@ -5490,12 +6223,12 @@ var init_marketplace3 = __esm(() => {
5490
6223
  passes.push(`plugins[${i}].homepage present`);
5491
6224
  }
5492
6225
  }
5493
- if (existsSync32(resolve20(dir, "README.md"))) {
6226
+ if (existsSync35(resolve20(dir, "README.md"))) {
5494
6227
  passes.push("README.md exists at marketplace root");
5495
6228
  } else {
5496
6229
  warnings.push("No README.md at marketplace root \u2014 recommended for discoverability");
5497
6230
  }
5498
- if (existsSync32(resolve20(dir, "LICENSE"))) {
6231
+ if (existsSync35(resolve20(dir, "LICENSE"))) {
5499
6232
  passes.push("LICENSE exists at marketplace root");
5500
6233
  } else {
5501
6234
  warnings.push("No LICENSE at marketplace root \u2014 recommended");
@@ -5504,21 +6237,21 @@ var init_marketplace3 = __esm(() => {
5504
6237
  }
5505
6238
  if (hasPluginsDirLayout) {
5506
6239
  passes.push("plugins/ directory exists");
5507
- const pluginEntries = readdirSync15(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
6240
+ const pluginEntries = readdirSync17(pluginsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
5508
6241
  if (pluginEntries.length === 0) {
5509
6242
  errors.push("plugins/ directory is empty \u2014 expected at least one plugin");
5510
6243
  return { errors, warnings, passes };
5511
6244
  }
5512
6245
  passes.push(`${pluginEntries.length} plugin(s) found`);
5513
- if (existsSync32(resolve20(dir, "README.md"))) {
6246
+ if (existsSync35(resolve20(dir, "README.md"))) {
5514
6247
  passes.push("README.md exists at marketplace root");
5515
6248
  } else {
5516
6249
  warnings.push("No README.md at marketplace root \u2014 recommended");
5517
6250
  }
5518
6251
  for (const plugin of pluginEntries) {
5519
- const pluginPath = join23(pluginsDir, plugin.name);
5520
- const hasSkills = existsSync32(join23(pluginPath, "skills"));
5521
- const hasManifest = existsSync32(join23(pluginPath, ".cursor-plugin", "plugin.json"));
6252
+ const pluginPath = join26(pluginsDir, plugin.name);
6253
+ const hasSkills = existsSync35(join26(pluginPath, "skills"));
6254
+ const hasManifest = existsSync35(join26(pluginPath, ".cursor-plugin", "plugin.json"));
5522
6255
  if (hasManifest || hasSkills) {
5523
6256
  passes.push(`Plugin "${plugin.name}" has ${hasManifest ? "manifest" : "skills/"}`);
5524
6257
  }
@@ -5531,7 +6264,7 @@ var init_marketplace3 = __esm(() => {
5531
6264
  });
5532
6265
 
5533
6266
  // src/validators/cursor/mcp.ts
5534
- import { existsSync as existsSync33 } from "fs";
6267
+ import { existsSync as existsSync36 } from "fs";
5535
6268
  import { resolve as resolve21 } from "path";
5536
6269
  var cursorMcpValidator;
5537
6270
  var init_mcp3 = __esm(() => {
@@ -5541,7 +6274,7 @@ var init_mcp3 = __esm(() => {
5541
6274
  name: "Cursor MCP Config",
5542
6275
  description: "Validates mcp.json (Cursor uses no leading dot; supports mcpServers wrapper or direct server map)",
5543
6276
  detect(dir) {
5544
- return existsSync33(resolve21(dir, "mcp.json"));
6277
+ return existsSync36(resolve21(dir, "mcp.json"));
5545
6278
  },
5546
6279
  async validate(dir, _opts) {
5547
6280
  const errors = [];
@@ -5607,7 +6340,7 @@ var init_mcp3 = __esm(() => {
5607
6340
  });
5608
6341
 
5609
6342
  // src/validators/cursor/skill.ts
5610
- import { existsSync as existsSync34 } from "fs";
6343
+ import { existsSync as existsSync37 } from "fs";
5611
6344
  import { resolve as resolve22 } from "path";
5612
6345
  var cursorSkillValidator;
5613
6346
  var init_skill3 = __esm(() => {
@@ -5618,7 +6351,7 @@ var init_skill3 = __esm(() => {
5618
6351
  name: "Cursor Skill",
5619
6352
  description: "Validates SKILL.md (shared format): frontmatter (name/description), body, supporting files, substitutions. Cursor uses the same SKILL.md spec as other providers.",
5620
6353
  detect(dir) {
5621
- return existsSync34(resolve22(dir, "SKILL.md"));
6354
+ return existsSync37(resolve22(dir, "SKILL.md"));
5622
6355
  },
5623
6356
  async validate(dir, _opts) {
5624
6357
  const loaded = await loadSkill(dir);
@@ -5636,7 +6369,7 @@ var init_skill3 = __esm(() => {
5636
6369
  });
5637
6370
 
5638
6371
  // src/validators/copilot/plugin.ts
5639
- import { existsSync as existsSync35 } from "fs";
6372
+ import { existsSync as existsSync38 } from "fs";
5640
6373
  import { resolve as resolve23 } from "path";
5641
6374
  var NAME_REGEX5, copilotPluginValidator;
5642
6375
  var init_plugin4 = __esm(() => {
@@ -5647,7 +6380,7 @@ var init_plugin4 = __esm(() => {
5647
6380
  name: "Copilot Plugin",
5648
6381
  description: "Validates .github/plugin/plugin.json (skills as array of paths, mcpServers support)",
5649
6382
  detect(dir) {
5650
- return existsSync35(resolve23(dir, ".github", "plugin", "plugin.json"));
6383
+ return existsSync38(resolve23(dir, ".github", "plugin", "plugin.json"));
5651
6384
  },
5652
6385
  async validate(dir, _opts) {
5653
6386
  const errors = [];
@@ -5690,9 +6423,9 @@ var init_plugin4 = __esm(() => {
5690
6423
  }
5691
6424
  const skillDir = resolve23(dir, p);
5692
6425
  const skillMd = resolve23(skillDir, "SKILL.md");
5693
- if (existsSync35(skillMd)) {
6426
+ if (existsSync38(skillMd)) {
5694
6427
  passes.push(`skills[${i}]: ${p}/SKILL.md exists`);
5695
- } else if (existsSync35(skillDir)) {
6428
+ } else if (existsSync38(skillDir)) {
5696
6429
  warnings.push(`skills[${i}]: directory exists but no SKILL.md inside`);
5697
6430
  } else {
5698
6431
  warnings.push(`skills[${i}]: path "${p}" does not exist`);
@@ -5704,7 +6437,7 @@ var init_plugin4 = __esm(() => {
5704
6437
  passes.push(`mcpServers: "${manifest.mcpServers}"`);
5705
6438
  const mcpRef = String(manifest.mcpServers);
5706
6439
  const mcpPath = resolve23(dir, mcpRef);
5707
- if (existsSync35(mcpPath)) {
6440
+ if (existsSync38(mcpPath)) {
5708
6441
  passes.push(`mcpServers file exists at ${mcpRef}`);
5709
6442
  } else {
5710
6443
  warnings.push(`mcpServers path "${mcpRef}" does not exist on disk`);
@@ -5733,8 +6466,14 @@ var init_plugin4 = __esm(() => {
5733
6466
  passes.push("homepage present");
5734
6467
  if (manifest.repository)
5735
6468
  passes.push("repository present");
5736
- if (Array.isArray(manifest.keywords)) {
5737
- passes.push(`keywords: [${manifest.keywords.join(", ")}]`);
6469
+ if (manifest.keywords !== undefined) {
6470
+ if (Array.isArray(manifest.keywords)) {
6471
+ passes.push(`keywords: [${manifest.keywords.join(", ")}] \u2014 If users mention any of these keywords, your plugin will get triggered in Copilot CLI`);
6472
+ } else {
6473
+ errors.push("keywords must be an array of strings");
6474
+ }
6475
+ } else {
6476
+ warnings.push('Missing "keywords" (recommended \u2014 if users mention any of these, your plugin will get triggered in Copilot CLI)');
5738
6477
  }
5739
6478
  const known = new Set([
5740
6479
  "name",
@@ -5758,7 +6497,7 @@ var init_plugin4 = __esm(() => {
5758
6497
  });
5759
6498
 
5760
6499
  // src/validators/copilot/marketplace.ts
5761
- import { existsSync as existsSync36 } from "fs";
6500
+ import { existsSync as existsSync39 } from "fs";
5762
6501
  import { resolve as resolve24 } from "path";
5763
6502
  var copilotMarketplaceValidator;
5764
6503
  var init_marketplace4 = __esm(() => {
@@ -5768,7 +6507,7 @@ var init_marketplace4 = __esm(() => {
5768
6507
  name: "Copilot Plugin Marketplace",
5769
6508
  description: "Validates .github/plugin/marketplace.json (string sources)",
5770
6509
  detect(dir) {
5771
- return existsSync36(resolve24(dir, ".github", "plugin", "marketplace.json"));
6510
+ return existsSync39(resolve24(dir, ".github", "plugin", "marketplace.json"));
5772
6511
  },
5773
6512
  async validate(dir, _opts) {
5774
6513
  const errors = [];
@@ -5818,9 +6557,9 @@ var init_marketplace4 = __esm(() => {
5818
6557
  const src = String(p.source);
5819
6558
  passes.push(`plugins[${i}].source: "${src}"`);
5820
6559
  const srcDir = resolve24(dir, src);
5821
- if (existsSync36(srcDir)) {
5822
- const hasManifest = existsSync36(resolve24(srcDir, ".github", "plugin", "plugin.json"));
5823
- const hasSkills = existsSync36(resolve24(srcDir, "skills"));
6560
+ if (existsSync39(srcDir)) {
6561
+ const hasManifest = existsSync39(resolve24(srcDir, ".github", "plugin", "plugin.json"));
6562
+ const hasSkills = existsSync39(resolve24(srcDir, "skills"));
5824
6563
  if (hasManifest || hasSkills) {
5825
6564
  passes.push(`plugins[${i}]: source exists (${hasManifest ? "manifest" : "skills/"})`);
5826
6565
  } else {
@@ -5837,12 +6576,12 @@ var init_marketplace4 = __esm(() => {
5837
6576
  if (p.version)
5838
6577
  passes.push(`plugins[${i}].version: "${p.version}"`);
5839
6578
  }
5840
- if (existsSync36(resolve24(dir, "README.md"))) {
6579
+ if (existsSync39(resolve24(dir, "README.md"))) {
5841
6580
  passes.push("README.md exists at marketplace root");
5842
6581
  } else {
5843
6582
  warnings.push("No README.md at marketplace root \u2014 recommended");
5844
6583
  }
5845
- if (existsSync36(resolve24(dir, "LICENSE"))) {
6584
+ if (existsSync39(resolve24(dir, "LICENSE"))) {
5846
6585
  passes.push("LICENSE exists at marketplace root");
5847
6586
  } else {
5848
6587
  warnings.push("No LICENSE at marketplace root \u2014 recommended");
@@ -5853,7 +6592,7 @@ var init_marketplace4 = __esm(() => {
5853
6592
  });
5854
6593
 
5855
6594
  // src/validators/copilot/mcp.ts
5856
- import { existsSync as existsSync37 } from "fs";
6595
+ import { existsSync as existsSync40 } from "fs";
5857
6596
  import { resolve as resolve25 } from "path";
5858
6597
  var copilotMcpValidator;
5859
6598
  var init_mcp4 = __esm(() => {
@@ -5863,7 +6602,7 @@ var init_mcp4 = __esm(() => {
5863
6602
  name: "Copilot MCP Config",
5864
6603
  description: "Validates .mcp.json (referenced via mcpServers in manifest). Supports stdio and http servers.",
5865
6604
  detect(dir) {
5866
- return existsSync37(resolve25(dir, ".mcp.json"));
6605
+ return existsSync40(resolve25(dir, ".mcp.json"));
5867
6606
  },
5868
6607
  async validate(dir, _opts) {
5869
6608
  const errors = [];
@@ -5929,7 +6668,7 @@ var init_mcp4 = __esm(() => {
5929
6668
  });
5930
6669
 
5931
6670
  // src/validators/copilot/skill.ts
5932
- import { existsSync as existsSync38 } from "fs";
6671
+ import { existsSync as existsSync41 } from "fs";
5933
6672
  import { resolve as resolve26 } from "path";
5934
6673
  var copilotSkillValidator;
5935
6674
  var init_skill4 = __esm(() => {
@@ -5940,7 +6679,7 @@ var init_skill4 = __esm(() => {
5940
6679
  name: "Copilot Skill",
5941
6680
  description: "Validates SKILL.md (shared format). Copilot supports skills referenced via array paths in the manifest.",
5942
6681
  detect(dir) {
5943
- return existsSync38(resolve26(dir, "SKILL.md"));
6682
+ return existsSync41(resolve26(dir, "SKILL.md"));
5944
6683
  },
5945
6684
  async validate(dir, _opts) {
5946
6685
  const loaded = await loadSkill(dir);
@@ -6108,7 +6847,7 @@ var init_validators = __esm(() => {
6108
6847
  // src/core/remote.ts
6109
6848
  import { spawnSync as spawnSync4 } from "child_process";
6110
6849
  import { mkdtempSync, rmSync } from "fs";
6111
- import { join as join25 } from "path";
6850
+ import { join as join28 } from "path";
6112
6851
  import { tmpdir } from "os";
6113
6852
  function parseRemoteUrl(input) {
6114
6853
  if (input.startsWith(".") || input.startsWith("/") || input.startsWith("~")) {
@@ -6145,7 +6884,7 @@ function isGhAvailable() {
6145
6884
  return ghAvailable;
6146
6885
  }
6147
6886
  async function cloneToTemp(parsed) {
6148
- const tmpDir = mkdtempSync(join25(tmpdir(), "dora-"));
6887
+ const tmpDir = mkdtempSync(join28(tmpdir(), "dora-"));
6149
6888
  const cleanup = () => {
6150
6889
  try {
6151
6890
  rmSync(tmpDir, { recursive: true, force: true });
@@ -6193,19 +6932,19 @@ var exports_validate_top = {};
6193
6932
  __export(exports_validate_top, {
6194
6933
  default: () => validate_top_default
6195
6934
  });
6196
- import { existsSync as existsSync40 } from "fs";
6935
+ import { existsSync as existsSync43 } from "fs";
6197
6936
  import { resolve as resolve27 } from "path";
6198
- var import_picocolors15, validate_top_default;
6937
+ var import_picocolors17, validate_top_default;
6199
6938
  var init_validate_top = __esm(() => {
6200
6939
  init_dist();
6201
6940
  init_out();
6202
6941
  init_validators();
6203
6942
  init_remote();
6204
- import_picocolors15 = __toESM(require_picocolors(), 1);
6943
+ import_picocolors17 = __toESM(require_picocolors(), 1);
6205
6944
  validate_top_default = defineCommand({
6206
6945
  meta: {
6207
6946
  name: "validate",
6208
- description: "Auto-detect project type and run matching validators. Accepts a local path or a Git URL (e.g. https://github.com/owner/repo)"
6947
+ description: "Auto-detect project type and run matching validators. Accepts a local path or a Git URL (e.g. https://github.com/owner/repo). Use --for <provider>:plugin to see keyword trigger messages."
6209
6948
  },
6210
6949
  args: {
6211
6950
  path: {
@@ -6241,7 +6980,7 @@ var init_validate_top = __esm(() => {
6241
6980
  let cleanup;
6242
6981
  if (remote) {
6243
6982
  ui.info(`
6244
- Cloning ${import_picocolors15.default.dim(args.path)}...`);
6983
+ Cloning ${import_picocolors17.default.dim(args.path)}...`);
6245
6984
  try {
6246
6985
  const result = await cloneToTemp(remote);
6247
6986
  fullPath = remote.subpath ? resolve27(result.dir, remote.subpath) : result.dir;
@@ -6251,14 +6990,14 @@ var init_validate_top = __esm(() => {
6251
6990
  ui.fail(msg);
6252
6991
  process.exit(1);
6253
6992
  }
6254
- if (!existsSync40(fullPath)) {
6993
+ if (!existsSync43(fullPath)) {
6255
6994
  cleanup();
6256
6995
  ui.fail(`Subdirectory not found in repo: ${remote.subpath}`);
6257
6996
  process.exit(1);
6258
6997
  }
6259
6998
  } else {
6260
6999
  fullPath = resolve27(args.path);
6261
- if (!existsSync40(fullPath)) {
7000
+ if (!existsSync43(fullPath)) {
6262
7001
  ui.fail(`Path not found: ${args.path}
6263
7002
 
6264
7003
  Check that the path is correct and the directory exists.`);
@@ -6289,13 +7028,13 @@ Check that the path is correct and the directory exists.`);
6289
7028
  ` + `Available providers:
6290
7029
  ` + providers.map((p) => {
6291
7030
  const pvs = validators.filter((v) => v.provider === p);
6292
- return ` ${import_picocolors15.default.bold(p)}
6293
- ` + pvs.map((v) => ` \u2022 ${import_picocolors15.default.dim(v.id)} \u2014 ${v.description}`).join(`
7031
+ return ` ${import_picocolors17.default.bold(p)}
7032
+ ` + pvs.map((v) => ` \u2022 ${import_picocolors17.default.dim(v.id)} \u2014 ${v.description}`).join(`
6294
7033
  `);
6295
7034
  }).join(`
6296
7035
  `) + `
6297
7036
 
6298
- Use ${import_picocolors15.default.dim("--for <provider>")} or ${import_picocolors15.default.dim("--for <provider:type>")} to target explicitly.`);
7037
+ Use ${import_picocolors17.default.dim("--for <provider>")} or ${import_picocolors17.default.dim("--for <provider:type>")} to target explicitly.`);
6299
7038
  process.exit(1);
6300
7039
  }
6301
7040
  const allResults = [];
@@ -6316,7 +7055,7 @@ Use ${import_picocolors15.default.dim("--for <provider>")} or ${import_picocolor
6316
7055
  } else {
6317
7056
  for (const { id, name, result } of allResults) {
6318
7057
  ui.write(`
6319
- ${import_picocolors15.default.bold("dora validate")} \u2014 ${import_picocolors15.default.white(name)} ${import_picocolors15.default.dim(`(${id})`)}
7058
+ ${import_picocolors17.default.bold("dora validate")} \u2014 ${import_picocolors17.default.white(name)} ${import_picocolors17.default.dim(`(${id})`)}
6320
7059
  `);
6321
7060
  ui.info(` Path: ${args.path}
6322
7061
  `);
@@ -6331,7 +7070,7 @@ Use ${import_picocolors15.default.dim("--for <provider>")} or ${import_picocolor
6331
7070
  }
6332
7071
  if (result.errors.length === 0 && result.warnings.length === 0) {
6333
7072
  ui.write(`
6334
- ${import_picocolors15.default.green("\u2713")} ${import_picocolors15.default.white("All checks passed.")}
7073
+ ${import_picocolors17.default.green("\u2713")} ${import_picocolors17.default.white("All checks passed.")}
6335
7074
  `);
6336
7075
  } else {
6337
7076
  ui.info(`
@@ -6353,16 +7092,16 @@ var exports_init2 = {};
6353
7092
  __export(exports_init2, {
6354
7093
  default: () => init_default2
6355
7094
  });
6356
- import { basename as basename6, join as join26 } from "path";
7095
+ import { basename as basename6, join as join29 } from "path";
6357
7096
  var {spawnSync: spawnSync5 } = globalThis.Bun;
6358
- var import_picocolors16, init_default2;
7097
+ var import_picocolors18, init_default2;
6359
7098
  var init_init2 = __esm(() => {
6360
7099
  init_dist();
6361
7100
  init_out();
6362
7101
  init_journal_config();
6363
7102
  init_journal_remote();
6364
7103
  init_prompt();
6365
- import_picocolors16 = __toESM(require_picocolors(), 1);
7104
+ import_picocolors18 = __toESM(require_picocolors(), 1);
6366
7105
  init_default2 = defineCommand({
6367
7106
  meta: {
6368
7107
  name: "init",
@@ -6389,17 +7128,17 @@ var init_init2 = __esm(() => {
6389
7128
  ui.heading("dora init \u2014 Set up doraval, your journal, and the coding agent dora should use on the fly");
6390
7129
  const ghCheck = ensureGhCli();
6391
7130
  if (!ghCheck.ok) {
6392
- ui.write(` ${import_picocolors16.default.red("\u2717")} ${import_picocolors16.default.white("The GitHub CLI (")}${import_picocolors16.default.bold("gh")}${import_picocolors16.default.white(") is not installed.")}
7131
+ ui.write(` ${import_picocolors18.default.red("\u2717")} ${import_picocolors18.default.white("The GitHub CLI (")}${import_picocolors18.default.bold("gh")}${import_picocolors18.default.white(") is not installed.")}
6393
7132
  `);
6394
- ui.info(` doraval uses ${import_picocolors16.default.bold("gh")} to fetch and sync journal files with GitHub.
7133
+ ui.info(` doraval uses ${import_picocolors18.default.bold("gh")} to fetch and sync journal files with GitHub.
6395
7134
  `);
6396
7135
  ui.info(` Install it:
6397
7136
  `);
6398
- ui.info(` macOS: ${import_picocolors16.default.dim("brew install gh")}`);
6399
- ui.info(` Linux: ${import_picocolors16.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
6400
- ui.info(` Windows: ${import_picocolors16.default.dim("winget install --id GitHub.cli")}
7137
+ ui.info(` macOS: ${import_picocolors18.default.dim("brew install gh")}`);
7138
+ ui.info(` Linux: ${import_picocolors18.default.dim("https://github.com/cli/cli/blob/trunk/docs/install_linux.md")}`);
7139
+ ui.info(` Windows: ${import_picocolors18.default.dim("winget install --id GitHub.cli")}
6401
7140
  `);
6402
- ui.info(` Then authenticate: ${import_picocolors16.default.dim("gh auth login")}
7141
+ ui.info(` Then authenticate: ${import_picocolors18.default.dim("gh auth login")}
6403
7142
  `);
6404
7143
  process.exit(1);
6405
7144
  }
@@ -6412,28 +7151,28 @@ var init_init2 = __esm(() => {
6412
7151
  if (gitOwner) {
6413
7152
  defaultRepo = `${gitOwner}/${gitOwner}.md`;
6414
7153
  if (ghLogin && ghLogin !== gitOwner) {
6415
- sourceNote = ` ${import_picocolors16.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
7154
+ sourceNote = ` ${import_picocolors18.default.dim("(from git remote; your active gh account is " + ghLogin + ")")}
6416
7155
  `;
6417
7156
  } else {
6418
- sourceNote = ` ${import_picocolors16.default.dim("(from git remote)")}
7157
+ sourceNote = ` ${import_picocolors18.default.dim("(from git remote)")}
6419
7158
  `;
6420
7159
  }
6421
7160
  } else if (ghLogin) {
6422
7161
  defaultRepo = `${ghLogin}/${ghLogin}.md`;
6423
- sourceNote = ` ${import_picocolors16.default.dim("(from your active gh account)")}
7162
+ sourceNote = ` ${import_picocolors18.default.dim("(from your active gh account)")}
6424
7163
  `;
6425
7164
  } else {
6426
- ui.warn(`Not logged in to GitHub. Run ${import_picocolors16.default.dim("gh auth login")} first.
7165
+ ui.warn(`Not logged in to GitHub. Run ${import_picocolors18.default.dim("gh auth login")} first.
6427
7166
  `);
6428
7167
  process.exit(1);
6429
7168
  }
6430
7169
  const existingConfig = await readConfig();
6431
7170
  if (existingConfig?.journal.repo) {
6432
7171
  defaultRepo = existingConfig.journal.repo;
6433
- sourceNote = ` ${import_picocolors16.default.dim("(from your previous journal setup)")}
7172
+ sourceNote = ` ${import_picocolors18.default.dim("(from your previous journal setup)")}
6434
7173
  `;
6435
7174
  }
6436
- ui.info(` Journal repo ${import_picocolors16.default.dim("(owner/name)")}`);
7175
+ ui.info(` Journal repo ${import_picocolors18.default.dim("(owner/name)")}`);
6437
7176
  if (sourceNote)
6438
7177
  ui.write(sourceNote);
6439
7178
  repo = prompt(" >", defaultRepo);
@@ -6445,11 +7184,11 @@ var init_init2 = __esm(() => {
6445
7184
  }
6446
7185
  project = sanitizeProjectName(project);
6447
7186
  if (!repoExists(repo)) {
6448
- ui.write(` ${import_picocolors16.default.red("\u2717")} ${import_picocolors16.default.white("Repository")} ${import_picocolors16.default.bold(repo)} ${import_picocolors16.default.white("not found on GitHub.")}
7187
+ ui.write(` ${import_picocolors18.default.red("\u2717")} ${import_picocolors18.default.white("Repository")} ${import_picocolors18.default.bold(repo)} ${import_picocolors18.default.white("not found on GitHub.")}
6449
7188
  `);
6450
7189
  ui.info(` Create it first:
6451
7190
  `);
6452
- ui.info(` ${import_picocolors16.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
7191
+ ui.info(` ${import_picocolors18.default.dim(`gh repo create ${repo} --private --description "Personal journal for agent decisions"`)}
6453
7192
  `);
6454
7193
  process.exit(1);
6455
7194
  }
@@ -6457,16 +7196,16 @@ var init_init2 = __esm(() => {
6457
7196
  const alreadyRegistered = existing?.journal.projects[project];
6458
7197
  const isRefresh = alreadyRegistered && args.refresh;
6459
7198
  if (alreadyRegistered && !isRefresh) {
6460
- ui.write(` ${import_picocolors16.default.yellow("\u26A0")} ${import_picocolors16.default.white("Project")} ${import_picocolors16.default.bold(project)} ${import_picocolors16.default.white("is already registered.")}
7199
+ ui.write(` ${import_picocolors18.default.yellow("\u26A0")} ${import_picocolors18.default.white("Project")} ${import_picocolors18.default.bold(project)} ${import_picocolors18.default.white("is already registered.")}
6461
7200
  `);
6462
7201
  ui.info(` Repo: ${existing.journal.repo}
6463
7202
  `);
6464
- ui.info(` To refresh journal files, use ${import_picocolors16.default.dim("dora journal update")} (or ${import_picocolors16.default.dim("dora init --refresh")}).
7203
+ ui.info(` To refresh journal files, use ${import_picocolors18.default.dim("dora journal update")} (or ${import_picocolors18.default.dim("dora init --refresh")}).
6465
7204
  `);
6466
7205
  }
6467
7206
  const journalsDir = getJournalsDir();
6468
7207
  const remotePath = `projects/${project}.md`;
6469
- const localPath = join26(journalsDir, `${project}.md`);
7208
+ const localPath = join29(journalsDir, `${project}.md`);
6470
7209
  const effectiveRepo = isRefresh && !args.repo ? existing.journal.repo : repo;
6471
7210
  const config = existing ?? {
6472
7211
  journal: { repo: effectiveRepo, projects: {} }
@@ -6477,9 +7216,9 @@ var init_init2 = __esm(() => {
6477
7216
  local_path: localPath
6478
7217
  };
6479
7218
  ensureDoravalDirs();
6480
- ui.write(` ${import_picocolors16.default.dim(import_picocolors16.default.gray("Fetching journal files from"))} ${import_picocolors16.default.gray(effectiveRepo)}${import_picocolors16.default.dim(import_picocolors16.default.gray("..."))}
7219
+ ui.write(` ${import_picocolors18.default.dim(import_picocolors18.default.gray("Fetching journal files from"))} ${import_picocolors18.default.gray(effectiveRepo)}${import_picocolors18.default.dim(import_picocolors18.default.gray("..."))}
6481
7220
  `);
6482
- const globalDest = join26(journalsDir, "global.md");
7221
+ const globalDest = join29(journalsDir, "global.md");
6483
7222
  const refreshGlobalRes = await refreshLocalJournalFile(effectiveRepo, "global.md", globalDest);
6484
7223
  let wroteGlobal;
6485
7224
  if (!refreshGlobalRes.ok) {
@@ -6496,7 +7235,7 @@ var init_init2 = __esm(() => {
6496
7235
  if (wroteGlobal) {
6497
7236
  ui.success("global.md");
6498
7237
  } else {
6499
- ui.write(` ${import_picocolors16.default.dim("\xB7")} global.md ${import_picocolors16.default.dim("(not found \u2014 will be created on first sync)")}`);
7238
+ ui.write(` ${import_picocolors18.default.dim("\xB7")} global.md ${import_picocolors18.default.dim("(not found \u2014 will be created on first sync)")}`);
6500
7239
  await Bun.write(globalDest, `# Global Journal
6501
7240
 
6502
7241
  Cross-project principles.
@@ -6518,7 +7257,7 @@ Cross-project principles.
6518
7257
  if (wroteProject) {
6519
7258
  ui.success(remotePath);
6520
7259
  } else {
6521
- ui.write(` ${import_picocolors16.default.dim("\xB7")} ${remotePath} ${import_picocolors16.default.dim("(not found \u2014 will be created on first sync)")}`);
7260
+ ui.write(` ${import_picocolors18.default.dim("\xB7")} ${remotePath} ${import_picocolors18.default.dim("(not found \u2014 will be created on first sync)")}`);
6522
7261
  await Bun.write(localPath, `# ${project} Journal
6523
7262
 
6524
7263
  Project-specific decisions.
@@ -6526,13 +7265,13 @@ Project-specific decisions.
6526
7265
  }
6527
7266
  await writeConfig(config);
6528
7267
  ui.write(`
6529
- ${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Journal ready for project")} ${import_picocolors16.default.bold(import_picocolors16.default.white(project))}.
7268
+ ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.white("Journal ready for project")} ${import_picocolors18.default.bold(import_picocolors18.default.white(project))}.
6530
7269
  `);
6531
7270
  const existingAgent = (await readConfig())?.agent;
6532
7271
  if (existingAgent?.command) {
6533
- ui.write(` ${import_picocolors16.default.bold(import_picocolors16.default.white("Coding agent (already configured)"))}
7272
+ ui.write(` ${import_picocolors18.default.bold(import_picocolors18.default.white("Coding agent (already configured)"))}
6534
7273
  `);
6535
- ui.write(` Current: ${import_picocolors16.default.dim(import_picocolors16.default.gray(existingAgent.command))} template: ${import_picocolors16.default.dim(import_picocolors16.default.gray(existingAgent.prompt_template || "(default)"))}
7274
+ ui.write(` Current: ${import_picocolors18.default.dim(import_picocolors18.default.gray(existingAgent.command))} template: ${import_picocolors18.default.dim(import_picocolors18.default.gray(existingAgent.prompt_template || "(default)"))}
6536
7275
  `);
6537
7276
  const change = prompt(" Reconfigure / change the coding agent for on-the-fly enrichment? (y/N)", "n");
6538
7277
  if (!/^y/i.test(String(change))) {
@@ -6542,16 +7281,16 @@ Project-specific decisions.
6542
7281
  if (existingAgent)
6543
7282
  cfg.agent = existingAgent;
6544
7283
  await writeConfig(cfg);
6545
- ui.write(` ${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Try:")} ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add "short decision"'))}
7284
+ ui.write(` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.white("Try:")} ${import_picocolors18.default.dim(import_picocolors18.default.gray('dora journal add "short decision"'))}
6546
7285
  `);
6547
7286
  process.exit(0);
6548
7287
  return;
6549
7288
  }
6550
7289
  ui.blank();
6551
7290
  } else {
6552
- ui.write(` ${import_picocolors16.default.bold(import_picocolors16.default.white("Coding agent for journal add"))}
7291
+ ui.write(` ${import_picocolors18.default.bold(import_picocolors18.default.white("Coding agent for journal add"))}
6553
7292
  `);
6554
- ui.info(` When configured, ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
7293
+ ui.info(` When configured, ${import_picocolors18.default.dim(import_picocolors18.default.gray('dora journal add ".."'))} will use your agent to enrich entries with tags and rationale automatically.
6555
7294
  `);
6556
7295
  }
6557
7296
  const common = [
@@ -6570,7 +7309,7 @@ Project-specific decisions.
6570
7309
  }
6571
7310
  }
6572
7311
  let agentCmd = detected || "claude";
6573
- ui.write(` Detected / default agent command: ${import_picocolors16.default.dim(import_picocolors16.default.gray(agentCmd))}`);
7312
+ ui.write(` Detected / default agent command: ${import_picocolors18.default.dim(import_picocolors18.default.gray(agentCmd))}`);
6574
7313
  agentCmd = prompt(" Agent command (the binary you run for prompts)", agentCmd);
6575
7314
  let template = detected ? common.find((c) => c.name === detected)?.template || '-p "{{prompt}}" --output-format json' : '-p "{{prompt}}" --output-format json';
6576
7315
  ui.info(` Prompt template (use {{prompt}} placeholder):`);
@@ -6582,11 +7321,11 @@ Project-specific decisions.
6582
7321
  };
6583
7322
  await writeConfig(finalConfig);
6584
7323
  ui.write(`
6585
- ${import_picocolors16.default.green("\u2713")} ${import_picocolors16.default.white("Agent configured.")}
7324
+ ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.white("Agent configured.")}
6586
7325
  `);
6587
- ui.info(` Re-run ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora init"))} anytime to change it.
7326
+ ui.info(` Re-run ${import_picocolors18.default.dim(import_picocolors18.default.gray("dora init"))} anytime to change it.
6588
7327
  `);
6589
- ui.info(` Next: ${import_picocolors16.default.dim(import_picocolors16.default.gray('dora journal add ".."'))}, ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora journal list"))}, or ${import_picocolors16.default.dim(import_picocolors16.default.gray("dora journal update"))}.
7328
+ ui.info(` Next: ${import_picocolors18.default.dim(import_picocolors18.default.gray('dora journal add ".."'))}, ${import_picocolors18.default.dim(import_picocolors18.default.gray("dora journal list"))}, or ${import_picocolors18.default.dim(import_picocolors18.default.gray("dora journal update"))}.
6590
7329
  `);
6591
7330
  process.exit(0);
6592
7331
  }
@@ -6595,7 +7334,7 @@ Project-specific decisions.
6595
7334
 
6596
7335
  // src/core/update.ts
6597
7336
  import { resolve as resolve28 } from "path";
6598
- import { homedir as homedir2 } from "os";
7337
+ import { homedir as homedir3 } from "os";
6599
7338
  function normalizePath(p) {
6600
7339
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
6601
7340
  }
@@ -6773,14 +7512,14 @@ async function readMarker() {
6773
7512
  async function writeMarker(marker) {
6774
7513
  try {
6775
7514
  const { mkdir, writeFile } = await import("fs/promises");
6776
- const { dirname: dirname7 } = await import("path");
6777
- await mkdir(dirname7(MARKER_PATH), { recursive: true });
7515
+ const { dirname: dirname8 } = await import("path");
7516
+ await mkdir(dirname8(MARKER_PATH), { recursive: true });
6778
7517
  await writeFile(MARKER_PATH, JSON.stringify(marker, null, 2));
6779
7518
  } catch {}
6780
7519
  }
6781
7520
  var MARKER_PATH;
6782
7521
  var init_update2 = __esm(() => {
6783
- MARKER_PATH = resolve28(homedir2(), ".doraval", "install.json");
7522
+ MARKER_PATH = resolve28(homedir3(), ".doraval", "install.json");
6784
7523
  });
6785
7524
 
6786
7525
  // src/cli/commands/update.ts
@@ -6789,7 +7528,7 @@ __export(exports_update2, {
6789
7528
  default: () => update_default2
6790
7529
  });
6791
7530
  import { spawnSync as spawnSync6 } from "child_process";
6792
- import { homedir as homedir3 } from "os";
7531
+ import { homedir as homedir4 } from "os";
6793
7532
  import { fileURLToPath } from "url";
6794
7533
  import { realpath, access } from "fs/promises";
6795
7534
  async function confirmUpdate() {
@@ -6835,7 +7574,7 @@ var init_update3 = __esm(() => {
6835
7574
  entrypoint,
6836
7575
  argv: process.argv,
6837
7576
  env: process.env,
6838
- homeDir: homedir3(),
7577
+ homeDir: homedir4(),
6839
7578
  realpath: (p) => realpath(p),
6840
7579
  exists: async (p) => {
6841
7580
  try {
@@ -6926,16 +7665,16 @@ var exports_providers = {};
6926
7665
  __export(exports_providers, {
6927
7666
  default: () => providers_default
6928
7667
  });
6929
- var import_picocolors17, providers_default;
7668
+ var import_picocolors19, providers_default;
6930
7669
  var init_providers2 = __esm(() => {
6931
7670
  init_dist();
6932
7671
  init_out();
6933
7672
  init_spec();
6934
- import_picocolors17 = __toESM(require_picocolors(), 1);
7673
+ import_picocolors19 = __toESM(require_picocolors(), 1);
6935
7674
  providers_default = defineCommand({
6936
7675
  meta: {
6937
7676
  name: "providers",
6938
- description: "List supported providers and their packaging details"
7677
+ description: "List supported providers and their packaging details (including keyword discovery)"
6939
7678
  },
6940
7679
  args: {
6941
7680
  json: {
@@ -6956,14 +7695,136 @@ var init_providers2 = __esm(() => {
6956
7695
  for (const id of supportedProviders) {
6957
7696
  const spec = getProviderSpec(id);
6958
7697
  ui.write(`
6959
- ${import_picocolors17.default.bold(id)} \u2014 ${spec.name}`);
7698
+ ${import_picocolors19.default.bold(id)} \u2014 ${spec.name}`);
6960
7699
  ui.info(` Manifest: ${spec.manifestPath}`);
6961
7700
  ui.info(` Marketplace: ${spec.marketplacePath}`);
6962
7701
  ui.info(` MCP: ${spec.mcpFilename}`);
6963
- ui.info(` Example: doraval validate . --for ${id}`);
7702
+ ui.info(` Keywords in plugin.json: supported \u2014 If users mention any of these keywords, your plugin will get triggered`);
7703
+ ui.info(` Example: doraval validate . --for ${id}:plugin`);
6964
7704
  }
6965
7705
  ui.write(`
6966
7706
  Use --json for machine-readable output.`);
7707
+ ui.write(` Tip: Add a "keywords" array to your plugin manifest for better agent discovery.`);
7708
+ process.exit(0);
7709
+ }
7710
+ });
7711
+ });
7712
+
7713
+ // src/cli/commands/completion.ts
7714
+ var exports_completion = {};
7715
+ __export(exports_completion, {
7716
+ default: () => completion_default
7717
+ });
7718
+ var commands, subCommands, completion_default;
7719
+ var init_completion = __esm(() => {
7720
+ init_dist();
7721
+ commands = [
7722
+ "validate",
7723
+ "init",
7724
+ "bump",
7725
+ "update",
7726
+ "providers",
7727
+ "skill",
7728
+ "journal",
7729
+ "claude",
7730
+ "codex",
7731
+ "cursor",
7732
+ "copilot"
7733
+ ];
7734
+ subCommands = {
7735
+ skill: ["validate", "drift", "judge"],
7736
+ journal: ["init", "list", "context", "hook", "update", "add", "sync"],
7737
+ hook: ["enable", "disable", "status"],
7738
+ claude: ["new", "bump"],
7739
+ codex: ["new", "bump"],
7740
+ cursor: ["new", "bump"],
7741
+ copilot: ["new", "bump"]
7742
+ };
7743
+ completion_default = defineCommand({
7744
+ meta: {
7745
+ name: "completion",
7746
+ description: "Generate shell completion scripts (bash, zsh, fish)"
7747
+ },
7748
+ args: {
7749
+ shell: {
7750
+ type: "positional",
7751
+ description: "Shell to generate completion for (bash | zsh | fish)",
7752
+ required: true
7753
+ }
7754
+ },
7755
+ run({ args }) {
7756
+ const shell = String(args.shell).toLowerCase();
7757
+ if (shell === "bash") {
7758
+ console.log(`# doraval bash completion
7759
+ _doraval_completions() {
7760
+ local cur prev
7761
+ COMPREPLY=()
7762
+ cur="\${COMP_WORDS[COMP_CWORD]}"
7763
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
7764
+
7765
+ if [ $COMP_CWORD -eq 1 ]; then
7766
+ COMPREPLY=( $(compgen -W "${commands.join(" ")}" -- "$cur") )
7767
+ elif [ $COMP_CWORD -eq 2 ]; then
7768
+ case "$prev" in
7769
+ skill) COMPREPLY=( $(compgen -W "${subCommands.skill.join(" ")}" -- "$cur") ) ;;
7770
+ journal) COMPREPLY=( $(compgen -W "${subCommands.journal.join(" ")}" -- "$cur") ) ;;
7771
+ hook) COMPREPLY=( $(compgen -W "${subCommands.hook.join(" ")}" -- "$cur") ) ;;
7772
+ claude|codex|cursor|copilot) COMPREPLY=( $(compgen -W "${subCommands.claude.join(" ")}" -- "$cur") ) ;;
7773
+ esac
7774
+ fi
7775
+ }
7776
+ complete -F _doraval_completions doraval
7777
+ `);
7778
+ } else if (shell === "zsh") {
7779
+ console.log(`# doraval zsh completion
7780
+ #compdef doraval
7781
+
7782
+ _doraval() {
7783
+ local -a commands sub
7784
+ commands=(validate init bump update providers skill journal claude codex cursor copilot)
7785
+ _arguments -C \\
7786
+ '1: :->cmd' \\
7787
+ '*::arg:->args'
7788
+
7789
+ case $state in
7790
+ cmd)
7791
+ _describe 'command' commands
7792
+ ;;
7793
+ args)
7794
+ case $words[1] in
7795
+ skill)
7796
+ _describe 'subcommand' (validate drift judge)
7797
+ ;;
7798
+ journal)
7799
+ _describe 'subcommand' (init list context hook update add sync)
7800
+ ;;
7801
+ hook)
7802
+ _describe 'subcommand' (enable disable status)
7803
+ ;;
7804
+ claude|codex|cursor|copilot)
7805
+ _describe 'subcommand' (new bump)
7806
+ ;;
7807
+ esac
7808
+ ;;
7809
+ esac
7810
+ }
7811
+
7812
+ _doraval "$@"
7813
+ `);
7814
+ } else if (shell === "fish") {
7815
+ console.log(`# doraval fish completion
7816
+ complete -c doraval -f
7817
+ complete -c doraval -n '__fish_use_subcommand' -a 'validate init bump update providers skill journal claude codex cursor copilot'
7818
+
7819
+ complete -c doraval -n '__fish_seen_subcommand_from skill' -a 'validate drift judge'
7820
+ complete -c doraval -n '__fish_seen_subcommand_from journal' -a 'init list context hook update add sync'
7821
+ complete -c doraval -n '__fish_seen_subcommand_from hook' -a 'enable disable status'
7822
+ complete -c doraval -n '__fish_seen_subcommand_from claude codex cursor copilot' -a 'new bump'
7823
+ `);
7824
+ } else {
7825
+ console.error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
7826
+ process.exit(1);
7827
+ }
6967
7828
  process.exit(0);
6968
7829
  }
6969
7830
  });
@@ -6972,7 +7833,7 @@ var init_providers2 = __esm(() => {
6972
7833
  // src/cli/index.ts
6973
7834
  init_dist();
6974
7835
  var import__package = __toESM(require_package(), 1);
6975
- var import_picocolors18 = __toESM(require_picocolors(), 1);
7836
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
6976
7837
  var skill = defineCommand({
6977
7838
  meta: {
6978
7839
  name: "skill",
@@ -6984,6 +7845,9 @@ var skill = defineCommand({
6984
7845
  judge: () => Promise.resolve().then(() => (init_judge(), exports_judge)).then((m) => m.default)
6985
7846
  },
6986
7847
  run() {
7848
+ const cliArgs = process.argv.slice(2);
7849
+ if (cliArgs[0] === "skill" && cliArgs.length > 1)
7850
+ return;
6987
7851
  showUsage(skill);
6988
7852
  }
6989
7853
  });
@@ -6995,11 +7859,16 @@ var journal = defineCommand({
6995
7859
  subCommands: {
6996
7860
  init: () => Promise.resolve().then(() => (init_init(), exports_init)).then((m) => m.default),
6997
7861
  list: () => Promise.resolve().then(() => (init_list(), exports_list)).then((m) => m.default),
7862
+ context: () => Promise.resolve().then(() => (init_context(), exports_context)).then((m) => m.default),
7863
+ hook: () => Promise.resolve().then(() => (init_hook(), exports_hook)).then((m) => m.default),
6998
7864
  update: () => Promise.resolve().then(() => (init_update(), exports_update)).then((m) => m.default),
6999
7865
  add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((m) => m.default),
7000
7866
  sync: () => Promise.resolve().then(() => (init_sync(), exports_sync)).then((m) => m.default)
7001
7867
  },
7002
7868
  run() {
7869
+ const cliArgs = process.argv.slice(2);
7870
+ if (cliArgs[0] === "journal" && cliArgs.length > 1)
7871
+ return;
7003
7872
  showUsage(journal);
7004
7873
  }
7005
7874
  });
@@ -7013,6 +7882,9 @@ var claude = defineCommand({
7013
7882
  bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
7014
7883
  },
7015
7884
  run() {
7885
+ const cliArgs = process.argv.slice(2);
7886
+ if (cliArgs[0] === "claude" && cliArgs.length > 1)
7887
+ return;
7016
7888
  showUsage(claude);
7017
7889
  }
7018
7890
  });
@@ -7026,6 +7898,9 @@ var codex = defineCommand({
7026
7898
  bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
7027
7899
  },
7028
7900
  run() {
7901
+ const cliArgs = process.argv.slice(2);
7902
+ if (cliArgs[0] === "codex" && cliArgs.length > 1)
7903
+ return;
7029
7904
  showUsage(codex);
7030
7905
  }
7031
7906
  });
@@ -7039,6 +7914,9 @@ var cursor = defineCommand({
7039
7914
  bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
7040
7915
  },
7041
7916
  run() {
7917
+ const cliArgs = process.argv.slice(2);
7918
+ if (cliArgs[0] === "cursor" && cliArgs.length > 1)
7919
+ return;
7042
7920
  showUsage(cursor);
7043
7921
  }
7044
7922
  });
@@ -7052,9 +7930,38 @@ var copilot = defineCommand({
7052
7930
  bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default)
7053
7931
  },
7054
7932
  run() {
7933
+ const cliArgs = process.argv.slice(2);
7934
+ if (cliArgs[0] === "copilot" && cliArgs.length > 1)
7935
+ return;
7055
7936
  showUsage(copilot);
7056
7937
  }
7057
7938
  });
7939
+ var ui2 = defineCommand({
7940
+ meta: {
7941
+ name: "ui",
7942
+ description: "Launch the local doraval web dashboard (no more typing commands for common tasks)"
7943
+ },
7944
+ args: {
7945
+ port: {
7946
+ type: "string",
7947
+ description: "Port to run the local UI server on (default 3737)",
7948
+ default: "3737"
7949
+ },
7950
+ open: {
7951
+ type: "boolean",
7952
+ description: "Automatically open the dashboard in your browser",
7953
+ default: true
7954
+ },
7955
+ host: {
7956
+ type: "string",
7957
+ description: "Host to bind (default 127.0.0.1 for local only)",
7958
+ default: "127.0.0.1"
7959
+ }
7960
+ },
7961
+ async run({ args }) {
7962
+ await Promise.resolve().then(() => (init_ui(), exports_ui)).then((m) => m.default.run({ args }));
7963
+ }
7964
+ });
7058
7965
  var doraemonArt = `
7059
7966
  \u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E0\u28E4\u28F4\u28F6\u28F6\u28F6\u28F6\u28F6\u2836\u28F6\u28E4\u28E4\u28C0\u2800\u2800\u2800\u2800\u2800\u2800
7060
7967
  \u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28E4\u28FE\u28FF\u28FF\u28FF\u2801\u2800\u2880\u2808\u28BF\u2880\u28C0\u2800\u2839\u28FF\u28FF\u28FF\u28E6\u28C4\u2800\u2800\u2800
@@ -7079,17 +7986,24 @@ var main = defineCommand({
7079
7986
  bump: () => Promise.resolve().then(() => (init_bump(), exports_bump)).then((m) => m.default),
7080
7987
  update: () => Promise.resolve().then(() => (init_update3(), exports_update2)).then((m) => m.default),
7081
7988
  providers: () => Promise.resolve().then(() => (init_providers2(), exports_providers)).then((m) => m.default),
7989
+ completion: () => Promise.resolve().then(() => (init_completion(), exports_completion)).then((m) => m.default),
7082
7990
  skill: () => Promise.resolve(skill),
7083
7991
  journal: () => Promise.resolve(journal),
7084
7992
  claude: () => Promise.resolve(claude),
7085
7993
  codex: () => Promise.resolve(codex),
7086
7994
  cursor: () => Promise.resolve(cursor),
7087
- copilot: () => Promise.resolve(copilot)
7995
+ copilot: () => Promise.resolve(copilot),
7996
+ ui: () => Promise.resolve(ui2)
7088
7997
  },
7089
7998
  run() {
7090
- console.log(`
7091
- ` + import_picocolors18.default.blue(doraemonArt) + `
7999
+ const cliArgs = process.argv.slice(2);
8000
+ if (cliArgs.length > 0)
8001
+ return;
8002
+ if (process.stdout.isTTY) {
8003
+ console.error(`
8004
+ ` + import_picocolors20.default.blue(doraemonArt) + `
7092
8005
  `);
8006
+ }
7093
8007
  showUsage(main);
7094
8008
  }
7095
8009
  });