@cdoing/cli 0.1.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.
Files changed (118) hide show
  1. package/.cdoing/permissions.json +8 -0
  2. package/dist/callbacks.d.ts +17 -0
  3. package/dist/callbacks.d.ts.map +1 -0
  4. package/dist/callbacks.js +265 -0
  5. package/dist/callbacks.js.map +1 -0
  6. package/dist/chat.d.ts +27 -0
  7. package/dist/chat.d.ts.map +1 -0
  8. package/dist/chat.js +57 -0
  9. package/dist/chat.js.map +1 -0
  10. package/dist/commands.d.ts +22 -0
  11. package/dist/commands.d.ts.map +1 -0
  12. package/dist/commands.js +452 -0
  13. package/dist/commands.js.map +1 -0
  14. package/dist/config.d.ts +84 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +427 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/help.d.ts +9 -0
  19. package/dist/help.d.ts.map +1 -0
  20. package/dist/help.js +167 -0
  21. package/dist/help.js.map +1 -0
  22. package/dist/history.d.ts +51 -0
  23. package/dist/history.d.ts.map +1 -0
  24. package/dist/history.js +207 -0
  25. package/dist/history.js.map +1 -0
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +220 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/oauth.d.ts +13 -0
  31. package/dist/oauth.d.ts.map +1 -0
  32. package/dist/oauth.js +182 -0
  33. package/dist/oauth.js.map +1 -0
  34. package/dist/review.d.ts +26 -0
  35. package/dist/review.d.ts.map +1 -0
  36. package/dist/review.js +198 -0
  37. package/dist/review.js.map +1 -0
  38. package/dist/serve.d.ts +23 -0
  39. package/dist/serve.d.ts.map +1 -0
  40. package/dist/serve.js +293 -0
  41. package/dist/serve.js.map +1 -0
  42. package/dist/tools.d.ts +14 -0
  43. package/dist/tools.d.ts.map +1 -0
  44. package/dist/tools.js +57 -0
  45. package/dist/tools.js.map +1 -0
  46. package/dist/ui/App.d.ts +24 -0
  47. package/dist/ui/App.d.ts.map +1 -0
  48. package/dist/ui/App.js +321 -0
  49. package/dist/ui/App.js.map +1 -0
  50. package/dist/ui/MessageList.d.ts +14 -0
  51. package/dist/ui/MessageList.d.ts.map +1 -0
  52. package/dist/ui/MessageList.js +147 -0
  53. package/dist/ui/MessageList.js.map +1 -0
  54. package/dist/ui/SessionBrowser.d.ts +18 -0
  55. package/dist/ui/SessionBrowser.d.ts.map +1 -0
  56. package/dist/ui/SessionBrowser.js +149 -0
  57. package/dist/ui/SessionBrowser.js.map +1 -0
  58. package/dist/ui/SetupWizard.d.ts +23 -0
  59. package/dist/ui/SetupWizard.d.ts.map +1 -0
  60. package/dist/ui/SetupWizard.js +402 -0
  61. package/dist/ui/SetupWizard.js.map +1 -0
  62. package/dist/ui/Spinner.d.ts +15 -0
  63. package/dist/ui/Spinner.d.ts.map +1 -0
  64. package/dist/ui/Spinner.js +111 -0
  65. package/dist/ui/Spinner.js.map +1 -0
  66. package/dist/ui/StatusBar.d.ts +16 -0
  67. package/dist/ui/StatusBar.d.ts.map +1 -0
  68. package/dist/ui/StatusBar.js +56 -0
  69. package/dist/ui/StatusBar.js.map +1 -0
  70. package/dist/ui/UserInput.d.ts +13 -0
  71. package/dist/ui/UserInput.d.ts.map +1 -0
  72. package/dist/ui/UserInput.js +872 -0
  73. package/dist/ui/UserInput.js.map +1 -0
  74. package/dist/ui/hooks/helpers.d.ts +55 -0
  75. package/dist/ui/hooks/helpers.d.ts.map +1 -0
  76. package/dist/ui/hooks/helpers.js +304 -0
  77. package/dist/ui/hooks/helpers.js.map +1 -0
  78. package/dist/ui/hooks/useAgent.d.ts +60 -0
  79. package/dist/ui/hooks/useAgent.d.ts.map +1 -0
  80. package/dist/ui/hooks/useAgent.js +213 -0
  81. package/dist/ui/hooks/useAgent.js.map +1 -0
  82. package/dist/ui/hooks/useChat.d.ts +74 -0
  83. package/dist/ui/hooks/useChat.d.ts.map +1 -0
  84. package/dist/ui/hooks/useChat.js +819 -0
  85. package/dist/ui/hooks/useChat.js.map +1 -0
  86. package/dist/ui/theme.d.ts +73 -0
  87. package/dist/ui/theme.d.ts.map +1 -0
  88. package/dist/ui/theme.js +214 -0
  89. package/dist/ui/theme.js.map +1 -0
  90. package/dist/ui/types.d.ts +37 -0
  91. package/dist/ui/types.d.ts.map +1 -0
  92. package/dist/ui/types.js +3 -0
  93. package/dist/ui/types.js.map +1 -0
  94. package/package.json +33 -0
  95. package/src/callbacks.ts +294 -0
  96. package/src/chat.ts +72 -0
  97. package/src/commands.ts +425 -0
  98. package/src/config.ts +462 -0
  99. package/src/help.ts +182 -0
  100. package/src/history.ts +205 -0
  101. package/src/index.ts +248 -0
  102. package/src/oauth.ts +164 -0
  103. package/src/review.ts +233 -0
  104. package/src/serve.ts +290 -0
  105. package/src/tools.ts +104 -0
  106. package/src/ui/App.tsx +426 -0
  107. package/src/ui/MessageList.tsx +222 -0
  108. package/src/ui/SessionBrowser.tsx +161 -0
  109. package/src/ui/SetupWizard.tsx +412 -0
  110. package/src/ui/Spinner.tsx +103 -0
  111. package/src/ui/StatusBar.tsx +106 -0
  112. package/src/ui/UserInput.tsx +954 -0
  113. package/src/ui/hooks/helpers.ts +271 -0
  114. package/src/ui/hooks/useAgent.ts +270 -0
  115. package/src/ui/hooks/useChat.ts +943 -0
  116. package/src/ui/theme.ts +326 -0
  117. package/src/ui/types.ts +41 -0
  118. package/tsconfig.json +18 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * CLI Subcommand Handlers
