@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
package/src/config.ts ADDED
@@ -0,0 +1,462 @@
1
+ /**
2
+ * CLI Configuration — parses options, validates API key,
3
+ * interactive setup wizard on first run.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import * as os from "os";
9
+ import * as readline from "readline";
10
+ import chalk from "chalk";
11
+ import { PermissionManager, PermissionMode, SandboxManager } from "@cdoing/core";
12
+ import { getApiKeyEnvVar, type ModelConfig } from "@cdoing/ai";
13
+ import { resolveOAuthToken } from "./oauth";
14
+
15
+ const CONFIG_DIR = path.join(os.homedir(), ".cdoing");
16
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
17
+
18
+ export interface StoredConfig {
19
+ provider?: string;
20
+ model?: string;
21
+ apiKeys?: Record<string, string>;
22
+ mode?: string;
23
+ baseUrl?: string;
24
+ apiKeyHelper?: string;
25
+ /** UI theme: "dark", "light", or "auto" (default) */
26
+ theme?: string;
27
+ /** Indexer configuration */
28
+ indexer?: {
29
+ /** Embedding model ID (e.g. "text-embedding-3-small", "nomic-embed-text") */
30
+ embeddingModel?: string;
31
+ /** Embedding provider: "openai", "ollama", or "none" (FTS only) */
32
+ embeddingProvider?: string;
33
+ /** Base URL for embedding API (e.g. "http://localhost:11434" for Ollama) */
34
+ embeddingBaseUrl?: string;
35
+ /** API key for embedding provider (uses main provider key if not set) */
36
+ embeddingApiKey?: string;
37
+ /** Auto-index on startup. Default: true */
38
+ autoIndex?: boolean;
39
+ };
40
+ }
41
+
42
+ export interface CLIOptions {
43
+ model?: string;
44
+ provider: string;
45
+ baseUrl?: string;
46
+ apiKey?: string;
47
+ oauthToken?: string;
48
+ mode: string;
49
+ dir: string;
50
+ login?: boolean;
51
+ logout?: boolean;
52
+ // New flags
53
+ print?: boolean;
54
+ resume?: string;
55
+ continue?: boolean;
56
+ maxTurns?: string;
57
+ outputFormat?: string;
58
+ verbose?: boolean;
59
+ systemPrompt?: string;
60
+ allowedTools?: string;
61
+ disallowedTools?: string;
62
+ }
63
+
64
+ export function parsePermissionMode(mode: string): PermissionMode {
65
+ switch (mode) {
66
+ case "bypassPermissions": return PermissionMode.BYPASS;
67
+ case "auto": return PermissionMode.BYPASS; // legacy alias
68
+ case "acceptEdits": return PermissionMode.ACCEPT_EDITS;
69
+ case "auto-edit": return PermissionMode.ACCEPT_EDITS; // legacy alias
70
+ case "plan": return PermissionMode.PLAN;
71
+ case "dontAsk": return PermissionMode.DONT_ASK;
72
+ case "default": return PermissionMode.DEFAULT;
73
+ default: return PermissionMode.DEFAULT; // "ask" and unknown → default
74
+ }
75
+ }
76
+
77
+ export function buildModelConfig(options: CLIOptions): Partial<ModelConfig> {
78
+ return {
79
+ provider: options.provider.toLowerCase(),
80
+ model: options.model,
81
+ baseURL: options.baseUrl,
82
+ apiKey: options.apiKey,
83
+ oauthToken: options.oauthToken,
84
+ };
85
+ }
86
+
87
+ export function createPermissionManager(options: CLIOptions): PermissionManager {
88
+ const dir = path.resolve(options.dir || process.cwd());
89
+ return new PermissionManager(parsePermissionMode(options.mode), dir);
90
+ }
91
+
92
+ export function createSandboxManager(options: CLIOptions): SandboxManager {
93
+ const dir = path.resolve(options.dir || process.cwd());
94
+ return new SandboxManager(dir, dir);
95
+ }
96
+
97
+ // ── Config file ─────────────────────────────────────────────
98
+
99
+ export function loadConfig(): StoredConfig {
100
+ try {
101
+ if (fs.existsSync(CONFIG_FILE))
102
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
103
+ } catch {}
104
+ return {};
105
+ }
106
+
107
+ export function saveConfig(config: StoredConfig): void {
108
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
109
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
110
+ }
111
+
112
+ const VALID_CONFIG_KEYS = ["provider", "model", "mode", "api-key", "base-url", "api-key-helper"] as const;
113
+ type ConfigKey = typeof VALID_CONFIG_KEYS[number];
114
+
115
+ /**
116
+ * Update a stored config value. Returns true on success.
117
+ */
118
+ export function updateStoredConfig(key: string, value: string): { success: boolean; error?: string } {
119
+ if (!VALID_CONFIG_KEYS.includes(key as ConfigKey)) {
120
+ return {
121
+ success: false,
122
+ error: `Unknown key "${key}". Valid keys: ${VALID_CONFIG_KEYS.join(", ")}`,
123
+ };
124
+ }
125
+
126
+ const config = loadConfig();
127
+
128
+ switch (key) {
129
+ case "provider":
130
+ config.provider = value;
131
+ break;
132
+ case "model":
133
+ config.model = value;
134
+ break;
135
+ case "mode": {
136
+ const validModes = ["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions", "ask", "auto-edit", "auto"];
137
+ if (!validModes.includes(value)) {
138
+ return { success: false, error: `Invalid mode "${value}". Valid: ${validModes.slice(0, 5).join(", ")}` };
139
+ }
140
+ config.mode = value;
141
+ break;
142
+ }
143
+ case "api-key": {
144
+ const provider = config.provider || "anthropic";
145
+ config.apiKeys = config.apiKeys || {};
146
+ config.apiKeys[provider] = value;
147
+ break;
148
+ }
149
+ case "base-url":
150
+ config.baseUrl = value;
151
+ break;
152
+ case "api-key-helper":
153
+ config.apiKeyHelper = value;
154
+ break;
155
+ }
156
+
157
+ saveConfig(config);
158
+ return { success: true };
159
+ }
160
+
161
+ /**
162
+ * Show all stored config values.
163
+ */
164
+ export function getStoredConfigDisplay(): string[] {
165
+ const config = loadConfig();
166
+ const lines: string[] = [];
167
+
168
+ lines.push(` provider: ${config.provider || "(not set — defaults to anthropic)"}`);
169
+ lines.push(` model: ${config.model || "(not set — uses provider default)"}`);
170
+ lines.push(` mode: ${config.mode || "(not set — defaults to ask)"}`);
171
+ lines.push(` base-url: ${config.baseUrl || "(not set)"}`);
172
+
173
+ lines.push(` api-key-helper: ${config.apiKeyHelper || "(not set)"}`);
174
+
175
+ if (config.apiKeys) {
176
+ for (const [provider, key] of Object.entries(config.apiKeys)) {
177
+ const masked = key.slice(0, 8) + "..." + key.slice(-4);
178
+ lines.push(` api-key [${provider}]: ${masked}`);
179
+ }
180
+ } else {
181
+ lines.push(` api-key: (not set)`);
182
+ }
183
+
184
+ return lines;
185
+ }
186
+
187
+ function ask(question: string): Promise<string> {
188
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
189
+ return new Promise((resolve) => {
190
+ rl.question(question, (a) => { rl.close(); resolve(a.trim()); });
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Check if an API key exists for the provider (env var or stored config).
196
+ * If missing, prompt the user to enter one and optionally save it.
197
+ * Returns the key, or undefined if skipped.
198
+ */
199
+ export async function promptApiKeyIfMissing(provider: string): Promise<string | undefined> {
200
+ const envVar = getApiKeyEnvVar(provider);
201
+ if (process.env[envVar]) return process.env[envVar];
202
+
203
+ const stored = loadConfig();
204
+ if (stored.apiKeys?.[provider]) return stored.apiKeys[provider];
205
+
206
+ // Ollama doesn't need a key
207
+ if (provider === "ollama") return undefined;
208
+
209
+ const info = PROVIDER_INFO[provider];
210
+ console.log(chalk.yellow(`\n No API key found for ${provider}.`));
211
+ if (info?.url) console.log(chalk.dim(` Get a key: ${info.url}\n`));
212
+
213
+ const key = await ask(chalk.green(` Enter your ${provider} API key (or press Enter to skip): `));
214
+ if (!key) {
215
+ console.log(chalk.dim(" Skipped. You can set it later with: /config set api-key <key>\n"));
216
+ return undefined;
217
+ }
218
+
219
+ const save = await ask(chalk.green(" Save to ~/.cdoing/config.json? (Y/n): "));
220
+ if (save.toLowerCase() !== "n") {
221
+ const config = loadConfig();
222
+ config.apiKeys = config.apiKeys || {};
223
+ config.apiKeys[provider] = key;
224
+ saveConfig(config);
225
+ console.log(chalk.green(" Saved!\n"));
226
+ }
227
+
228
+ return key;
229
+ }
230
+
231
+ export interface SelectOption {
232
+ label: string;
233
+ hint?: string;
234
+ value: string;
235
+ }
236
+
237
+ export function selectMenu(title: string, options: SelectOption[], defaultIndex = 0): Promise<string> {
238
+ return new Promise((resolve) => {
239
+ let idx = defaultIndex;
240
+
241
+ const render = () => {
242
+ // Move cursor up to redraw (skip on first render)
243
+ if ((render as any).drawn) {
244
+ process.stdout.write(`\x1b[${options.length + 1}A`);
245
+ }
246
+ (render as any).drawn = true;
247
+
248
+ process.stdout.write(`\x1b[2K\n`);
249
+ options.forEach((opt, i) => {
250
+ const selected = i === idx;
251
+ const pointer = selected ? chalk.cyan(" ❯ ") : " ";
252
+ const label = selected ? chalk.bold.white(opt.label) : chalk.white(opt.label);
253
+ const hint = opt.hint ? chalk.dim(` ${opt.hint}`) : "";
254
+ process.stdout.write(`\x1b[2K${pointer}${label}${hint}\n`);
255
+ });
256
+ };
257
+
258
+ console.log(chalk.bold.cyan(`\n ${title}`));
259
+ render();
260
+
261
+ readline.emitKeypressEvents(process.stdin);
262
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
263
+
264
+ const onKey = (_: string, key: readline.Key) => {
265
+ if (key.name === "up") {
266
+ idx = (idx - 1 + options.length) % options.length;
267
+ render();
268
+ } else if (key.name === "down") {
269
+ idx = (idx + 1) % options.length;
270
+ render();
271
+ } else if (key.name === "return") {
272
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
273
+ process.stdin.removeListener("keypress", onKey);
274
+ process.stdout.write("\n");
275
+ resolve(options[idx].value);
276
+ } else if (key.name === "c" && key.ctrl) {
277
+ process.stdout.write("\n");
278
+ process.exit(0);
279
+ }
280
+ };
281
+
282
+ process.stdin.on("keypress", onKey);
283
+ });
284
+ }
285
+
286
+ // ── API key resolution ──────────────────────────────────────
287
+
288
+ const PROVIDER_INFO: Record<string, { name: string; url: string }> = {
289
+ anthropic: { name: "Anthropic (Claude)", url: "https://console.anthropic.com/settings/keys" },
290
+ openai: { name: "OpenAI (GPT)", url: "https://platform.openai.com/api-keys" },
291
+ google: { name: "Google (Gemini)", url: "https://aistudio.google.com/apikey" },
292
+ };
293
+
294
+ const PROVIDER_MODELS: Record<string, SelectOption[]> = {
295
+ anthropic: [
296
+ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", hint: "recommended · fast & smart" },
297
+ { value: "claude-opus-4-6", label: "Claude Opus 4.6", hint: "most capable" },
298
+ { value: "claude-haiku-4-5", label: "Claude Haiku 4.5", hint: "fastest" },
299
+ ],
300
+ openai: [
301
+ { value: "gpt-4o", label: "GPT-4o", hint: "recommended" },
302
+ { value: "gpt-4o-mini", label: "GPT-4o mini", hint: "fastest" },
303
+ { value: "o3-mini", label: "o3-mini", hint: "reasoning" },
304
+ ],
305
+ google: [
306
+ { value: "gemini-2.0-flash", label: "Gemini 2.0 Flash", hint: "recommended · fast" },
307
+ { value: "gemini-1.5-pro", label: "Gemini 1.5 Pro", hint: "most capable" },
308
+ { value: "gemini-1.5-flash", label: "Gemini 1.5 Flash", hint: "fastest" },
309
+ ],
310
+ };
311
+
312
+ /**
313
+ * Run the apiKeyHelper script and return its stdout trimmed, or null on failure.
314
+ */
315
+ function runApiKeyHelper(scriptPath: string): string | null {
316
+ try {
317
+ const { execFileSync } = require("child_process") as typeof import("child_process");
318
+ const resolved = scriptPath.startsWith("~")
319
+ ? path.join(os.homedir(), scriptPath.slice(1))
320
+ : scriptPath;
321
+ const key = execFileSync(resolved, { encoding: "utf-8", timeout: 5000 }).trim();
322
+ return key || null;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Resolve API key from: flag → apiKeyHelper → env → stored config → OAuth token → interactive setup.
330
+ * Mutates options.apiKey so downstream code can use it.
331
+ */
332
+ export async function resolveApiKey(options: CLIOptions): Promise<void> {
333
+ if (options.apiKey) return;
334
+
335
+ // Apply stored config to options (stored provider wins over CLI default "anthropic")
336
+ const stored = loadConfig();
337
+ const isDefaultProvider = options.provider === "anthropic";
338
+ if (isDefaultProvider && stored.provider) options.provider = stored.provider;
339
+ if (!options.model && stored.model) options.model = stored.model;
340
+ if (!options.baseUrl && stored.baseUrl) options.baseUrl = stored.baseUrl;
341
+
342
+ const provider = options.provider.toLowerCase();
343
+ const envVar = getApiKeyEnvVar(provider);
344
+
345
+ // apiKeyHelper: run a script to get the key dynamically (e.g. for proxies)
346
+ if (stored.apiKeyHelper) {
347
+ const key = runApiKeyHelper(stored.apiKeyHelper);
348
+ if (key) {
349
+ options.apiKey = key;
350
+ return;
351
+ }
352
+ console.log(chalk.yellow(` Warning: apiKeyHelper script failed or returned empty — falling back.\n`));
353
+ }
354
+
355
+ if (process.env[envVar]) return;
356
+
357
+ if (stored.apiKeys?.[provider]) {
358
+ options.apiKey = stored.apiKeys[provider];
359
+ return;
360
+ }
361
+
362
+ // Check OAuth tokens (Anthropic only)
363
+ if (provider === "anthropic") {
364
+ const oauthToken = await resolveOAuthToken();
365
+ if (oauthToken) {
366
+ options.oauthToken = oauthToken;
367
+ return;
368
+ }
369
+ }
370
+
371
+ // Interactive setup — no key found, prompt user
372
+ const stored2 = stored;
373
+ console.log();
374
+ console.log(chalk.bold.cyan(" Welcome to Cdoing Agent!"));
375
+ console.log(chalk.dim(" Let's set up authentication."));
376
+
377
+ // Always show provider selection — pre-select stored or current value
378
+ const providerOptions = [
379
+ { value: "anthropic", label: "Anthropic (Claude)", hint: "claude-sonnet-4-6, claude-opus-4-6" },
380
+ { value: "openai", label: "OpenAI (GPT)", hint: "gpt-4o, gpt-4o-mini" },
381
+ { value: "google", label: "Google (Gemini)", hint: "gemini-2.0-flash, gemini-1.5-pro" },
382
+ ];
383
+ const currentProvider = stored2.provider || options.provider || "anthropic";
384
+ const defaultProviderIdx = Math.max(0, providerOptions.findIndex(p => p.value === currentProvider));
385
+ const chosenProvider = await selectMenu("Choose a provider (↑↓ navigate · Enter select)", providerOptions, defaultProviderIdx);
386
+ options.provider = chosenProvider;
387
+
388
+ const provider2 = options.provider.toLowerCase();
389
+
390
+ // Re-check env/stored now that provider is confirmed
391
+ const envVar2 = getApiKeyEnvVar(provider2);
392
+ if (process.env[envVar2]) return;
393
+ if (stored2.apiKeys?.[provider2]) {
394
+ options.apiKey = stored2.apiKeys[provider2];
395
+ return;
396
+ }
397
+ if (provider2 === "anthropic") {
398
+ const oauthToken = await resolveOAuthToken();
399
+ if (oauthToken) { options.oauthToken = oauthToken; return; }
400
+ }
401
+
402
+ // Model selection
403
+ const modelOptions = PROVIDER_MODELS[provider2];
404
+ if (modelOptions && !options.model && !stored2.model) {
405
+ const chosenModel = await selectMenu("Choose a model (↑↓ navigate · Enter select)", modelOptions);
406
+ options.model = chosenModel;
407
+ }
408
+
409
+ const info = PROVIDER_INFO[provider2];
410
+
411
+ // Anthropic: offer API key or OAuth
412
+ if (provider2 === "anthropic") {
413
+ const authMethod = await selectMenu("Choose authentication method", [
414
+ { value: "apikey", label: "API key", hint: "from console.anthropic.com" },
415
+ { value: "oauth", label: "OAuth token", hint: "Claude Pro/Max · run: claude config get oauth_token" },
416
+ ]);
417
+
418
+ if (authMethod === "oauth") {
419
+ console.log();
420
+ console.log(chalk.dim(" To get your OAuth token:"));
421
+ console.log(chalk.dim(" 1. Install Claude Code: npm install -g @anthropic-ai/claude-code"));
422
+ console.log(chalk.dim(" 2. Login: claude login"));
423
+ console.log(chalk.dim(" 3. Get token: claude config get oauth_token"));
424
+ console.log();
425
+ const token = await ask(chalk.green(" Paste your OAuth token (sk-ant-oat01-...): "));
426
+ if (token && token.startsWith("sk-ant-")) {
427
+ const config = loadConfig();
428
+ config.apiKeys = config.apiKeys || {};
429
+ config.apiKeys[provider2] = token;
430
+ config.provider = provider2;
431
+ if (options.model) config.model = options.model;
432
+ saveConfig(config);
433
+ options.apiKey = token;
434
+ console.log(chalk.green("\n Token saved!\n"));
435
+ return;
436
+ }
437
+ console.log(chalk.yellow("\n That doesn't look like a Claude token. Falling back to API key.\n"));
438
+ }
439
+ }
440
+
441
+ // API key entry
442
+ if (info?.url) console.log(chalk.dim(`\n Get a key: ${info.url}\n`));
443
+
444
+ const apiKey = await ask(chalk.green(" Enter your API key: "));
445
+ if (!apiKey) {
446
+ console.log(chalk.red("\n No key provided. Exiting.\n"));
447
+ process.exit(1);
448
+ }
449
+
450
+ const save = await ask(chalk.green(" Save to ~/.cdoing/config.json? (Y/n): "));
451
+ if (save.toLowerCase() !== "n") {
452
+ const config = loadConfig();
453
+ config.apiKeys = config.apiKeys || {};
454
+ config.apiKeys[provider2] = apiKey;
455
+ config.provider = provider2;
456
+ if (options.model) config.model = options.model;
457
+ saveConfig(config);
458
+ console.log(chalk.green(" Saved!\n"));
459
+ }
460
+
461
+ options.apiKey = apiKey;
462
+ }
package/src/help.ts ADDED
@@ -0,0 +1,182 @@
1
+ import chalk from "chalk";
2
+ import figlet from "figlet";
3
+
4
+ const VERSION = "0.1.0";
5
+
6
+ const GRADIENT = ["#FF6B6B", "#FEC89A", "#FFD93D", "#6BCB77", "#4D96FF", "#9B5DE5"];
7
+
8
+ function gradient(text: string): string {
9
+ return text.split("").map((char, i) => {
10
+ const color = GRADIENT[Math.floor((i / text.length) * GRADIENT.length)];
11
+ return chalk.hex(color)(char);
12
+ }).join("");
13
+ }
14
+
15
+ export function printWelcome(): void {
16
+ const banner = figlet.textSync("Cdoing", {
17
+ font: "ANSI Shadow",
18
+ horizontalLayout: "default",
19
+ });
20
+
21
+ // Apply gradient color line-by-line
22
+ const lines = banner.split("\n");
23
+ console.log();
24
+ lines.forEach((line, i) => {
25
+ const color = GRADIENT[Math.floor((i / lines.length) * GRADIENT.length)];
26
+ console.log(" " + chalk.hex(color)(line));
27
+ });
28
+
29
+ console.log(
30
+ " " + chalk.hex("#9B5DE5").bold(`AI-Powered Coding Assistant`) +
31
+ chalk.hex("#78909C")(` v${VERSION}`)
32
+ );
33
+ console.log(
34
+ " " + chalk.hex("#546E7A")("by ") +
35
+ chalk.hex("#4FC3F7").bold("@awaisshah228") +
36
+ chalk.hex("#546E7A")(" · founder")
37
+ );
38
+ console.log();
39
+ console.log(chalk.hex("#90A4AE")(" 💬 Type a message and press ") + chalk.hex("#FFD93D")("Enter") + chalk.hex("#90A4AE")(" to chat"));
40
+ console.log(chalk.hex("#90A4AE")(" /help") + chalk.hex("#78909C")(" for commands · ") + chalk.hex("#90A4AE")("!cmd") + chalk.hex("#78909C")(" to run shell commands"));
41
+ console.log();
42
+ }
43
+
44
+ export function printHelp(): void {
45
+ console.log();
46
+
47
+ // Shortcuts section
48
+ console.log(chalk.hex("#FF6B6B").bold(" ⌨️ Shortcuts"));
49
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
50
+ console.log(chalk.hex("#FFD93D")(" ? ") + chalk.hex("#B0BEC5")("Show this help"));
51
+ console.log(chalk.hex("#FFD93D")(" ESC ") + chalk.hex("#B0BEC5")("Cancel current operation"));
52
+ console.log(chalk.hex("#FFD93D")(" Ctrl+C ") + chalk.hex("#B0BEC5")("Cancel / Exit"));
53
+ console.log();
54
+
55
+ // Commands section
56
+ console.log(chalk.hex("#6BCB77").bold(" 📝 Commands"));
57
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
58
+ const commands = [
59
+ ["/help", "Show this help"],
60
+ ["/new", "Start a new conversation"],
61
+ ["/history", "List saved conversations"],
62
+ ["/resume <id>", "Resume a saved conversation"],
63
+ ["/delete <id>", "Delete a saved conversation"],
64
+ ["/clear", "Clear current conversation"],
65
+ ["/config", "Show current configuration"],
66
+ ["/model <name>", "Switch model"],
67
+ ["/provider <name>", "Switch provider"],
68
+ ["/mode <mode>", "Change permission mode"],
69
+ ["/permissions", "View/clear stored permissions"],
70
+ ["/memory", "View/manage persistent memory"],
71
+ ["/hooks", "View configured hooks"],
72
+ ["/plan [request]", "Plan before executing"],
73
+ ["/effort <level>", "Set effort (low/med/high/max)"],
74
+ ["/btw <question>", "Ask without saving to history"],
75
+ ["/rules", "View project rules"],
76
+ ["/mcp", "MCP server status"],
77
+ ["/context", "List @ context providers"],
78
+ ["/usage", "Show token usage and cost"],
79
+ ["/compact", "Compress conversation context"],
80
+ ["/cost", "Show detailed cost breakdown"],
81
+ ["/tasks", "Show agent task list"],
82
+ ["/queue", "Show message queue"],
83
+ ["/doctor", "Check system health"],
84
+ ["/init", "Initialize project config"],
85
+ ["/dir <path>", "Change working directory"],
86
+ ["/exit", "Exit"],
87
+ ];
88
+ for (const [cmd, desc] of commands) {
89
+ console.log(chalk.hex("#4FC3F7")(` ${cmd.padEnd(20)}`) + chalk.hex("#B0BEC5")(desc));
90
+ }
91
+ console.log();
92
+
93
+ // Context providers section
94
+ console.log(chalk.hex("#E040FB").bold(" 📎 Context Providers"));
95
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
96
+ const contextProviders = [
97
+ ["@terminal", "Last terminal command output"],
98
+ ["@tree [path] [depth]", "Workspace file tree"],
99
+ ["@url <url>", "Fetch web page content"],
100
+ ["@codebase <query>", "Search entire codebase"],
101
+ ["@open", "All open files (VS Code only)"],
102
+ ["@problems", "File diagnostics (VS Code only)"],
103
+ ];
104
+ for (const [trigger, desc] of contextProviders) {
105
+ console.log(chalk.hex("#CE93D8")(` ${trigger.padEnd(24)}`) + chalk.hex("#B0BEC5")(desc));
106
+ }
107
+ console.log();
108
+
109
+ // Auth section
110
+ console.log(chalk.hex("#9B5DE5").bold(" 🔐 Authentication"));
111
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
112
+ console.log(chalk.hex("#BA68C8")(" /login ") + chalk.hex("#B0BEC5")("Show authentication options"));
113
+ console.log(chalk.hex("#BA68C8")(" /logout ") + chalk.hex("#B0BEC5")("Clear stored tokens"));
114
+ console.log(chalk.hex("#BA68C8")(" /auth-status ") + chalk.hex("#B0BEC5")("Show current auth status"));
115
+ console.log();
116
+
117
+ // CLI Usage section
118
+ console.log(chalk.hex("#4D96FF").bold(" 💻 CLI Usage"));
119
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
120
+ const cliExamples = [
121
+ ["cdoing", "Interactive mode"],
122
+ ["cdoing \"fix the bug\"", "One-shot prompt"],
123
+ ["cdoing --api-key sk-...", "Use API key directly"],
124
+ ["cdoing -p openai -m gpt-4o", "Use OpenAI"],
125
+ ["cdoing --mode auto", "Skip all permission prompts"],
126
+ ["cdoing -d ./my-project", "Set working directory"],
127
+ ];
128
+ for (const [cmd, desc] of cliExamples) {
129
+ console.log(chalk.hex("#64B5F6")(` ${cmd.padEnd(30)}`) + chalk.hex("#90A4AE")(desc));
130
+ }
131
+ console.log();
132
+
133
+ // Advanced Flags section
134
+ console.log(chalk.hex("#FFA726").bold(" ⚙️ Advanced Flags"));
135
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
136
+ const advancedFlags = [
137
+ ["--print \"prompt\"", "Non-interactive output"],
138
+ ["-r <id> \"prompt\"", "Resume conversation by ID"],
139
+ ["-c \"prompt\"", "Continue last conversation"],
140
+ ["--max-turns 5", "Limit agent iterations"],
141
+ ["--output-format json", "Output as JSON"],
142
+ ["--verbose", "Enable debug logging"],
143
+ ["--system-prompt \"...\"", "Custom system prompt"],
144
+ ["--allowed-tools a,b", "Whitelist tools"],
145
+ ["--disallowed-tools a,b", "Blacklist tools"],
146
+ ];
147
+ for (const [flag, desc] of advancedFlags) {
148
+ console.log(chalk.hex("#FFB74D")(` ${flag.padEnd(26)}`) + chalk.hex("#90A4AE")(desc));
149
+ }
150
+ console.log();
151
+
152
+ // Subcommands section
153
+ console.log(chalk.hex("#26A69A").bold(" 🔧 Subcommands"));
154
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
155
+ const subcommands = [
156
+ ["cdoing config list", "List config values"],
157
+ ["cdoing config get <key>", "Get config value"],
158
+ ["cdoing config set <k> <v>", "Set config value"],
159
+ ["cdoing init", "Initialize .cdoing/config.md"],
160
+ ["cdoing doctor", "Diagnose setup issues"],
161
+ ];
162
+ for (const [cmd, desc] of subcommands) {
163
+ console.log(chalk.hex("#4DB6AC")(` ${cmd.padEnd(26)}`) + chalk.hex("#90A4AE")(desc));
164
+ }
165
+ console.log();
166
+ }
167
+
168
+ export function printConfig(config: {
169
+ provider: string;
170
+ model: string;
171
+ mode: string;
172
+ dir: string;
173
+ }): void {
174
+ console.log();
175
+ console.log(chalk.hex("#4FC3F7").bold(" ⚙️ Current Configuration"));
176
+ console.log(chalk.hex("#78909C")(" ─────────────────────────────────────"));
177
+ console.log(chalk.hex("#81C784")(" Provider ") + chalk.hex("#78909C")("│ ") + chalk.hex("#FFFFFF")(config.provider));
178
+ console.log(chalk.hex("#FFB74D")(" Model ") + chalk.hex("#78909C")("│ ") + chalk.hex("#FFFFFF")(config.model || "(default)"));
179
+ console.log(chalk.hex("#BA68C8")(" Mode ") + chalk.hex("#78909C")("│ ") + chalk.hex("#FFFFFF")(config.mode));
180
+ console.log(chalk.hex("#4FC3F7")(" Directory ") + chalk.hex("#78909C")("│ ") + chalk.hex("#FFFFFF")(config.dir));
181
+ console.log();
182
+ }