@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,294 @@
1
+ /**
2
+ * Agent Callbacks — formatted console output for interactive and one-shot modes.
3
+ * Includes token usage display and basic markdown rendering.
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import type { Ora } from "ora";
8
+ import type { AgentCallbacks } from "@cdoing/ai";
9
+ import type { TurnUsage } from "@cdoing/ai";
10
+
11
+ // Tool categories with icons and colors
12
+ const TOOL_STYLES: Record<string, { icon: string; color: (s: string) => string }> = {
13
+ // File operations
14
+ file_read: { icon: "📖", color: chalk.hex("#4FC3F7") },
15
+ file_write: { icon: "✏️ ", color: chalk.hex("#81C784") },
16
+ file_edit: { icon: "🔧", color: chalk.hex("#FFB74D") },
17
+ // Search
18
+ glob_search: { icon: "🔍", color: chalk.hex("#BA68C8") },
19
+ grep_search: { icon: "🔎", color: chalk.hex("#9575CD") },
20
+ // Execution
21
+ shell_exec: { icon: "💻", color: chalk.hex("#4DD0E1") },
22
+ file_run: { icon: "▶️ ", color: chalk.hex("#4DB6AC") },
23
+ code_verify: { icon: "✅", color: chalk.hex("#AED581") },
24
+ // Web
25
+ web_fetch: { icon: "🌐", color: chalk.hex("#64B5F6") },
26
+ web_search: { icon: "🔮", color: chalk.hex("#7986CB") },
27
+ // Agent
28
+ sub_agent: { icon: "🤖", color: chalk.hex("#F06292") },
29
+ // Tasks
30
+ todo: { icon: "📋", color: chalk.hex("#FF8A65") },
31
+ };
32
+
33
+ /** Get tool style or default */
34
+ function getToolStyle(name: string): { icon: string; color: (s: string) => string } {
35
+ return TOOL_STYLES[name] || { icon: "⚡", color: chalk.hex("#FFC107") };
36
+ }
37
+
38
+ /** Basic terminal markdown rendering */
39
+ function renderMarkdown(text: string): string {
40
+ let result = text;
41
+
42
+ // Code blocks with language (```lang\n...\n```)
43
+ result = result.replace(/```(\w+)?\n([\s\S]*?)```/g, (_match, lang, code) => {
44
+ const langColor = chalk.hex("#82AAFF");
45
+ const header = lang ? chalk.dim(" ┌─ ") + langColor(lang) + chalk.dim(" ─┐") + "\n" : "";
46
+ const formatted = code
47
+ .split("\n")
48
+ .map((line: string) => chalk.hex("#C3E88D")(` │ ${line}`))
49
+ .join("\n");
50
+ return `\n${header}${formatted}\n` + chalk.dim(" └────────┘\n");
51
+ });
52
+
53
+ // Inline code
54
+ result = result.replace(/`([^`]+)`/g, (_m: string, code: string) =>
55
+ chalk.bgHex("#2D2D2D").hex("#FFD54F")(` ${code} `)
56
+ );
57
+
58
+ // Bold
59
+ result = result.replace(/\*\*([^*]+)\*\*/g, (_m: string, t: string) => chalk.bold.hex("#FFFFFF")(t));
60
+
61
+ // Italic
62
+ result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_m: string, t: string) => chalk.italic.hex("#B0BEC5")(t));
63
+
64
+ // Headers with gradient effect
65
+ result = result.replace(/^### (.+)$/gm, (_m: string, t: string) =>
66
+ chalk.hex("#4FC3F7")(" ▸ ") + chalk.bold.hex("#4FC3F7")(t)
67
+ );
68
+ result = result.replace(/^## (.+)$/gm, (_m: string, t: string) =>
69
+ chalk.hex("#29B6F6")(" ▸▸ ") + chalk.bold.hex("#29B6F6")(t)
70
+ );
71
+ result = result.replace(/^# (.+)$/gm, (_m: string, t: string) =>
72
+ chalk.hex("#03A9F4")(" ▸▸▸ ") + chalk.bold.hex("#03A9F4")(t)
73
+ );
74
+
75
+ // Bullet lists with colorful bullets
76
+ result = result.replace(/^(\s*)[-*] (.+)$/gm, (_m: string, indent: string, t: string) =>
77
+ `${indent} ${chalk.hex("#FF7043")("●")} ${t}`
78
+ );
79
+
80
+ // Numbered lists
81
+ result = result.replace(/^(\s*)(\d+)\. (.+)$/gm, (_m: string, indent: string, n: string, t: string) =>
82
+ `${indent} ${chalk.hex("#AB47BC")(n + ".")} ${t}`
83
+ );
84
+
85
+ // Horizontal rules
86
+ result = result.replace(/^---+$/gm, chalk.hex("#546E7A")(" ═══════════════════════════════"));
87
+
88
+ return result;
89
+ }
90
+
91
+ /** Format token usage for display with colors */
92
+ function formatUsage(usage: TurnUsage): string {
93
+ const parts: string[] = [];
94
+ if (usage.inputTokens > 0 || usage.outputTokens > 0) {
95
+ parts.push(
96
+ chalk.hex("#4FC3F7")(usage.inputTokens.toLocaleString()) +
97
+ chalk.hex("#78909C")(" → ") +
98
+ chalk.hex("#81C784")(usage.outputTokens.toLocaleString())
99
+ );
100
+ }
101
+ parts.push(chalk.hex("#B0BEC5")(`${usage.totalTokens.toLocaleString()} tokens`));
102
+ if (usage.cost !== undefined) {
103
+ parts.push(chalk.hex("#FFD54F")(`$${usage.cost.toFixed(4)}`));
104
+ }
105
+ return parts.join(chalk.hex("#546E7A")(" · "));
106
+ }
107
+
108
+ /** Interactive mode: spinner + colors + markdown */
109
+ export function createInteractiveCallbacks(spinner: Ora): AgentCallbacks {
110
+ let isFirstToken = true;
111
+ let buffer = "";
112
+
113
+ return {
114
+ onToken: (token) => {
115
+ if (isFirstToken) {
116
+ spinner.stop();
117
+ process.stdout.write(chalk.hex("#E0E0E0")(" "));
118
+ isFirstToken = false;
119
+ }
120
+ // Buffer tokens and render markdown on newlines
121
+ buffer += token;
122
+ const lines = buffer.split("\n");
123
+ // Write all complete lines with markdown rendering
124
+ for (let i = 0; i < lines.length - 1; i++) {
125
+ const rendered = renderMarkdown(lines[i]);
126
+ process.stdout.write(rendered + "\n");
127
+ }
128
+ // Keep the last incomplete line in the buffer
129
+ buffer = lines[lines.length - 1];
130
+ },
131
+
132
+ onToolCall: (name, input) => {
133
+ // Flush buffer
134
+ if (buffer) {
135
+ process.stdout.write(renderMarkdown(buffer));
136
+ buffer = "";
137
+ }
138
+ spinner.stop();
139
+
140
+ const style = getToolStyle(name);
141
+
142
+ // Special formatting for todo tool
143
+ if (name === "todo") {
144
+ const action = (input as Record<string, unknown>).action;
145
+ const subject = (input as Record<string, unknown>).subject;
146
+ const status = (input as Record<string, unknown>).status;
147
+ const id = (input as Record<string, unknown>).id;
148
+
149
+ if (action === "create" && subject) {
150
+ console.log(chalk.hex("#FF8A65")(`\n 📋 Creating task: `) + chalk.bold.white(String(subject)));
151
+ } else if (action === "update" && id && status) {
152
+ const statusStyle = status === "completed"
153
+ ? { icon: "✅", color: chalk.hex("#81C784") }
154
+ : status === "in_progress"
155
+ ? { icon: "🔄", color: chalk.hex("#FFB74D") }
156
+ : { icon: "📌", color: chalk.hex("#90A4AE") };
157
+ console.log(statusStyle.color(`\n ${statusStyle.icon} Task #${id}: `) + chalk.bold.white(String(status)));
158
+ } else if (action === "list") {
159
+ console.log(chalk.hex("#FF8A65")(`\n 📋 Listing tasks`));
160
+ } else {
161
+ const preview = JSON.stringify(input).substring(0, 60);
162
+ console.log(`\n ${style.icon} ` + style.color(name) + chalk.hex("#78909C")(` ${preview}`));
163
+ }
164
+ } else {
165
+ // Colorful tool call display
166
+ const preview = formatToolPreview(name, input);
167
+ console.log(`\n ${style.icon} ` + style.color(name) + chalk.hex("#78909C")(` ${preview}`));
168
+ }
169
+ },
170
+
171
+ onToolResult: (name, result, isError) => {
172
+ const style = getToolStyle(name);
173
+
174
+ if (isError) {
175
+ console.log(chalk.hex("#EF5350")(` ✗ `) + chalk.hex("#EF5350").dim(name) + chalk.hex("#EF5350")(" failed"));
176
+ } else if (name === "todo") {
177
+ console.log(chalk.hex("#81C784")(` ✓ `) + chalk.hex("#A5D6A7")(result.split("\n")[0]));
178
+ } else {
179
+ const preview = result.substring(0, 100).replace(/\n/g, " ");
180
+ console.log(chalk.hex("#81C784")(` ✓ `) + style.color(name) + chalk.hex("#90A4AE")(` ${preview}`));
181
+ }
182
+ spinner.start(chalk.hex("#B0BEC5")(" 🧠 Thinking..."));
183
+ isFirstToken = true;
184
+ },
185
+
186
+ onComplete: () => {
187
+ // Flush remaining buffer
188
+ if (buffer) {
189
+ process.stdout.write(renderMarkdown(buffer));
190
+ buffer = "";
191
+ }
192
+ spinner.stop();
193
+ console.log("\n");
194
+ },
195
+
196
+ onError: (error) => {
197
+ if (buffer) {
198
+ process.stdout.write(renderMarkdown(buffer));
199
+ buffer = "";
200
+ }
201
+ spinner.stop();
202
+ console.log(chalk.hex("#EF5350")(`\n ❌ Error: `) + chalk.hex("#FFCDD2")(error.message) + "\n");
203
+ },
204
+
205
+ onUsage: (usage) => {
206
+ // Show usage with nice formatting
207
+ const formatted = formatUsage(usage);
208
+ console.log(chalk.hex("#546E7A")(` ╭─ `) + formatted + chalk.hex("#546E7A")(` ─╮`));
209
+ },
210
+ };
211
+ }
212
+
213
+ /** Format tool input preview based on tool type */
214
+ function formatToolPreview(name: string, input: Record<string, unknown>): string {
215
+ switch (name) {
216
+ case "file_read":
217
+ case "file_write":
218
+ case "file_edit":
219
+ return String(input.path || input.file_path || "").split("/").pop() || "";
220
+ case "shell_exec":
221
+ const cmd = String(input.command || "").substring(0, 50);
222
+ return cmd.length < String(input.command || "").length ? cmd + "..." : cmd;
223
+ case "glob_search":
224
+ return String(input.pattern || "");
225
+ case "grep_search":
226
+ return `"${String(input.pattern || "").substring(0, 30)}"`;
227
+ case "web_fetch":
228
+ case "web_search":
229
+ return String(input.url || input.query || "").substring(0, 40);
230
+ default:
231
+ return JSON.stringify(input).substring(0, 60);
232
+ }
233
+ }
234
+
235
+ /** One-shot mode: colorful minimal output */
236
+ export function createOneShotCallbacks(): AgentCallbacks {
237
+ return {
238
+ onToken: (token) => process.stdout.write(token),
239
+ onToolCall: (name) => {
240
+ const style = getToolStyle(name);
241
+ console.log(`\n${style.icon} ` + style.color(name));
242
+ },
243
+ onToolResult: (name, _r, isErr) => {
244
+ if (isErr) {
245
+ console.log(chalk.hex("#EF5350")(`✗ ${name} failed`));
246
+ } else {
247
+ const style = getToolStyle(name);
248
+ console.log(chalk.hex("#81C784")(`✓ `) + style.color(name));
249
+ }
250
+ },
251
+ onComplete: () => console.log(),
252
+ onError: (err) => console.error(chalk.hex("#EF5350")(`❌ Error: `) + chalk.hex("#FFCDD2")(err.message)),
253
+ onUsage: (usage) => {
254
+ console.error(chalk.hex("#546E7A")(`╭─ `) + formatUsage(usage) + chalk.hex("#546E7A")(` ─╮`));
255
+ },
256
+ };
257
+ }
258
+
259
+ /** Print mode: simple text output (for --print flag) */
260
+ export function createPrintCallbacks(): AgentCallbacks {
261
+ return {
262
+ onToken: (token) => process.stdout.write(token),
263
+ onToolCall: () => {},
264
+ onToolResult: () => {},
265
+ onComplete: () => console.log(),
266
+ onError: (e) => console.error(e.message),
267
+ };
268
+ }
269
+
270
+ /** JSON mode: structured JSON output (for --output-format json) */
271
+ export function createJsonCallbacks(): AgentCallbacks {
272
+ const result: { response: string; tools: Array<{ name: string; input: Record<string, unknown> }> } = {
273
+ response: "",
274
+ tools: [],
275
+ };
276
+ return {
277
+ onToken: (token) => { result.response += token; },
278
+ onToolCall: (name, input) => { result.tools.push({ name, input }); },
279
+ onToolResult: () => {},
280
+ onComplete: () => console.log(JSON.stringify(result, null, 2)),
281
+ onError: (e) => console.error(JSON.stringify({ error: e.message })),
282
+ };
283
+ }
284
+
285
+ /** Stream JSON mode: line-delimited JSON events (for --output-format stream-json) */
286
+ export function createStreamJsonCallbacks(): AgentCallbacks {
287
+ return {
288
+ onToken: (token) => console.log(JSON.stringify({ type: "token", data: token })),
289
+ onToolCall: (name, input) => console.log(JSON.stringify({ type: "tool_call", name, input })),
290
+ onToolResult: (name, result) => console.log(JSON.stringify({ type: "tool_result", name, result })),
291
+ onComplete: () => console.log(JSON.stringify({ type: "complete" })),
292
+ onError: (e) => console.log(JSON.stringify({ type: "error", message: e.message })),
293
+ };
294
+ }
package/src/chat.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * ChatInterface — launches the Ink-based TUI.
3
+ *
4
+ * This replaces the raw readline REPL with a React/Ink component tree,
5
+ * modelled on how Continue (continuedev/continue) builds its CLI.
6
+ *
7
+ * Structure:
8
+ * App.tsx — root Ink component
9
+ * MessageList — committed messages (via <Static>)
10
+ * StreamingMessage — live token stream
11
+ * UserInput — keyboard input + slash-command suggestions
12
+ * StatusBar — provider / model / mode / cost
13
+ * hooks/useChat — all agent state + slash-command logic
14
+ */
15
+
16
+ import React from "react";
17
+ import { render } from "ink";
18
+ import type { ModelConfig } from "@cdoing/ai";
19
+ import type {
20
+ ToolRegistry,
21
+ PermissionManager,
22
+ HookManager,
23
+ MemoryStore,
24
+ TodoStore,
25
+ } from "@cdoing/core";
26
+ import { App } from "./ui/App";
27
+ import { printWelcome } from "./help";
28
+ import { initTheme } from "./ui/theme";
29
+
30
+ export class ChatInterface {
31
+ private modelConfig: Partial<ModelConfig>;
32
+ private toolRegistry: ToolRegistry;
33
+ private permissionManager: PermissionManager;
34
+ private hookManager: HookManager;
35
+ private memoryStore: MemoryStore;
36
+ private todoStore: TodoStore | null;
37
+
38
+ constructor(
39
+ modelConfig: Partial<ModelConfig>,
40
+ toolRegistry: ToolRegistry,
41
+ permissionManager: PermissionManager,
42
+ hookManager: HookManager,
43
+ memoryStore: MemoryStore,
44
+ todoStore?: TodoStore,
45
+ ) {
46
+ this.modelConfig = modelConfig;
47
+ this.toolRegistry = toolRegistry;
48
+ this.permissionManager = permissionManager;
49
+ this.hookManager = hookManager;
50
+ this.memoryStore = memoryStore;
51
+ this.todoStore = todoStore || null;
52
+ }
53
+
54
+ async start(initialPrompt?: string): Promise<void> {
55
+ initTheme();
56
+ printWelcome();
57
+
58
+ const { waitUntilExit } = render(
59
+ React.createElement(App, {
60
+ modelConfig: this.modelConfig,
61
+ toolRegistry: this.toolRegistry,
62
+ permissionManager: this.permissionManager,
63
+ hookManager: this.hookManager,
64
+ memoryStore: this.memoryStore,
65
+ todoStore: this.todoStore || undefined,
66
+ initialPrompt,
67
+ }),
68
+ );
69
+
70
+ await waitUntilExit();
71
+ }
72
+ }