3
+ *
4
+ * Handlers for: cdoing config, cdoing init, cdoing doctor
5
+ */
6
+
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ import chalk from "chalk";
11
+ import figlet from "figlet";
12
+ import { loadConfig, saveConfig, getStoredConfigDisplay, updateStoredConfig } from "./config";
13
+ import { getApiKeyEnvVar } from "@cdoing/ai";
14
+
15
+ const CONFIG_DIR = path.join(os.homedir(), ".cdoing");
16
+ const PROJECT_CONFIG_DIR = ".cdoing";
17
+ const PROJECT_CONFIG_FILE = "config.md";
18
+
19
+ /**
20
+ * Handle `cdoing config <action> [key] [value]`
21
+ */
22
+ export function handleConfigCommand(action: string, key?: string, value?: string): void {
23
+ switch (action) {
24
+ case "list":
25
+ console.log();
26
+ console.log(chalk.hex("#4FC3F7").bold(" ⚙️ Stored Config"));
27
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
28
+ for (const line of getStoredConfigDisplay()) {
29
+ console.log(chalk.hex("#B0BEC5")(` ${line}`));
30
+ }
31
+ console.log();
32
+ break;
33
+
34
+ case "get":
35
+ if (!key) {
36
+ console.log(chalk.hex("#EF5350")("\n ❌ Usage: cdoing config get <key>"));
37
+ console.log(chalk.hex("#78909C")(" Keys: provider, model, mode, api-key, base-url\n"));
38
+ return;
39
+ }
40
+ const config = loadConfig();
41
+ let val: string | undefined;
42
+ switch (key) {
43
+ case "provider": val = config.provider; break;
44
+ case "model": val = config.model; break;
45
+ case "mode": val = config.mode; break;
46
+ case "base-url": val = config.baseUrl; break;
47
+ case "api-key":
48
+ const provider = config.provider || "anthropic";
49
+ val = config.apiKeys?.[provider];
50
+ if (val) val = val.slice(0, 8) + "..." + val.slice(-4);
51
+ break;
52
+ default:
53
+ console.log(chalk.hex("#EF5350")(`\n ❌ Unknown key: ${key}\n`));
54
+ return;
55
+ }
56
+ console.log(val ? chalk.hex("#81C784")(val) : chalk.hex("#78909C")("(not set)"));
57
+ break;
58
+
59
+ case "set":
60
+ if (!key || value === undefined) {
61
+ console.log(chalk.hex("#EF5350")("\n ❌ Usage: cdoing config set <key> <value>"));
62
+ console.log(chalk.hex("#78909C")(" Keys: provider, model, mode, api-key, base-url\n"));
63
+ return;
64
+ }
65
+ const result = updateStoredConfig(key, value);
66
+ if (result.success) {
67
+ const display = key === "api-key" ? value.slice(0, 8) + "..." : value;
68
+ console.log();
69
+ console.log(chalk.hex("#81C784")(" ✓ Saved: ") + chalk.hex("#4FC3F7")(key) + chalk.hex("#78909C")(" = ") + chalk.hex("#FFFFFF")(display));
70
+ console.log();
71
+ } else {
72
+ console.log(chalk.hex("#EF5350")(`\n ❌ ${result.error}\n`));
73
+ }
74
+ break;
75
+
76
+ default:
77
+ console.log(chalk.hex("#EF5350")(`\n ❌ Unknown action: ${action}`));
78
+ console.log(chalk.hex("#78909C")(" Usage: cdoing config <list|get|set> [key] [value]\n"));
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Handle `cdoing init` - create .cdoing/ directory and config.md template
84
+ */
85
+ export function handleInit(): void {
86
+ const cwd = process.cwd();
87
+ const configDir = path.join(cwd, PROJECT_CONFIG_DIR);
88
+ const configFile = path.join(configDir, PROJECT_CONFIG_FILE);
89
+
90
+ if (fs.existsSync(configFile)) {
91
+ console.log();
92
+ console.log(chalk.hex("#FFB74D")(" ⚠️ Project already initialized"));
93
+ console.log(chalk.hex("#90A4AE")(" .cdoing/config.md exists"));
94
+ console.log();
95
+ return;
96
+ }
97
+
98
+ if (!fs.existsSync(configDir)) {
99
+ fs.mkdirSync(configDir, { recursive: true });
100
+ }
101
+
102
+ const template = `# Project Configuration
103
+
104
+ This file configures cdoing for this project.
105
+
106
+ ## Instructions
107
+
108
+ Add project-specific instructions here. The agent will follow these guidelines.
109
+
110
+ \`\`\`
111
+ - Use TypeScript for all new code
112
+ - Follow existing code patterns
113
+ - Write tests for new features
114
+ \`\`\`
115
+
116
+ ## Context
117
+
118
+ Describe your project here:
119
+ - What does this project do?
120
+ - What technologies does it use?
121
+ - Any special conventions?
122
+
123
+ ## Files to Ignore
124
+
125
+ Files the agent should not modify:
126
+ - node_modules/
127
+ - dist/
128
+ - .env
129
+
130
+ ## Preferred Tools
131
+
132
+ - Use npm for package management
133
+ - Use vitest for testing
134
+ `;
135
+
136
+ fs.writeFileSync(configFile, template, "utf-8");
137
+ console.log();
138
+ console.log(chalk.hex("#6BCB77")(figlet.textSync("Init!", { font: "Small" })));
139
+ console.log(chalk.hex("#81C784")(" ✨ Project initialized!"));
140
+ console.log(chalk.hex("#90A4AE")(" Created: ") + chalk.hex("#4FC3F7")(".cdoing/config.md"));
141
+ console.log();
142
+ console.log(chalk.hex("#78909C")(" Edit this file to customize agent behavior for your project."));
143
+ console.log();
144
+ }
145
+
146
+ // ── Shell completion scripts ──────────────────────────────────────────────────
147
+
148
+ const ZSH_COMPLETION = `#compdef cdoing
149
+ # Zsh completion for cdoing — install with: cdoing completions zsh > ~/.zsh/completions/_cdoing
150
+ # Then add to ~/.zshrc: fpath=(~/.zsh/completions $fpath) && autoload -Uz compinit && compinit
151
+
152
+ _cdoing() {
153
+ local context state line
154
+ typeset -A opt_args
155
+
156
+ _arguments -C \\
157
+ '(-m --model)'{-m,--model}'[Model to use]:model:->models' \\
158
+ '(-p --provider)'{-p,--provider}'[AI provider]:provider:(anthropic openai google ollama custom)' \\
159
+ '--base-url[Base URL for custom providers]:url:' \\
160
+ '--api-key[API key]:key:' \\
161
+ '--mode[Permission mode]:mode:(ask auto-edit auto)' \\
162
+ '(-d --dir)'{-d,--dir}'[Working directory]:directory:_directories' \\
163
+ '--login[Login with Claude via OAuth]' \\
164
+ '--logout[Clear stored OAuth tokens]' \\
165
+ '--print[Print output only]' \\
166
+ '(-r --resume)'{-r,--resume}'[Resume conversation by ID]:id:' \\
167
+ '(-c --continue)'{-c,--continue}'[Continue most recent conversation]' \\
168
+ '--max-turns[Maximum agent turns]:turns:' \\
169
+ '--output-format[Output format]:format:(text json stream-json)' \\
170
+ '--verbose[Enable verbose logging]' \\
171
+ '--system-prompt[Custom system prompt]:prompt:' \\
172
+ '--allowed-tools[Comma-separated allowed tools]:tools:' \\
173
+ '--disallowed-tools[Comma-separated disallowed tools]:tools:' \\
174
+ '(-h --help)'{-h,--help}'[Show help]' \\
175
+ '(-V --version)'{-V,--version}'[Show version]' \\
176
+ '1: :->subcmd' \\
177
+ '*:: :->args'
178
+
179
+ case $state in
180
+ subcmd)
181
+ local -a cmds
182
+ cmds=(
183
+ 'config:Manage configuration'
184
+ 'init:Initialize project with .cdoing/config.md'
185
+ 'doctor:Diagnose setup and configuration'
186
+ 'completions:Generate shell completion script'
187
+ )
188
+ _describe 'subcommand' cmds
189
+ ;;
190
+ models)
191
+ local -a models
192
+ models=(
193
+ 'claude-sonnet-4-6:Anthropic Sonnet 4.6'
194
+ 'claude-opus-4-6:Anthropic Opus 4.6'
195
+ 'claude-haiku-4-5:Anthropic Haiku 4.5'
196
+ 'gpt-4o:OpenAI GPT-4o'
197
+ 'gpt-4o-mini:OpenAI GPT-4o Mini'
198
+ 'o3-mini:OpenAI o3 Mini'
199
+ 'gemini-2.0-flash:Google Gemini 2.0 Flash'
200
+ 'gemini-1.5-pro:Google Gemini 1.5 Pro'
201
+ 'llama3.1:Ollama LLaMA 3.1'
202
+ 'mistral:Ollama Mistral'
203
+ 'codellama:Ollama CodeLlama'
204
+ )
205
+ _describe 'model' models
206
+ ;;
207
+ args)
208
+ case $words[1] in
209
+ config)
210
+ _arguments \\
211
+ '1:action:(get set list)' \\
212
+ '2:key:(provider model mode api-key base-url)' \\
213
+ '3:value:'
214
+ ;;
215
+ completions)
216
+ _arguments '1:shell:(zsh bash)'
217
+ ;;
218
+ esac
219
+ ;;
220
+ esac
221
+ }
222
+
223
+ _cdoing "$@"
224
+ `;
225
+
226
+ const BASH_COMPLETION = `# Bash completion for cdoing — install with: cdoing completions bash > ~/.bash_completion.d/cdoing
227
+ # Then add to ~/.bashrc: source ~/.bash_completion.d/cdoing
228
+
229
+ _cdoing_completion() {
230
+ local cur prev words cword
231
+ _init_completion 2>/dev/null || {
232
+ COMPREPLY=()
233
+ cur="\${COMP_WORDS[COMP_CWORD]}"
234
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
235
+ }
236
+
237
+ local subcommands="config init doctor completions"
238
+ local flags="--model --provider --base-url --api-key --mode --dir --login --logout --print --resume --continue --max-turns --output-format --verbose --system-prompt --allowed-tools --disallowed-tools --help --version"
239
+ local models="claude-sonnet-4-6 claude-opus-4-6 claude-haiku-4-5 gpt-4o gpt-4o-mini o3-mini gemini-2.0-flash gemini-1.5-pro llama3.1 mistral codellama"
240
+
241
+ case "\${prev}" in
242
+ --model|-m)
243
+ COMPREPLY=( \$(compgen -W "\${models}" -- "\${cur}") )
244
+ return ;;
245
+ --provider|-p)
246
+ COMPREPLY=( \$(compgen -W "anthropic openai google ollama custom" -- "\${cur}") )
247
+ return ;;
248
+ --mode)
249
+ COMPREPLY=( \$(compgen -W "ask auto-edit auto" -- "\${cur}") )
250
+ return ;;
251
+ --output-format)
252
+ COMPREPLY=( \$(compgen -W "text json stream-json" -- "\${cur}") )
253
+ return ;;
254
+ --dir|-d)
255
+ COMPREPLY=( \$(compgen -d -- "\${cur}") )
256
+ return ;;
257
+ config)
258
+ COMPREPLY=( \$(compgen -W "get set list" -- "\${cur}") )
259
+ return ;;
260
+ get|set)
261
+ COMPREPLY=( \$(compgen -W "provider model mode api-key base-url" -- "\${cur}") )
262
+ return ;;
263
+ completions)
264
+ COMPREPLY=( \$(compgen -W "zsh bash" -- "\${cur}") )
265
+ return ;;
266
+ esac
267
+
268
+ if [[ "\${cur}" == -* ]]; then
269
+ COMPREPLY=( \$(compgen -W "\${flags}" -- "\${cur}") )
270
+ return
271
+ fi
272
+
273
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
274
+ COMPREPLY=( \$(compgen -W "\${subcommands}" -- "\${cur}") )
275
+ return
276
+ fi
277
+ }
278
+
279
+ complete -F _cdoing_completion cdoing
280
+ `;
281
+
282
+ /**
283
+ * Handle `cdoing completions <shell>` - print shell completion script
284
+ */
285
+ export function handleCompletions(shell: string): void {
286
+ const s = (shell || "").toLowerCase();
287
+
288
+ if (s === "zsh") {
289
+ process.stdout.write(ZSH_COMPLETION);
290
+ return;
291
+ }
292
+
293
+ if (s === "bash") {
294
+ process.stdout.write(BASH_COMPLETION);
295
+ return;
296
+ }
297
+
298
+ // No shell arg — print install instructions
299
+ console.log();
300
+ console.log(chalk.hex("#4FC3F7").bold(" 🐚 Shell Completions"));
301
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
302
+ console.log();
303
+ console.log(chalk.hex("#B0BEC5")(" Usage: cdoing completions <shell>"));
304
+ console.log(chalk.hex("#78909C")(" Shells: zsh bash"));
305
+ console.log();
306
+ console.log(chalk.hex("#FFB74D").bold(" Zsh:"));
307
+ console.log(chalk.hex("#90A4AE")(" mkdir -p ~/.zsh/completions"));
308
+ console.log(chalk.hex("#81C784")(" cdoing completions zsh > ~/.zsh/completions/_cdoing"));
309
+ console.log(chalk.hex("#90A4AE")(" # Add to ~/.zshrc if not already present:"));
310
+ console.log(chalk.hex("#90A4AE")(' echo \'fpath=(~/.zsh/completions $fpath)\' >> ~/.zshrc'));
311
+ console.log(chalk.hex("#90A4AE")(' echo "autoload -Uz compinit && compinit" >> ~/.zshrc'));
312
+ console.log(chalk.hex("#90A4AE")(" source ~/.zshrc"));
313
+ console.log();
314
+ console.log(chalk.hex("#FFB74D").bold(" Bash:"));
315
+ console.log(chalk.hex("#90A4AE")(" mkdir -p ~/.bash_completion.d"));
316
+ console.log(chalk.hex("#81C784")(" cdoing completions bash > ~/.bash_completion.d/cdoing"));
317
+ console.log(chalk.hex("#90A4AE")(' echo "source ~/.bash_completion.d/cdoing" >> ~/.bashrc'));
318
+ console.log(chalk.hex("#90A4AE")(" source ~/.bashrc"));
319
+ console.log();
320
+ }
321
+
322
+ /**
323
+ * Handle `cdoing doctor` - diagnose setup and configuration issues
324
+ */
325
+ export function handleDoctor(): void {
326
+ console.log();
327
+ console.log(chalk.hex("#4FC3F7").bold(" 🏥 System Diagnostics"));
328
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
329
+ console.log();
330
+
331
+ let issues = 0;
332
+ let passed = 0;
333
+
334
+ const ok = (msg: string) => {
335
+ passed++;
336
+ console.log(chalk.hex("#81C784")(" ✓ ") + chalk.hex("#B0BEC5")(msg));
337
+ };
338
+ const skip = (msg: string) => {
339
+ console.log(chalk.hex("#78909C")(" ○ ") + chalk.hex("#78909C")(msg));
340
+ };
341
+ const fail = (msg: string) => {
342
+ issues++;
343
+ console.log(chalk.hex("#EF5350")(" ✗ ") + chalk.hex("#FFCDD2")(msg));
344
+ };
345
+
346
+ // Check global config directory
347
+ if (fs.existsSync(CONFIG_DIR)) {
348
+ ok("Global config: ~/.cdoing/");
349
+ } else {
350
+ skip("Global config: not created yet");
351
+ }
352
+
353
+ // Check API keys
354
+ const config = loadConfig();
355
+ const providers = ["anthropic", "openai", "google"];
356
+ const providerIcons: Record<string, string> = {
357
+ anthropic: "🤖",
358
+ openai: "🧠",
359
+ google: "🌐",
360
+ };
361
+
362
+ for (const provider of providers) {
363
+ const envVar = getApiKeyEnvVar(provider);
364
+ const hasEnv = !!process.env[envVar];
365
+ const hasStored = !!config.apiKeys?.[provider];
366
+ const icon = providerIcons[provider] || "🔑";
367
+
368
+ if (hasEnv) {
369
+ ok(`${icon} ${provider}: API key in ${envVar}`);
370
+ } else if (hasStored) {
371
+ ok(`${icon} ${provider}: API key in config`);
372
+ } else {
373
+ skip(`${icon} ${provider}: no API key configured`);
374
+ }
375
+ }
376
+
377
+ // Check project config
378
+ const projectConfig = path.join(process.cwd(), PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
379
+ if (fs.existsSync(projectConfig)) {
380
+ ok("📁 Project config: .cdoing/config.md");
381
+ } else {
382
+ skip("📁 Project config: not initialized (run: cdoing init)");
383
+ }
384
+
385
+ // Check hooks
386
+ const globalHooks = path.join(CONFIG_DIR, "hooks.json");
387
+ const projectHooks = path.join(process.cwd(), PROJECT_CONFIG_DIR, "hooks.json");
388
+
389
+ if (fs.existsSync(globalHooks)) {
390
+ ok("🪝 Global hooks: ~/.cdoing/hooks.json");
391
+ }
392
+ if (fs.existsSync(projectHooks)) {
393
+ ok("🪝 Project hooks: .cdoing/hooks.json");
394
+ }
395
+
396
+ // Check permissions
397
+ const globalPerms = path.join(CONFIG_DIR, "permissions.json");
398
+ const projectPerms = path.join(process.cwd(), PROJECT_CONFIG_DIR, "permissions.json");
399
+
400
+ if (fs.existsSync(globalPerms)) {
401
+ ok("🔐 Global permissions: ~/.cdoing/permissions.json");
402
+ }
403
+ if (fs.existsSync(projectPerms)) {
404
+ ok("🔐 Project permissions: .cdoing/permissions.json");
405
+ }
406
+
407
+ // Check Node.js version
408
+ const nodeVersion = process.version;
409
+ const major = parseInt(nodeVersion.slice(1).split(".")[0], 10);
410
+ if (major >= 18) {
411
+ ok(`📦 Node.js: ${nodeVersion}`);
412
+ } else {
413
+ fail(`📦 Node.js: ${nodeVersion} (requires 18+)`);
414
+ }
415
+
416
+ // Summary
417
+ console.log();
418
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
419
+ if (issues === 0) {
420
+ console.log(chalk.hex("#81C784").bold(` ✨ All ${passed} checks passed!`));
421
+ } else {
422
+ console.log(chalk.hex("#FFB74D")(` ⚠️ ${passed} passed, ${issues} issue(s) found`));
423
+ }
424
+ console.log();
425
+ }