deweyou-cli 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-05-17
4
+
5
+ ### Added
6
+
7
+ - add deweyou-cli help and version flags
8
+ ## 0.4.0 - 2026-05-17
9
+
10
+ ### Added
11
+
12
+ - add interactive rule install prompts
13
+ - wire rule installs into init
14
+ - add rule install adapters
15
+ - parse rule install init flags
16
+
17
+ ### Fixed
18
+
19
+ - refuse agents symlink writes
20
+ - harden rule install safety
21
+ - preserve explicit init mode
22
+ - preserve init flags in wizard
23
+ - omit claude preview without rules
24
+ - include agents preview for claude installs
25
+ - read inline project rules from cache
26
+ - forward init rule install flags
27
+ - handle Claude rule install validation
28
+ - preserve only AGENTS Claude symlink
29
+ - document rule install init flags
30
+
31
+ ### Changed
32
+
33
+ - share managed markdown sections
34
+
35
+ ### Documentation
36
+
37
+ - document rule install targets
3
38
  ## 0.3.1 - 2026-05-17
4
39
 
5
40
  ### Changed
package/README.md CHANGED
@@ -37,6 +37,8 @@ cd /path/to/your/repo
37
37
  deweyou-cli agent init
38
38
  deweyou-cli agent doctor
39
39
  deweyou-cli agent context --format markdown
40
+ deweyou-cli agent -h
41
+ deweyou-cli -v
40
42
  ```
41
43
 
42
44
  For a non-interactive setup that selects everything:
@@ -59,6 +61,13 @@ skills and rules; a writing or design repo can select different ones.
59
61
 
60
62
  ## Commands
61
63
 
64
+ General options:
65
+
66
+ | Option | Meaning |
67
+ |--------|---------|
68
+ | `-h`, `--help` | Show help. Supports nested help such as `deweyou-cli agent -h` and `deweyou-cli agent init -h`. |
69
+ | `-v`, `--version` | Show the installed CLI version. |
70
+
62
71
  ### `deweyou-cli agent update`
63
72
 
64
73
  Refreshes the local Dewey asset cache from the default `deweyou/agents` source
@@ -91,6 +100,12 @@ Initializes the current repository with selected skills and rules.
91
100
  deweyou-cli agent init
92
101
  ```
93
102
 
103
+ Usage:
104
+
105
+ ```text
106
+ deweyou-cli agent init [--all] [--skills a,b] [--rules a,b] [--mode link|copy|pointer] [--scope project|global] [--tools codex,claude|all] [--rule-wiring reference|inline] [--yes] [--dry-run] [--force]
107
+ ```
108
+
94
109
  Without selection flags, this opens an interactive setup where you choose:
95
110
 
96
111
  - install mode
@@ -102,6 +117,8 @@ Scripted examples:
102
117
  ```bash
103
118
  deweyou-cli agent init --all --mode link --yes
104
119
  deweyou-cli agent init --skills repo-memory,spec-driven-coding,git-delivery --rules code-style
120
+ deweyou-cli agent init --scope project --tools codex,claude --rules code-style --mode link
121
+ deweyou-cli agent init --scope global --tools all --rules code-style --rule-wiring reference --yes
105
122
  deweyou-cli agent init --dry-run
106
123
  ```
107
124
 
@@ -182,6 +199,10 @@ AGENTS.md
182
199
  `AGENTS.md` receives a managed Dewey section that points agents at the selected
183
200
  workflow context. Existing content outside that managed section is preserved.
184
201
 
202
+ Project installs write repository instruction files such as `AGENTS.md` and
203
+ `CLAUDE.md`. Global installs write user-level instruction files such as
204
+ `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md`.
205
+
185
206
  ## Safety Notes
186
207
 
187
208
  - Run `deweyou-cli agent update` before `deweyou-cli agent init`.
package/dist/deweyou.mjs CHANGED
@@ -11,7 +11,10 @@ const VALUE_FLAGS = new Set([
11
11
  "mode",
12
12
  "skills",
13
13
  "rules",
14
- "format"
14
+ "format",
15
+ "scope",
16
+ "tools",
17
+ "rule-wiring"
15
18
  ]);
16
19
  const FLAGS_BY_COMMAND = {
17
20
  init: new Set([
@@ -19,6 +22,9 @@ const FLAGS_BY_COMMAND = {
19
22
  "skills",
20
23
  "rules",
21
24
  "mode",
25
+ "scope",
26
+ "tools",
27
+ "rule-wiring",
22
28
  "yes",
23
29
  "dry-run",
24
30
  "force"
@@ -69,19 +75,37 @@ function isAllowedForCommand(command, name) {
69
75
  return FLAGS_BY_COMMAND[command]?.has(name) ?? false;
70
76
  }
71
77
  function parseValue(name, value) {
72
- if (name === "skills" || name === "rules") return value.split(",").map((item) => item.trim()).filter(Boolean);
78
+ if (name === "skills" || name === "rules" || name === "tools") return value.split(",").map((item) => item.trim()).filter(Boolean);
73
79
  return value;
74
80
  }
75
81
  function toCamel(value) {
76
82
  return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
77
83
  }
78
84
  //#endregion
85
+ //#region package.json
86
+ var version = "0.5.0";
87
+ //#endregion
79
88
  //#region src/cli/main.ts
89
+ const COMMANDS = [
90
+ "init",
91
+ "update",
92
+ "context",
93
+ "doctor"
94
+ ];
80
95
  async function main(argv) {
96
+ const help = helpFor(argv);
97
+ if (help) {
98
+ console.log(help);
99
+ return;
100
+ }
101
+ if (isVersionRequest(argv)) {
102
+ console.log(version);
103
+ return;
104
+ }
81
105
  const parsed = parseArgs(argv);
82
106
  if (parsed.topic !== "agent") printUsageAndThrow();
83
107
  if (parsed.command === "init") {
84
- const { runInit } = await import("./init-g2D-URoL.mjs");
108
+ const { runInit } = await import("./init-6406tZjR.mjs");
85
109
  await runInit(parsed.flags);
86
110
  return;
87
111
  }
@@ -103,16 +127,87 @@ async function main(argv) {
103
127
  printUsageAndThrow();
104
128
  }
105
129
  function usage() {
130
+ return rootUsage();
131
+ }
132
+ function rootUsage() {
133
+ return `Usage:
134
+ deweyou-cli agent <command> [options]
135
+
136
+ Commands:
137
+ agent init Initialize the current repository with Dewey assets.
138
+ agent update Refresh the local Dewey asset cache.
139
+ agent context Print the active Dewey agent context.
140
+ agent doctor Check whether the repository and cache are healthy.
141
+
142
+ Options:
143
+ -h, --help Show help.
144
+ -v, --version Show the CLI version.`;
145
+ }
146
+ function agentUsage() {
106
147
  return `Usage:
107
- deweyou-cli agent init [--all] [--skills a,b] [--rules a,b] [--mode link|copy|pointer] [--yes] [--dry-run] [--force]
148
+ deweyou-cli agent init [--all] [--skills a,b] [--rules a,b] [--mode link|copy|pointer] [--scope project|global] [--tools codex,claude|all] [--rule-wiring reference|inline] [--yes] [--dry-run] [--force]
108
149
  deweyou-cli agent update
109
150
  deweyou-cli agent context [--format markdown|json]
110
- deweyou-cli agent doctor`;
151
+ deweyou-cli agent doctor
152
+
153
+ Run \`deweyou-cli agent <command> -h\` for command-specific help.`;
154
+ }
155
+ function commandUsage(command) {
156
+ if (command === "init") return `Usage:
157
+ deweyou-cli agent init [--all] [--skills a,b] [--rules a,b] [--mode link|copy|pointer] [--scope project|global] [--tools codex,claude|all] [--rule-wiring reference|inline] [--yes] [--dry-run] [--force]
158
+
159
+ Options:
160
+ --all Select every skill and rule.
161
+ --skills a,b Select comma-separated skill ids.
162
+ --rules a,b Select comma-separated rule ids.
163
+ --mode link|copy|pointer Choose how assets are referenced.
164
+ --scope project|global Write project or user-level instructions.
165
+ --tools codex,claude|all Select target agent tools.
166
+ --rule-wiring reference|inline
167
+ Choose how rules are written into instructions.
168
+ --yes Run without prompts for scripted selections.
169
+ --dry-run Print the plan without writing files.
170
+ --force Replace existing Dewey-managed destinations.
171
+ -h, --help Show help.`;
172
+ if (command === "context") return `Usage:
173
+ deweyou-cli agent context [--format markdown|json]
174
+
175
+ Options:
176
+ --format markdown|json Choose human-readable or structured output.
177
+ -h, --help Show help.`;
178
+ if (command === "update") return `Usage:
179
+ deweyou-cli agent update
180
+
181
+ Options:
182
+ -h, --help Show help.`;
183
+ return `Usage:
184
+ deweyou-cli agent doctor
185
+
186
+ Options:
187
+ -h, --help Show help.`;
111
188
  }
112
189
  function printUsageAndThrow() {
113
190
  console.log(usage());
114
191
  throw usageError("", { silent: true });
115
192
  }
193
+ function helpFor(argv) {
194
+ if (!argv.some(isHelpFlag)) return null;
195
+ if (isHelpFlag(argv[0])) return rootUsage();
196
+ if (argv[0] !== "agent") return rootUsage();
197
+ const command = argv[1];
198
+ if (!command || isHelpFlag(command)) return agentUsage();
199
+ if (isAgentCommand(command)) return commandUsage(command);
200
+ return agentUsage();
201
+ }
202
+ function isVersionRequest(argv) {
203
+ return argv.length === 1 && (argv[0] === "-v" || argv[0] === "--version");
204
+ }
205
+ function isHelpFlag(value) {
206
+ return value === "-h" || value === "--help";
207
+ }
208
+ function isAgentCommand(value) {
209
+ return COMMANDS.includes(value);
210
+ }
116
211
  //#endregion
117
212
  //#region src/bin/deweyou.ts
118
213
  main(process.argv.slice(2)).catch((error) => {
@@ -1,18 +1,37 @@
1
1
  import { cachePaths, n as writeJson, t as readJson } from "./cache-rbXm6Wyh.mjs";
2
- import { cp, lstat, mkdir, readFile, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
2
+ import { cp, lstat, mkdir, readFile, readlink, realpath, rename, rm, symlink, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { basename, dirname, isAbsolute, join, relative } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
5
+ //#region src/cli/managed-section.ts
6
+ function upsertManagedSection(contents, { start, end, body }) {
7
+ const section = `${start}\n${body.trimEnd()}\n${end}`;
8
+ const markedSection = new RegExp(`${escapeRegex(start)}[\\s\\S]*?${escapeRegex(end)}`);
9
+ if (markedSection.test(contents)) return ensureTrailingNewline(contents.replace(markedSection, section));
10
+ const trimmed = contents.trimEnd();
11
+ if (!trimmed) return `${section}\n`;
12
+ return `${trimmed}\n\n${section}\n`;
13
+ }
14
+ function escapeRegex(value) {
15
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16
+ }
17
+ function ensureTrailingNewline(value) {
18
+ return value.endsWith("\n") ? value : `${value}\n`;
19
+ }
20
+ //#endregion
5
21
  //#region src/cli/agents-md.ts
6
22
  const DEWEYOU_SECTION_START = "<!-- deweyou-agent:start -->";
7
23
  const DEWEYOU_SECTION_END = "<!-- deweyou-agent:end -->";
8
- const DEWEY_SECTION = `${DEWEYOU_SECTION_START}
9
- ## Dewey Workflow
24
+ const DEWEY_SECTION_BODY = `## Dewey Workflow
10
25
 
11
- This repository uses Dewey's personal agent workflow. Inspect \`.agents/\` before making changes, then run \`deweyou-cli agent context --format markdown\` and follow the returned rules, skill index, asset paths, and runtime notices.
12
- ${DEWEYOU_SECTION_END}`;
26
+ This repository uses Dewey's personal agent workflow. Inspect \`.agents/\` before making changes, then run \`deweyou-cli agent context --format markdown\` and follow the returned rules, skill index, asset paths, and runtime notices.`;
13
27
  async function upsertAgentsSection(repoRoot) {
14
28
  const path = join(repoRoot, "AGENTS.md");
15
- const next = upsertSection(await readAgentsFile(path));
29
+ await validateAgentsWritePath(path);
30
+ const next = upsertManagedSection(await readAgentsFile(path), {
31
+ start: DEWEYOU_SECTION_START,
32
+ end: DEWEYOU_SECTION_END,
33
+ body: DEWEY_SECTION_BODY
34
+ });
16
35
  await writeFile(path, next);
17
36
  return next;
18
37
  }
@@ -25,18 +44,160 @@ async function readAgentsFile(path) {
25
44
  throw error;
26
45
  }
27
46
  }
28
- function upsertSection(contents) {
29
- const markedSection = new RegExp(`${escapeRegex(DEWEYOU_SECTION_START)}[\\s\\S]*?${escapeRegex(DEWEYOU_SECTION_END)}`);
30
- if (markedSection.test(contents)) return ensureTrailingNewline(contents.replace(markedSection, DEWEY_SECTION));
31
- const trimmed = contents.trimEnd();
32
- if (!trimmed) return `${DEWEY_SECTION}\n`;
33
- return `${trimmed}\n\n${DEWEY_SECTION}\n`;
47
+ async function validateAgentsWritePath(path) {
48
+ try {
49
+ if ((await lstat(path)).isSymbolicLink()) throw new Error(`Refusing to write Dewey workflow through symlink: ${path}`);
50
+ } catch (error) {
51
+ if (!(error instanceof Error) || !("code" in error)) throw error;
52
+ if (error.code === "ENOENT") return;
53
+ throw error;
54
+ }
34
55
  }
35
- function escapeRegex(value) {
36
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
56
+ //#endregion
57
+ //#region src/cli/rule-install.ts
58
+ const CODEX_START = "<!-- deweyou-codex-rules:start -->";
59
+ const CODEX_END = "<!-- deweyou-codex-rules:end -->";
60
+ const CLAUDE_START = "<!-- deweyou-claude-rules:start -->";
61
+ const CLAUDE_END = "<!-- deweyou-claude-rules:end -->";
62
+ async function planRuleInstall(input) {
63
+ validateRuleInstallInput(input);
64
+ if (input.selectedRules.length === 0) return {
65
+ files: [],
66
+ operations: []
67
+ };
68
+ const operations = [];
69
+ if (input.tools.includes("codex")) operations.push(await codexOperation(input));
70
+ if (input.tools.includes("claude")) {
71
+ const operation = await claudeOperation(input);
72
+ if (operation) operations.push(operation);
73
+ }
74
+ return {
75
+ files: operations.map((operation) => operation.path),
76
+ operations
77
+ };
37
78
  }
38
- function ensureTrailingNewline(value) {
39
- return value.endsWith("\n") ? value : `${value}\n`;
79
+ async function applyRuleInstall(plan) {
80
+ for (const operation of plan.operations) {
81
+ await mkdir(dirname(operation.path), { recursive: true });
82
+ await validateInstructionWritePath(operation.path);
83
+ const next = upsertManagedSection(await readTextIfPresent(operation.path), operation);
84
+ await writeFile(operation.path, next);
85
+ }
86
+ }
87
+ async function codexOperation(input) {
88
+ return {
89
+ path: input.scope === "project" ? join(input.repoRoot, "AGENTS.md") : join(input.homeDir, ".codex", "AGENTS.md"),
90
+ start: CODEX_START,
91
+ end: CODEX_END,
92
+ body: await renderRuleSection(input, "Codex")
93
+ };
94
+ }
95
+ async function claudeOperation(input) {
96
+ if (input.scope === "project") {
97
+ const claudePath = join(input.repoRoot, "CLAUDE.md");
98
+ if (await isSymlinkToAgentsMd(claudePath, input.repoRoot)) {
99
+ if (input.tools.includes("codex")) return null;
100
+ return {
101
+ path: join(input.repoRoot, "AGENTS.md"),
102
+ start: CLAUDE_START,
103
+ end: CLAUDE_END,
104
+ body: await renderRuleSection(input, "Claude Code")
105
+ };
106
+ }
107
+ if (input.tools.includes("codex") && !await exists(claudePath)) return {
108
+ path: claudePath,
109
+ start: CLAUDE_START,
110
+ end: CLAUDE_END,
111
+ body: "@AGENTS.md"
112
+ };
113
+ return {
114
+ path: claudePath,
115
+ start: CLAUDE_START,
116
+ end: CLAUDE_END,
117
+ body: await renderRuleSection(input, "Claude Code")
118
+ };
119
+ }
120
+ return {
121
+ path: join(input.homeDir, ".claude", "CLAUDE.md"),
122
+ start: CLAUDE_START,
123
+ end: CLAUDE_END,
124
+ body: await renderRuleSection(input, "Claude Code")
125
+ };
126
+ }
127
+ function validateRuleInstallInput(input) {
128
+ if (input.scope !== "project" && input.scope !== "global") throw new Error(`Invalid rule install scope: ${input.scope}`);
129
+ for (const tool of input.tools) if (tool !== "codex" && tool !== "claude") throw new Error(`Invalid rule install tool: ${tool}`);
130
+ if (input.ruleWiring !== "reference" && input.ruleWiring !== "inline") throw new Error(`Invalid rule wiring: ${input.ruleWiring}`);
131
+ if (input.scope === "global") for (const rule of input.selectedRules) {
132
+ const path = requireRulePath(input, rule);
133
+ if (!isPathInside(input.cacheRoot, path)) throw new Error(`Global Dewey rule path must be inside cacheRoot for ${rule}: ${path}`);
134
+ }
135
+ }
136
+ async function renderRuleSection(input, toolLabel) {
137
+ if (input.ruleWiring === "inline") return `## Dewey Rules for ${toolLabel}
138
+
139
+ Follow these selected Dewey rules:
140
+
141
+ ${(await Promise.all(input.selectedRules.map(async (rule) => {
142
+ return `### ${rule}\n\n${await readFile(requireRulePath(input, rule), "utf8")}`;
143
+ }))).join("\n\n")}`;
144
+ return `## Dewey Rules for ${toolLabel}
145
+
146
+ Follow these selected Dewey rules. Read the referenced files before applying a rule:
147
+
148
+ ${input.selectedRules.map((rule) => `- ${rule}: ${displayPath(input, requireRulePath(input, rule))}`).join("\n")}`;
149
+ }
150
+ function requireRulePath(input, rule) {
151
+ const path = input.rulePaths.get(rule);
152
+ if (!path) throw new Error(`Missing path for Dewey rule: ${rule}`);
153
+ return path;
154
+ }
155
+ function displayPath(input, path) {
156
+ if (input.scope === "project") return relative(input.repoRoot, path);
157
+ return path;
158
+ }
159
+ function isPathInside(root, path) {
160
+ const pathFromRoot = relative(resolve(root), resolve(path));
161
+ return pathFromRoot === "" || !pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot);
162
+ }
163
+ async function readTextIfPresent(path) {
164
+ try {
165
+ return await readFile(path, "utf8");
166
+ } catch (error) {
167
+ if (!(error instanceof Error) || !("code" in error)) throw error;
168
+ if (error.code === "ENOENT") return "";
169
+ throw error;
170
+ }
171
+ }
172
+ async function validateInstructionWritePath(path) {
173
+ try {
174
+ if ((await lstat(path)).isSymbolicLink()) throw new Error(`Refusing to write Dewey rules through symlink: ${path}`);
175
+ } catch (error) {
176
+ if (!(error instanceof Error) || !("code" in error)) throw error;
177
+ if (error.code === "ENOENT") return;
178
+ throw error;
179
+ }
180
+ }
181
+ async function exists(path) {
182
+ try {
183
+ await lstat(path);
184
+ return true;
185
+ } catch (error) {
186
+ if (!(error instanceof Error) || !("code" in error)) throw error;
187
+ if (error.code === "ENOENT") return false;
188
+ throw error;
189
+ }
190
+ }
191
+ async function isSymlinkToAgentsMd(path, repoRoot) {
192
+ try {
193
+ if (!(await lstat(path)).isSymbolicLink()) return false;
194
+ const target = await readlink(path);
195
+ return target === "AGENTS.md" || resolve(dirname(path), target) === resolve(repoRoot, "AGENTS.md");
196
+ } catch (error) {
197
+ if (!(error instanceof Error) || !("code" in error)) throw error;
198
+ if (error.code === "ENOENT") return false;
199
+ throw error;
200
+ }
40
201
  }
41
202
  //#endregion
42
203
  //#region src/cli/init.ts
@@ -45,9 +206,23 @@ const VALID_MODES = new Set([
45
206
  "copy",
46
207
  "pointer"
47
208
  ]);
209
+ const VALID_SCOPES = new Set(["project", "global"]);
210
+ const VALID_TOOLS = new Set(["codex", "claude"]);
211
+ const VALID_TOOL_SELECTIONS = new Set([
212
+ "all",
213
+ "codex",
214
+ "claude"
215
+ ]);
216
+ const VALID_RULE_WIRING = new Set(["reference", "inline"]);
48
217
  const SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
49
- async function initRepo({ repoRoot = process.cwd(), homeDir = homedir(), mode = "link", selected, force = false, dryRun = false } = {}) {
218
+ async function initRepo(options = {}) {
219
+ const { repoRoot = process.cwd(), homeDir = homedir(), mode = "link", selected, force = false, dryRun = false } = options;
50
220
  if (!selected) throw new Error("selected assets are required");
221
+ const scope = options.scope ?? "project";
222
+ const ruleWiring = options.ruleWiring ?? "reference";
223
+ validateScope(scope);
224
+ validateRuleWiring(ruleWiring);
225
+ const tools = normalizeTools(options.tools);
51
226
  validateMode(mode);
52
227
  const paths = cachePaths({ homeDir });
53
228
  const registry = await readCachedRegistry(paths.assetsRoot);
@@ -55,6 +230,17 @@ async function initRepo({ repoRoot = process.cwd(), homeDir = homedir(), mode =
55
230
  const assets = normalizeSelected(selected);
56
231
  const agentsRoot = join(repoRoot, ".agents");
57
232
  validateSelectedAssets(registry, assets);
233
+ if (scope === "global") return await initGlobal({
234
+ homeDir,
235
+ repoRoot,
236
+ paths,
237
+ registry,
238
+ cacheManifest,
239
+ assets,
240
+ tools,
241
+ ruleWiring,
242
+ dryRun
243
+ });
58
244
  const plan = await buildInitPlan({
59
245
  repoRoot,
60
246
  agentsRoot,
@@ -75,21 +261,62 @@ async function initRepo({ repoRoot = process.cwd(), homeDir = homedir(), mode =
75
261
  cacheRoot: paths.assetsRoot,
76
262
  assets,
77
263
  assetSnapshot: snapshotSelectedAssetMetadata(registry, assets),
264
+ scope,
265
+ tools,
266
+ ruleWiring,
78
267
  initializedAt: (/* @__PURE__ */ new Date()).toISOString()
79
268
  };
269
+ const rulePlan = await planRuleInstall({
270
+ repoRoot,
271
+ homeDir,
272
+ cacheRoot: paths.assetsRoot,
273
+ scope: "project",
274
+ tools,
275
+ ruleWiring,
276
+ selectedRules: assets.rules,
277
+ rulePaths: projectRulePathMap(repoRoot, paths.assetsRoot, registry, assets, mode, ruleWiring)
278
+ });
80
279
  if (dryRun) return {
280
+ ...manifest,
81
281
  dryRun: true,
82
- mode,
83
- source: cacheManifest.source,
84
- cacheRoot: paths.assetsRoot,
85
- assets,
86
- assetSnapshot: snapshotSelectedAssetMetadata(registry, assets),
87
- files: plan.files
282
+ files: uniqueFiles([...plan.files, ...rulePlan.files])
88
283
  };
89
284
  await mkdir(agentsRoot, { recursive: true });
90
285
  if (mode !== "pointer") await installAssets(plan.assets);
91
286
  await writeJson(join(agentsRoot, "manifest.json"), manifest);
92
287
  await upsertAgentsSection(repoRoot);
288
+ await applyRuleInstall(rulePlan);
289
+ return manifest;
290
+ }
291
+ async function initGlobal({ homeDir, repoRoot, paths, registry, cacheManifest, assets, tools, ruleWiring, dryRun }) {
292
+ if (assets.skills.length > 0) throw new Error("Global installs currently support rules only");
293
+ const rulePlan = await planRuleInstall({
294
+ repoRoot,
295
+ homeDir,
296
+ cacheRoot: paths.assetsRoot,
297
+ scope: "global",
298
+ tools,
299
+ ruleWiring,
300
+ selectedRules: assets.rules,
301
+ rulePaths: rulePathMap(paths.assetsRoot, registry, assets.rules)
302
+ });
303
+ const manifest = {
304
+ scope: "global",
305
+ source: cacheManifest.source,
306
+ cacheRoot: paths.assetsRoot,
307
+ assets,
308
+ assetSnapshot: snapshotSelectedAssetMetadata(registry, assets),
309
+ tools,
310
+ ruleWiring,
311
+ initializedAt: (/* @__PURE__ */ new Date()).toISOString()
312
+ };
313
+ if (dryRun) return {
314
+ ...manifest,
315
+ dryRun: true,
316
+ files: uniqueFiles([...rulePlan.files, join(homeDir, ".deweyou/agents/global-manifest.json")])
317
+ };
318
+ await applyRuleInstall(rulePlan);
319
+ await writeJson(join(homeDir, ".deweyou/agents/global-manifest.json"), manifest);
93
320
  return manifest;
94
321
  }
95
322
  async function runInit(flags = {}, { promptForInit } = {}) {
@@ -98,6 +325,9 @@ async function runInit(flags = {}, { promptForInit } = {}) {
98
325
  const registry = await readCachedRegistry(cachePaths({ homeDir }).assetsRoot);
99
326
  const scripted = hasScriptedSelectionFlags(flags);
100
327
  let mode = flags.mode ?? "link";
328
+ let scope = flags.scope;
329
+ let tools = flags.tools;
330
+ let ruleWiring = flags.ruleWiring;
101
331
  let selected;
102
332
  validateMode(mode);
103
333
  if (flags.yes && !scripted) throw new Error("--yes requires --all, --skills, or --rules");
@@ -109,15 +339,25 @@ async function runInit(flags = {}, { promptForInit } = {}) {
109
339
  const prompted = await (promptForInit ?? await loadPromptForInit())({
110
340
  registry,
111
341
  repoRoot,
112
- mode: flags.mode
342
+ mode: flags.mode,
343
+ scope: flags.scope,
344
+ tools: flags.tools,
345
+ ruleWiring: flags.ruleWiring
113
346
  });
114
- mode = prompted.mode;
347
+ mode = flags.mode ?? prompted.mode;
348
+ scope = flags.scope ?? prompted.scope;
349
+ tools = flags.tools ?? prompted.tools;
350
+ ruleWiring = flags.ruleWiring ?? prompted.ruleWiring;
115
351
  selected = prompted.selected;
116
352
  }
117
353
  if (!hasSelectedAssets(selected)) throw new Error("No assets selected. Pass --all, --skills, or --rules, or run interactive setup.");
354
+ if (scripted && scope === "global" && !flags.yes && !flags.dryRun) throw new Error("--scope global with scripted selections requires --yes or --dry-run");
118
355
  const manifest = await initRepo({
119
356
  repoRoot,
120
357
  mode,
358
+ scope,
359
+ tools,
360
+ ruleWiring,
121
361
  selected,
122
362
  force: flags.force ?? false,
123
363
  dryRun: flags.dryRun ?? false,
@@ -130,7 +370,7 @@ async function runInit(flags = {}, { promptForInit } = {}) {
130
370
  return manifest;
131
371
  }
132
372
  async function loadPromptForInit() {
133
- const { promptForInit } = await import("./prompts-DVRcV560.mjs");
373
+ const { promptForInit } = await import("./prompts-DdaeRkBa.mjs");
134
374
  return promptForInit;
135
375
  }
136
376
  async function readCachedRegistry(assetsRoot) {
@@ -175,6 +415,22 @@ function validateMode(mode) {
175
415
  if (typeof mode !== "string") throw new Error("mode must be one of link, copy, or pointer");
176
416
  if (!VALID_MODES.has(mode)) throw new Error("mode must be one of link, copy, or pointer");
177
417
  }
418
+ function validateScope(scope) {
419
+ if (typeof scope !== "string") throw new Error("scope must be one of project or global");
420
+ if (!VALID_SCOPES.has(scope)) throw new Error("scope must be one of project or global");
421
+ }
422
+ function normalizeTools(tools) {
423
+ if (tools) {
424
+ for (const tool of tools) if (typeof tool !== "string" || !VALID_TOOL_SELECTIONS.has(tool)) throw new Error(`tool must be one of codex or claude: ${tool}`);
425
+ }
426
+ const selected = !tools || tools.includes("all") ? ["codex", "claude"] : [...tools];
427
+ for (const tool of selected) if (typeof tool !== "string" || !VALID_TOOLS.has(tool)) throw new Error(`tool must be one of codex or claude: ${tool}`);
428
+ return [...new Set(selected)];
429
+ }
430
+ function validateRuleWiring(ruleWiring) {
431
+ if (typeof ruleWiring !== "string") throw new Error("ruleWiring must be one of reference or inline");
432
+ if (!VALID_RULE_WIRING.has(ruleWiring)) throw new Error("ruleWiring must be one of reference or inline");
433
+ }
178
434
  function validateSelectedAssets(registry, selected) {
179
435
  for (const skill of selected.skills) {
180
436
  if (!registry.assets.skills[skill]) throw new Error(`Unknown Dewey skill: ${skill}`);
@@ -206,6 +462,16 @@ function pickAssetMetadata(asset) {
206
462
  hash: asset.hash
207
463
  };
208
464
  }
465
+ function uniqueFiles(files) {
466
+ return [...new Set(files)];
467
+ }
468
+ function rulePathMap(assetsRoot, registry, rules) {
469
+ return new Map(rules.map((rule) => [rule, join(assetsRoot, registry.assets.rules[rule].path)]));
470
+ }
471
+ function projectRulePathMap(repoRoot, assetsRoot, registry, assets, mode, ruleWiring) {
472
+ if (mode === "pointer" || ruleWiring === "inline") return rulePathMap(assetsRoot, registry, assets.rules);
473
+ return new Map(assets.rules.map((rule) => [rule, join(repoRoot, ".agents", "rules", `${rule}.md`)]));
474
+ }
209
475
  async function buildInitPlan({ repoRoot, agentsRoot, assetsRoot, registry, assets, mode }) {
210
476
  const assetPlans = mode === "pointer" ? [] : await Promise.all([...assets.skills.map((id) => buildAssetPlan({
211
477
  kind: "skill",
@@ -0,0 +1,189 @@
1
+ import { cancel, confirm, intro, isCancel, multiselect, note, select } from "@clack/prompts";
2
+ //#region src/cli/prompts.ts
3
+ const SETUP_SCOPES = [{
4
+ value: "project",
5
+ label: "project",
6
+ hint: "Install into this repository."
7
+ }, {
8
+ value: "global",
9
+ label: "global",
10
+ hint: "Install into Codex and Claude user homes."
11
+ }];
12
+ const TOOL_OPTIONS = [
13
+ {
14
+ value: "both",
15
+ label: "both",
16
+ hint: "Wire Codex and Claude Code."
17
+ },
18
+ {
19
+ value: "codex",
20
+ label: "codex",
21
+ hint: "Wire AGENTS.md only."
22
+ },
23
+ {
24
+ value: "claude",
25
+ label: "claude",
26
+ hint: "Wire CLAUDE.md only."
27
+ }
28
+ ];
29
+ const SETUP_MODES = [
30
+ {
31
+ value: "link",
32
+ label: "link",
33
+ hint: "Symlink assets from the Dewey cache."
34
+ },
35
+ {
36
+ value: "copy",
37
+ label: "copy",
38
+ hint: "Copy asset files into this repository."
39
+ },
40
+ {
41
+ value: "pointer",
42
+ label: "pointer",
43
+ hint: "Write only the manifest and AGENTS.md pointers."
44
+ }
45
+ ];
46
+ const RULE_WIRING_OPTIONS = [{
47
+ value: "reference",
48
+ label: "reference",
49
+ hint: "Reference selected rule files."
50
+ }, {
51
+ value: "inline",
52
+ label: "inline",
53
+ hint: "Inline selected rule bodies."
54
+ }];
55
+ const ASSET_SCOPES = [
56
+ {
57
+ value: "all",
58
+ label: "all",
59
+ hint: "Enable every cached skill and rule."
60
+ },
61
+ {
62
+ value: "custom",
63
+ label: "custom",
64
+ hint: "Choose skills and rules individually."
65
+ },
66
+ {
67
+ value: "skills",
68
+ label: "skills only",
69
+ hint: "Choose skills without installing rules."
70
+ },
71
+ {
72
+ value: "rules",
73
+ label: "rules only",
74
+ hint: "Choose rules without installing skills."
75
+ }
76
+ ];
77
+ const GLOBAL_ASSET_SCOPES = [{
78
+ value: "all",
79
+ label: "all rules",
80
+ hint: "Enable every cached rule."
81
+ }, {
82
+ value: "rules",
83
+ label: "choose rules",
84
+ hint: "Choose rules individually."
85
+ }];
86
+ async function promptForInit({ registry, repoRoot, mode, scope, tools, ruleWiring }) {
87
+ intro("Dewey Agent Setup");
88
+ note(repoRoot, "Repository");
89
+ const selectedScope = scope ?? await promptOrExit(select({
90
+ message: "Select install scope",
91
+ options: SETUP_SCOPES
92
+ }));
93
+ const selectedTools = tools === void 0 ? normalizePromptTools(await promptOrExit(select({
94
+ message: "Select tools",
95
+ options: TOOL_OPTIONS
96
+ }))) : normalizeToolSelection(tools);
97
+ const selectedMode = selectedScope === "global" ? "pointer" : mode ?? await promptOrExit(select({
98
+ message: "Select setup mode",
99
+ options: SETUP_MODES
100
+ }));
101
+ const selected = await selectAssets({
102
+ registry,
103
+ scope: await promptOrExit(select({
104
+ message: "Select asset scope",
105
+ options: selectedScope === "global" ? GLOBAL_ASSET_SCOPES : ASSET_SCOPES
106
+ })),
107
+ installScope: selectedScope
108
+ });
109
+ const selectedRuleWiring = ruleWiring ?? (selected.rules.length > 0 ? await promptOrExit(select({
110
+ message: "Select rule wiring",
111
+ options: RULE_WIRING_OPTIONS
112
+ })) : "reference");
113
+ note(plannedFiles({
114
+ repoRoot,
115
+ scope: selectedScope,
116
+ tools: selectedTools,
117
+ selected
118
+ }), "Dewey will update");
119
+ if (!await promptOrExit(confirm({ message: `Enable ${selected.skills.length} skill(s) and ${selected.rules.length} rule(s) using ${selectedMode} mode?` }))) exitCancelled();
120
+ return {
121
+ mode: selectedMode,
122
+ scope: selectedScope,
123
+ tools: selectedTools,
124
+ ruleWiring: selectedRuleWiring,
125
+ selected
126
+ };
127
+ }
128
+ async function selectAssets({ registry, scope, installScope }) {
129
+ if (scope === "all") return {
130
+ skills: installScope === "global" ? [] : Object.keys(registry.assets.skills),
131
+ rules: Object.keys(registry.assets.rules)
132
+ };
133
+ const selected = {
134
+ skills: [],
135
+ rules: []
136
+ };
137
+ if (installScope === "project" && (scope === "custom" || scope === "skills")) selected.skills = await promptOrExit(multiselect({
138
+ message: "Select skills",
139
+ options: assetOptions(registry.assets.skills),
140
+ required: false
141
+ }));
142
+ if (scope === "custom" || scope === "rules") selected.rules = await promptOrExit(multiselect({
143
+ message: "Select rules",
144
+ options: assetOptions(registry.assets.rules),
145
+ required: false
146
+ }));
147
+ return selected;
148
+ }
149
+ function assetOptions(assets) {
150
+ return Object.entries(assets).map(([name, asset]) => ({
151
+ value: name,
152
+ label: name,
153
+ hint: asset.description
154
+ }));
155
+ }
156
+ function normalizePromptTools(selectedTools) {
157
+ if (selectedTools === "both") return ["codex", "claude"];
158
+ return [selectedTools];
159
+ }
160
+ function normalizeToolSelection(tools) {
161
+ if (tools.includes("all")) return ["codex", "claude"];
162
+ return [...new Set(tools)];
163
+ }
164
+ function plannedFiles({ repoRoot, scope, tools, selected }) {
165
+ const files = [];
166
+ if (scope === "global") {
167
+ if (tools.includes("codex")) files.push("~/.codex/AGENTS.md");
168
+ if (tools.includes("claude")) files.push("~/.claude/CLAUDE.md");
169
+ files.push("~/.deweyou/agents/global-manifest.json");
170
+ return files.join("\n");
171
+ }
172
+ files.push("AGENTS.md");
173
+ if (tools.includes("claude") && selected.rules.length > 0) files.push("CLAUDE.md");
174
+ files.push(".agents/manifest.json");
175
+ if (selected.skills.length > 0) files.push(".agents/skills/<skill>/SKILL.md");
176
+ if (selected.rules.length > 0) files.push(".agents/rules/<rule>.md");
177
+ return `${repoRoot}\n\n${files.join("\n")}`;
178
+ }
179
+ async function promptOrExit(prompt) {
180
+ const value = await prompt;
181
+ if (isCancel(value)) exitCancelled();
182
+ return value;
183
+ }
184
+ function exitCancelled() {
185
+ cancel("Dewey agent setup cancelled.");
186
+ process.exit(0);
187
+ }
188
+ //#endregion
189
+ export { promptForInit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deweyou-cli",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Dewey's personal agent workflow bootstrapper",
6
6
  "files": [
@@ -1,100 +0,0 @@
1
- import { cancel, confirm, intro, isCancel, multiselect, note, select } from "@clack/prompts";
2
- //#region src/cli/prompts.ts
3
- const SETUP_MODES = [
4
- {
5
- value: "link",
6
- label: "link",
7
- hint: "Symlink assets from the Dewey cache."
8
- },
9
- {
10
- value: "copy",
11
- label: "copy",
12
- hint: "Copy asset files into this repository."
13
- },
14
- {
15
- value: "pointer",
16
- label: "pointer",
17
- hint: "Write only the manifest and AGENTS.md pointers."
18
- }
19
- ];
20
- const ASSET_SCOPES = [
21
- {
22
- value: "all",
23
- label: "all",
24
- hint: "Enable every cached skill and rule."
25
- },
26
- {
27
- value: "custom",
28
- label: "custom",
29
- hint: "Choose skills and rules individually."
30
- },
31
- {
32
- value: "skills",
33
- label: "skills only",
34
- hint: "Choose skills without installing rules."
35
- },
36
- {
37
- value: "rules",
38
- label: "rules only",
39
- hint: "Choose rules without installing skills."
40
- }
41
- ];
42
- async function promptForInit({ registry, repoRoot, mode }) {
43
- intro("Dewey Agent Setup");
44
- note(repoRoot, "Repository");
45
- const selectedMode = mode ?? await promptOrExit(select({
46
- message: "Select setup mode",
47
- options: SETUP_MODES
48
- }));
49
- const selected = await selectAssets({
50
- registry,
51
- scope: await promptOrExit(select({
52
- message: "Select asset scope",
53
- options: ASSET_SCOPES
54
- }))
55
- });
56
- if (!await promptOrExit(confirm({ message: `Enable ${selected.skills.length} skill(s) and ${selected.rules.length} rule(s) using ${selectedMode} mode?` }))) exitCancelled();
57
- return {
58
- mode: selectedMode,
59
- selected
60
- };
61
- }
62
- async function selectAssets({ registry, scope }) {
63
- if (scope === "all") return {
64
- skills: Object.keys(registry.assets.skills),
65
- rules: Object.keys(registry.assets.rules)
66
- };
67
- const selected = {
68
- skills: [],
69
- rules: []
70
- };
71
- if (scope === "custom" || scope === "skills") selected.skills = await promptOrExit(multiselect({
72
- message: "Select skills",
73
- options: assetOptions(registry.assets.skills),
74
- required: false
75
- }));
76
- if (scope === "custom" || scope === "rules") selected.rules = await promptOrExit(multiselect({
77
- message: "Select rules",
78
- options: assetOptions(registry.assets.rules),
79
- required: false
80
- }));
81
- return selected;
82
- }
83
- function assetOptions(assets) {
84
- return Object.entries(assets).map(([name, asset]) => ({
85
- value: name,
86
- label: name,
87
- hint: asset.description
88
- }));
89
- }
90
- async function promptOrExit(prompt) {
91
- const value = await prompt;
92
- if (isCancel(value)) exitCancelled();
93
- return value;
94
- }
95
- function exitCancelled() {
96
- cancel("Dewey agent setup cancelled.");
97
- process.exit(0);
98
- }
99
- //#endregion
100
- export { promptForInit };