@axplusb/kepler 0.0.1 → 1.0.1

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 (218) hide show
  1. package/README.md +82 -0
  2. package/package.json +36 -4
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/costs/route.ts +88 -0
  6. package/pulse/app/api/export/route.ts +77 -0
  7. package/pulse/app/api/history/route.ts +11 -0
  8. package/pulse/app/api/import/route.ts +31 -0
  9. package/pulse/app/api/memory/route.ts +52 -0
  10. package/pulse/app/api/plans/route.ts +9 -0
  11. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  12. package/pulse/app/api/projects/route.ts +121 -0
  13. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  14. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  15. package/pulse/app/api/sessions/route.ts +112 -0
  16. package/pulse/app/api/settings/route.ts +14 -0
  17. package/pulse/app/api/stats/route.ts +143 -0
  18. package/pulse/app/api/todos/route.ts +9 -0
  19. package/pulse/app/api/tools/route.ts +160 -0
  20. package/pulse/app/costs/page.tsx +179 -0
  21. package/pulse/app/export/page.tsx +465 -0
  22. package/pulse/app/favicon.ico +0 -0
  23. package/pulse/app/globals.css +263 -0
  24. package/pulse/app/help/page.tsx +142 -0
  25. package/pulse/app/history/page.tsx +157 -0
  26. package/pulse/app/layout.tsx +46 -0
  27. package/pulse/app/memory/page.tsx +365 -0
  28. package/pulse/app/overview-client.tsx +393 -0
  29. package/pulse/app/page.tsx +14 -0
  30. package/pulse/app/plans/page.tsx +308 -0
  31. package/pulse/app/projects/[slug]/page.tsx +390 -0
  32. package/pulse/app/projects/page.tsx +110 -0
  33. package/pulse/app/sessions/[id]/page.tsx +243 -0
  34. package/pulse/app/sessions/page.tsx +39 -0
  35. package/pulse/app/settings/page.tsx +188 -0
  36. package/pulse/app/todos/page.tsx +211 -0
  37. package/pulse/app/tools/page.tsx +249 -0
  38. package/pulse/cli.js +159 -0
  39. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  40. package/pulse/components/activity/streak-card.tsx +36 -0
  41. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  42. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  43. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  44. package/pulse/components/costs/model-token-table.tsx +60 -0
  45. package/pulse/components/global-search.tsx +193 -0
  46. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  47. package/pulse/components/layout/bottom-nav.tsx +52 -0
  48. package/pulse/components/layout/client-layout.tsx +31 -0
  49. package/pulse/components/layout/sidebar-context.tsx +50 -0
  50. package/pulse/components/layout/sidebar.tsx +182 -0
  51. package/pulse/components/layout/top-bar.tsx +121 -0
  52. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  53. package/pulse/components/overview/conversation-table.tsx +148 -0
  54. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  55. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  56. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  57. package/pulse/components/overview/stat-card.tsx +102 -0
  58. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  59. package/pulse/components/projects/project-card.tsx +175 -0
  60. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  61. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  62. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  63. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  64. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  65. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  66. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  67. package/pulse/components/sessions/session-badges.tsx +49 -0
  68. package/pulse/components/sessions/session-table.tsx +299 -0
  69. package/pulse/components/theme-provider.tsx +44 -0
  70. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  71. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  72. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  73. package/pulse/components/tools/version-history-table.tsx +32 -0
  74. package/pulse/components/ui/alert.tsx +66 -0
  75. package/pulse/components/ui/badge.tsx +48 -0
  76. package/pulse/components/ui/breadcrumb.tsx +109 -0
  77. package/pulse/components/ui/button.tsx +64 -0
  78. package/pulse/components/ui/calendar.tsx +220 -0
  79. package/pulse/components/ui/card.tsx +92 -0
  80. package/pulse/components/ui/command.tsx +158 -0
  81. package/pulse/components/ui/dialog.tsx +158 -0
  82. package/pulse/components/ui/input.tsx +21 -0
  83. package/pulse/components/ui/popover.tsx +89 -0
  84. package/pulse/components/ui/progress.tsx +31 -0
  85. package/pulse/components/ui/select.tsx +190 -0
  86. package/pulse/components/ui/separator.tsx +28 -0
  87. package/pulse/components/ui/sheet.tsx +143 -0
  88. package/pulse/components/ui/skeleton.tsx +13 -0
  89. package/pulse/components/ui/table.tsx +116 -0
  90. package/pulse/components/ui/tabs.tsx +91 -0
  91. package/pulse/components/ui/tooltip.tsx +57 -0
  92. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  93. package/pulse/components.json +23 -0
  94. package/pulse/eslint.config.mjs +18 -0
  95. package/pulse/lib/claude-reader.ts +594 -0
  96. package/pulse/lib/decode.ts +129 -0
  97. package/pulse/lib/pricing.ts +102 -0
  98. package/pulse/lib/replay-parser.ts +165 -0
  99. package/pulse/lib/tool-categories.ts +127 -0
  100. package/pulse/lib/utils.ts +6 -0
  101. package/pulse/next-env.d.ts +6 -0
  102. package/pulse/next.config.ts +16 -0
  103. package/pulse/package.json +45 -0
  104. package/pulse/postcss.config.mjs +7 -0
  105. package/pulse/public/activity.png +0 -0
  106. package/pulse/public/cc-lens.png +0 -0
  107. package/pulse/public/command-k.png +0 -0
  108. package/pulse/public/costs.png +0 -0
  109. package/pulse/public/dashboard-dark.png +0 -0
  110. package/pulse/public/dashboard-white.png +0 -0
  111. package/pulse/public/export.png +0 -0
  112. package/pulse/public/file.svg +1 -0
  113. package/pulse/public/globe.svg +1 -0
  114. package/pulse/public/next.svg +1 -0
  115. package/pulse/public/projects.png +0 -0
  116. package/pulse/public/session-chat.png +0 -0
  117. package/pulse/public/todos.png +0 -0
  118. package/pulse/public/tools.png +0 -0
  119. package/pulse/public/vercel.svg +1 -0
  120. package/pulse/public/window.svg +1 -0
  121. package/pulse/tsconfig.json +34 -0
  122. package/pulse/types/claude.ts +294 -0
  123. package/src/agents/loader.mjs +89 -0
  124. package/src/agents/parser.mjs +98 -0
  125. package/src/agents/teams.mjs +123 -0
  126. package/src/auth/oauth.mjs +220 -0
  127. package/src/auth/tarang-auth.mjs +277 -0
  128. package/src/config/cli-args.mjs +173 -0
  129. package/src/config/env.mjs +263 -0
  130. package/src/config/settings.mjs +132 -0
  131. package/src/context/ast-parser.mjs +298 -0
  132. package/src/context/bm25.mjs +85 -0
  133. package/src/context/retriever.mjs +270 -0
  134. package/src/context/skeleton.mjs +134 -0
  135. package/src/core/agent-loop.mjs +480 -0
  136. package/src/core/approval.mjs +273 -0
  137. package/src/core/backend-url.mjs +57 -0
  138. package/src/core/cache.mjs +105 -0
  139. package/src/core/callback-client.mjs +149 -0
  140. package/src/core/checkpoints.mjs +142 -0
  141. package/src/core/context-manager.mjs +198 -0
  142. package/src/core/headless.mjs +168 -0
  143. package/src/core/hooks-manager.mjs +87 -0
  144. package/src/core/jsonl-writer.mjs +351 -0
  145. package/src/core/local-agent.mjs +429 -0
  146. package/src/core/local-store.mjs +325 -0
  147. package/src/core/mode-selector.mjs +51 -0
  148. package/src/core/output-filter.mjs +177 -0
  149. package/src/core/paths.mjs +101 -0
  150. package/src/core/pricing.mjs +314 -0
  151. package/src/core/providers.mjs +219 -0
  152. package/src/core/rate-limiter.mjs +119 -0
  153. package/src/core/safety.mjs +200 -0
  154. package/src/core/scheduler.mjs +173 -0
  155. package/src/core/session-manager.mjs +317 -0
  156. package/src/core/session.mjs +143 -0
  157. package/src/core/settings-sync.mjs +85 -0
  158. package/src/core/stagnation.mjs +57 -0
  159. package/src/core/stream-client.mjs +367 -0
  160. package/src/core/streaming.mjs +182 -0
  161. package/src/core/system-prompt.mjs +135 -0
  162. package/src/core/tool-executor.mjs +725 -0
  163. package/src/hooks/engine.mjs +162 -0
  164. package/src/index.mjs +370 -0
  165. package/src/mcp/client.mjs +253 -0
  166. package/src/mcp/transport-shttp.mjs +130 -0
  167. package/src/mcp/transport-sse.mjs +131 -0
  168. package/src/mcp/transport-ws.mjs +134 -0
  169. package/src/permissions/checker.mjs +57 -0
  170. package/src/permissions/command-classifier.mjs +573 -0
  171. package/src/permissions/injection-check.mjs +60 -0
  172. package/src/permissions/path-check.mjs +102 -0
  173. package/src/permissions/prompt.mjs +73 -0
  174. package/src/permissions/sandbox.mjs +112 -0
  175. package/src/plugins/loader.mjs +138 -0
  176. package/src/skills/loader.mjs +147 -0
  177. package/src/skills/runner.mjs +55 -0
  178. package/src/telemetry/index.mjs +96 -0
  179. package/src/terminal/agents.mjs +177 -0
  180. package/src/terminal/analytics.mjs +292 -0
  181. package/src/terminal/ansi.mjs +421 -0
  182. package/src/terminal/main.mjs +150 -0
  183. package/src/terminal/repl.mjs +1484 -0
  184. package/src/terminal/tool-display.mjs +58 -0
  185. package/src/tools/agent.mjs +137 -0
  186. package/src/tools/ask-user.mjs +61 -0
  187. package/src/tools/bash.mjs +148 -0
  188. package/src/tools/cron-create.mjs +120 -0
  189. package/src/tools/cron-delete.mjs +49 -0
  190. package/src/tools/cron-list.mjs +37 -0
  191. package/src/tools/edit.mjs +82 -0
  192. package/src/tools/enter-worktree.mjs +69 -0
  193. package/src/tools/exit-worktree.mjs +57 -0
  194. package/src/tools/glob.mjs +117 -0
  195. package/src/tools/grep.mjs +129 -0
  196. package/src/tools/lint.mjs +71 -0
  197. package/src/tools/ls.mjs +58 -0
  198. package/src/tools/lsp.mjs +115 -0
  199. package/src/tools/multi-edit.mjs +94 -0
  200. package/src/tools/notebook-edit.mjs +96 -0
  201. package/src/tools/read-mcp-resource.mjs +57 -0
  202. package/src/tools/read.mjs +138 -0
  203. package/src/tools/registry.mjs +132 -0
  204. package/src/tools/remote-trigger.mjs +84 -0
  205. package/src/tools/send-message.mjs +64 -0
  206. package/src/tools/skill.mjs +52 -0
  207. package/src/tools/test-runner.mjs +49 -0
  208. package/src/tools/todo-write.mjs +68 -0
  209. package/src/tools/tool-search.mjs +77 -0
  210. package/src/tools/web-fetch.mjs +65 -0
  211. package/src/tools/web-search.mjs +89 -0
  212. package/src/tools/write.mjs +55 -0
  213. package/src/ui/banner.mjs +237 -0
  214. package/src/ui/commands.mjs +499 -0
  215. package/src/ui/formatter.mjs +379 -0
  216. package/src/ui/markdown.mjs +278 -0
  217. package/src/ui/slash-commands.mjs +258 -0
  218. package/index.js +0 -1
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Slash Commands — all 39 commands from Claude Code.
3
+ *
4
+ * Each command is a function(args, state) that returns a string response.
5
+ * Commands are invoked via /command-name in the REPL.
6
+ */
7
+
8
+ import { SessionManager } from '../core/session.mjs';
9
+ import { CheckpointManager } from '../core/checkpoints.mjs';
10
+ import { PromptCache } from '../core/cache.mjs';
11
+ import { readEnv, listEnvVars } from '../config/env.mjs';
12
+ import * as telemetry from '../telemetry/index.mjs';
13
+
14
+ const checkpoints = new CheckpointManager();
15
+ const promptCache = new PromptCache();
16
+ let sessionManager = null;
17
+
18
+ function getSession() {
19
+ if (!sessionManager) sessionManager = new SessionManager();
20
+ return sessionManager;
21
+ }
22
+
23
+ /**
24
+ * All slash commands.
25
+ */
26
+ export const COMMANDS = {
27
+ '/help': {
28
+ description: 'Show available commands',
29
+ handler(args, state) {
30
+ const lines = ['', 'Available commands:'];
31
+ for (const [name, cmd] of Object.entries(COMMANDS)) {
32
+ lines.push(` ${name.padEnd(20)} ${cmd.description}`);
33
+ }
34
+ lines.push('');
35
+ return lines.join('\n');
36
+ },
37
+ },
38
+
39
+ '/clear': {
40
+ description: 'Clear conversation history',
41
+ handler(args, state) {
42
+ state.messages.length = 0;
43
+ state.turnCount = 0;
44
+ return 'Conversation cleared.';
45
+ },
46
+ },
47
+
48
+ '/compact': {
49
+ description: 'Manually compact conversation context',
50
+ handler(args, state) {
51
+ const before = state.messages.length;
52
+ const beforeTokens = state._contextManager
53
+ ? state._contextManager.getTokenCount(state.messages)
54
+ : 0;
55
+
56
+ if (state._contextManager) {
57
+ state.messages = state._contextManager.compact(state.messages);
58
+ } else {
59
+ if (state.messages.length > 10) {
60
+ state.messages = state.messages.slice(-8);
61
+ }
62
+ }
63
+
64
+ const afterTokens = state._contextManager
65
+ ? state._contextManager.getTokenCount(state.messages)
66
+ : 0;
67
+
68
+ return `Compacted: ${before} -> ${state.messages.length} messages` +
69
+ (beforeTokens ? ` (~${beforeTokens} -> ~${afterTokens} tokens)` : '');
70
+ },
71
+ },
72
+
73
+ '/cost': {
74
+ description: 'Show token usage and estimated cost',
75
+ handler(args, state) {
76
+ const { input, output } = state.tokenUsage;
77
+ // Use model-appropriate pricing
78
+ const model = state.model || '';
79
+ let priceIn = 3, priceOut = 15; // Sonnet default
80
+ if (model.includes('haiku')) { priceIn = 0.25; priceOut = 1.25; }
81
+ if (model.includes('opus')) { priceIn = 15; priceOut = 75; }
82
+
83
+ const costIn = (input / 1_000_000) * priceIn;
84
+ const costOut = (output / 1_000_000) * priceOut;
85
+ const total = costIn + costOut;
86
+ return [
87
+ `Token usage: input=${input}, output=${output}`,
88
+ `Estimated cost: $${total.toFixed(4)} (in: $${costIn.toFixed(4)}, out: $${costOut.toFixed(4)})`,
89
+ `Model: ${state.model || 'default'}`,
90
+ `Turns: ${state.turnCount}`,
91
+ ].join('\n');
92
+ },
93
+ },
94
+
95
+ '/doctor': {
96
+ description: 'Check system health and configuration',
97
+ handler(args, state) {
98
+ const checks = [];
99
+ checks.push(`Node.js: ${process.version}`);
100
+ checks.push(`ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? 'set' : 'NOT SET'}`);
101
+ checks.push(`Model: ${state.model || 'default'}`);
102
+ checks.push(`Tools: ${state.tools?.list?.()?.length || 0}`);
103
+ checks.push(`Messages: ${state.messages.length}`);
104
+ checks.push(`CWD: ${process.cwd()}`);
105
+ checks.push(`Platform: ${process.platform}`);
106
+
107
+ // Check API connectivity
108
+ let apiStatus = 'unchecked';
109
+ if (process.env.ANTHROPIC_API_KEY) {
110
+ apiStatus = 'key present';
111
+ }
112
+ checks.push(`API: ${apiStatus}`);
113
+
114
+ // Check MCP servers
115
+ const mcpCount = state._mcpClients?.length || 0;
116
+ checks.push(`MCP servers: ${mcpCount}`);
117
+
118
+ return `System check:\n${checks.map(c => ` ${c}`).join('\n')}`;
119
+ },
120
+ },
121
+
122
+ '/fast': {
123
+ description: 'Toggle fast mode (uses faster, cheaper model)',
124
+ handler(args, state) {
125
+ if (state.model?.includes('haiku')) {
126
+ state.model = 'claude-sonnet-4-6';
127
+ return 'Fast mode OFF — using claude-sonnet-4-6';
128
+ }
129
+ state.model = 'claude-haiku-4-5';
130
+ return 'Fast mode ON — using claude-haiku-4-5';
131
+ },
132
+ },
133
+
134
+ '/model': {
135
+ description: 'Show or switch model',
136
+ handler(args, state) {
137
+ if (args) {
138
+ state.model = args;
139
+ return `Model switched to: ${args}`;
140
+ }
141
+ return `Current model: ${state.model || 'default'}`;
142
+ },
143
+ },
144
+
145
+ '/tokens': {
146
+ description: 'Show token usage and context size',
147
+ handler(args, state) {
148
+ const contextTokens = state._contextManager
149
+ ? state._contextManager.getTokenCount(state.messages)
150
+ : '?';
151
+ return [
152
+ `Input: ${state.tokenUsage.input}, Output: ${state.tokenUsage.output}`,
153
+ `Messages: ${state.messages.length}`,
154
+ `Context: ~${contextTokens} tokens`,
155
+ ].join('\n');
156
+ },
157
+ },
158
+
159
+ '/tools': {
160
+ description: 'List available tools',
161
+ handler(args, state) {
162
+ const tools = state.tools?.list?.() || [];
163
+ if (tools.length === 0) return 'No tools registered.';
164
+ const lines = tools.map(t => ` ${t.name.padEnd(20)} ${(t.description || '').slice(0, 55)}`);
165
+ return `Tools (${tools.length}):\n${lines.join('\n')}`;
166
+ },
167
+ },
168
+
169
+ '/quit': {
170
+ description: 'Exit the REPL',
171
+ handler() { return 'EXIT'; },
172
+ },
173
+
174
+ '/exit': {
175
+ description: 'Exit the REPL',
176
+ handler() { return 'EXIT'; },
177
+ },
178
+
179
+ '/bug': {
180
+ description: 'Report a bug',
181
+ handler() {
182
+ return 'Report bugs at: https://github.com/ruvnet/open-claude-code/issues';
183
+ },
184
+ },
185
+
186
+ '/review': {
187
+ description: 'Review recent changes',
188
+ handler(args, state) {
189
+ try {
190
+ const { execSync } = require('child_process');
191
+ const diff = execSync('git diff --stat HEAD~1 2>/dev/null || echo "No git history"', { encoding: 'utf-8' });
192
+ return `Recent changes:\n${diff}`;
193
+ } catch {
194
+ return 'Unable to review changes (not in a git repo or no history).';
195
+ }
196
+ },
197
+ },
198
+
199
+ '/init': {
200
+ description: 'Initialize Claude Code in current directory',
201
+ handler() {
202
+ const fs = require('fs');
203
+ const path = require('path');
204
+ const claudeDir = path.join(process.cwd(), '.claude');
205
+ fs.mkdirSync(claudeDir, { recursive: true });
206
+ const settingsFile = path.join(claudeDir, 'settings.json');
207
+ if (!fs.existsSync(settingsFile)) {
208
+ fs.writeFileSync(settingsFile, JSON.stringify({ permissions: {}, hooks: {} }, null, 2));
209
+ }
210
+ return `Initialized .claude/ in ${process.cwd()}`;
211
+ },
212
+ },
213
+
214
+ '/login': {
215
+ description: 'Set API key',
216
+ handler(args) {
217
+ if (args) {
218
+ process.env.ANTHROPIC_API_KEY = args;
219
+ return 'API key set.';
220
+ }
221
+ return 'Usage: /login <api-key>';
222
+ },
223
+ },
224
+
225
+ '/logout': {
226
+ description: 'Clear API key',
227
+ handler() {
228
+ delete process.env.ANTHROPIC_API_KEY;
229
+ return 'API key cleared.';
230
+ },
231
+ },
232
+
233
+ '/status': {
234
+ description: 'Show session status',
235
+ handler(args, state) {
236
+ const session = getSession();
237
+ const info = session.info();
238
+ return [
239
+ `Session: ${info.id}`,
240
+ `Project: ${info.projectDir}`,
241
+ `Started: ${info.startedAt}`,
242
+ `Model: ${state.model}`,
243
+ `Turns: ${state.turnCount}`,
244
+ `Messages: ${state.messages.length}`,
245
+ ].join('\n');
246
+ },
247
+ },
248
+
249
+ '/config': {
250
+ description: 'Show current configuration',
251
+ handler(args, state) {
252
+ const env = readEnv();
253
+ const lines = ['Configuration:'];
254
+ for (const [key, val] of Object.entries(env)) {
255
+ if (key.includes('KEY') || key.includes('TOKEN')) continue;
256
+ lines.push(` ${key}: ${val}`);
257
+ }
258
+ return lines.join('\n');
259
+ },
260
+ },
261
+
262
+ '/memory': {
263
+ description: 'Show conversation memory usage',
264
+ handler(args, state) {
265
+ const msgSize = JSON.stringify(state.messages).length;
266
+ const tokenEst = state._contextManager
267
+ ? state._contextManager.getTokenCount(state.messages)
268
+ : Math.ceil(msgSize / 4);
269
+ return `Memory: ${state.messages.length} messages, ~${(msgSize / 1024).toFixed(1)}KB, ~${tokenEst} tokens`;
270
+ },
271
+ },
272
+
273
+ '/forget': {
274
+ description: 'Remove last N messages',
275
+ handler(args, state) {
276
+ const n = parseInt(args) || 2;
277
+ const removed = state.messages.splice(-n, n);
278
+ return `Removed ${removed.length} messages.`;
279
+ },
280
+ },
281
+
282
+ '/effort': {
283
+ description: 'Set effort level (low/normal/high)',
284
+ handler(args, state) {
285
+ const levels = ['low', 'normal', 'high'];
286
+ if (args && levels.includes(args)) {
287
+ state._effortLevel = args;
288
+ return `Effort level set to: ${args}`;
289
+ }
290
+ return `Current effort: ${state._effortLevel || 'normal'}. Options: low, normal, high`;
291
+ },
292
+ },
293
+
294
+ '/think': {
295
+ description: 'Toggle extended thinking',
296
+ handler(args, state) {
297
+ state._thinking = !state._thinking;
298
+ return `Extended thinking: ${state._thinking ? 'ON' : 'OFF'}`;
299
+ },
300
+ },
301
+
302
+ '/plan': {
303
+ description: 'Enter plan mode (read-only)',
304
+ handler(args, state) {
305
+ state._planMode = !state._planMode;
306
+ return `Plan mode: ${state._planMode ? 'ON (read-only)' : 'OFF'}`;
307
+ },
308
+ },
309
+
310
+ '/vim': {
311
+ description: 'Toggle vim keybindings',
312
+ handler(args, state) {
313
+ state._vimMode = !state._vimMode;
314
+ return `Vim mode: ${state._vimMode ? 'ON' : 'OFF'}`;
315
+ },
316
+ },
317
+
318
+ '/terminal-setup': {
319
+ description: 'Show terminal setup info',
320
+ handler() {
321
+ return [
322
+ 'Terminal setup:',
323
+ ` TERM: ${process.env.TERM || 'unknown'}`,
324
+ ` COLUMNS: ${process.stdout.columns || 'unknown'}`,
325
+ ` ROWS: ${process.stdout.rows || 'unknown'}`,
326
+ ` Color: ${process.stdout.hasColors?.() ? 'yes' : 'unknown'}`,
327
+ ` Unicode: ${process.env.LANG?.includes('UTF') ? 'yes' : 'unknown'}`,
328
+ ].join('\n');
329
+ },
330
+ },
331
+
332
+ '/mcp': {
333
+ description: 'Show MCP server status',
334
+ handler(args, state) {
335
+ if (!state._mcpClients || state._mcpClients.length === 0) {
336
+ return 'No MCP servers connected.';
337
+ }
338
+ const lines = state._mcpClients.map((c, i) =>
339
+ ` ${i + 1}. ${c.config?.command || 'unknown'} — ${c.connected ? 'connected' : 'disconnected'}`
340
+ );
341
+ return `MCP servers:\n${lines.join('\n')}`;
342
+ },
343
+ },
344
+
345
+ '/permissions': {
346
+ description: 'Show permission mode',
347
+ handler(args, state) {
348
+ return `Permission mode: ${state._permissionMode || 'default'}`;
349
+ },
350
+ },
351
+
352
+ '/hooks': {
353
+ description: 'Show configured hooks',
354
+ handler(args, state) {
355
+ if (!state._hooks) return 'No hooks configured.';
356
+ const hooks = state._hooks;
357
+ const lines = [];
358
+ for (const [event, handlers] of Object.entries(hooks)) {
359
+ const arr = Array.isArray(handlers) ? handlers : [handlers];
360
+ lines.push(` ${event}: ${arr.length} handler(s)`);
361
+ }
362
+ return lines.length > 0 ? `Hooks:\n${lines.join('\n')}` : 'No hooks configured.';
363
+ },
364
+ },
365
+
366
+ '/agents': {
367
+ description: 'List custom agents',
368
+ handler(args, state) {
369
+ if (!state._agentLoader) return 'No agent loader initialized.';
370
+ const agents = state._agentLoader.list();
371
+ if (agents.length === 0) return 'No custom agents loaded.';
372
+ return `Agents:\n${agents.map(a => ` ${a.name}: ${a.description}`).join('\n')}`;
373
+ },
374
+ },
375
+
376
+ '/skills': {
377
+ description: 'List available skills',
378
+ handler(args, state) {
379
+ if (!state._skillsLoader) return 'No skills loaded.';
380
+ const skills = state._skillsLoader.list();
381
+ if (skills.length === 0) return 'No skills loaded.';
382
+ return `Skills:\n${skills.map(s => ` /${s.name}: ${s.description}`).join('\n')}`;
383
+ },
384
+ },
385
+
386
+ '/schedule': {
387
+ description: 'List scheduled tasks',
388
+ handler() {
389
+ const { cronStore } = require('../tools/cron-create.mjs');
390
+ if (!cronStore || cronStore.size === 0) return 'No scheduled tasks.';
391
+ const lines = [];
392
+ for (const [, job] of cronStore) {
393
+ lines.push(` ${job.id}: ${job.name} (${job.schedule})`);
394
+ }
395
+ return `Scheduled:\n${lines.join('\n')}`;
396
+ },
397
+ },
398
+
399
+ '/extra-usage': {
400
+ description: 'Show detailed usage stats',
401
+ handler(args, state) {
402
+ const cacheStats = promptCache.getStats();
403
+ const telemetryStats = telemetry.getStats();
404
+ return [
405
+ `Tokens: in=${state.tokenUsage.input}, out=${state.tokenUsage.output}`,
406
+ `Cache: hits=${cacheStats.cacheHits}, misses=${cacheStats.cacheMisses}, rate=${cacheStats.hitRate}`,
407
+ `Telemetry: ${telemetryStats.totalEvents} events`,
408
+ ].join('\n');
409
+ },
410
+ },
411
+
412
+ '/undo': {
413
+ description: 'Undo last file edit (restore checkpoint)',
414
+ handler() {
415
+ const result = checkpoints.undo();
416
+ if (!result) return 'No checkpoints to undo.';
417
+ if (result.restored) return `Restored: ${result.filePath}`;
418
+ return `Undo failed: ${result.error || 'unknown error'}`;
419
+ },
420
+ },
421
+
422
+ '/diff': {
423
+ description: 'Show git diff',
424
+ handler() {
425
+ try {
426
+ const { execSync } = require('child_process');
427
+ return execSync('git diff --stat 2>/dev/null || echo "Not in a git repo"', { encoding: 'utf-8' });
428
+ } catch {
429
+ return 'Unable to show diff.';
430
+ }
431
+ },
432
+ },
433
+
434
+ '/listen': {
435
+ description: 'Toggle listening mode (voice input stub)',
436
+ handler(args, state) {
437
+ state._listening = !state._listening;
438
+ return `Listening mode: ${state._listening ? 'ON (stub)' : 'OFF'}`;
439
+ },
440
+ },
441
+
442
+ '/commit': {
443
+ description: 'Create a git commit with AI message',
444
+ handler(args) {
445
+ try {
446
+ const { execSync } = require('child_process');
447
+ const msg = args || 'Update from open-claude-code';
448
+ execSync('git add -A', { encoding: 'utf-8' });
449
+ execSync(`git commit -m "${msg}"`, { encoding: 'utf-8' });
450
+ return `Committed: ${msg}`;
451
+ } catch (err) {
452
+ return `Commit failed: ${err.message}`;
453
+ }
454
+ },
455
+ },
456
+
457
+ '/pr': {
458
+ description: 'Create a pull request (stub)',
459
+ handler() {
460
+ return 'PR creation requires gh CLI. Run: gh pr create --fill';
461
+ },
462
+ },
463
+
464
+ '/release': {
465
+ description: 'Create a release (stub)',
466
+ handler() {
467
+ return 'Release creation requires gh CLI. Run: gh release create <tag>';
468
+ },
469
+ },
470
+ };
471
+
472
+ /**
473
+ * Execute a slash command.
474
+ * @param {string} input - full command string (e.g., "/model claude-sonnet-4-6")
475
+ * @param {object} state - agent loop state
476
+ * @returns {{ response: string, exit: boolean }}
477
+ */
478
+ export function executeCommand(input, state) {
479
+ const parts = input.split(/\s+/);
480
+ const cmd = parts[0].toLowerCase();
481
+ const args = parts.slice(1).join(' ');
482
+
483
+ const command = COMMANDS[cmd];
484
+ if (!command) {
485
+ return { response: `Unknown command: ${cmd}. Type /help for available commands.`, exit: false };
486
+ }
487
+
488
+ const response = command.handler(args, state);
489
+ return { response, exit: response === 'EXIT' };
490
+ }
491
+
492
+ /**
493
+ * Get command completions for tab-complete.
494
+ * @param {string} partial
495
+ * @returns {string[]}
496
+ */
497
+ export function getCompletions(partial) {
498
+ return Object.keys(COMMANDS).filter(c => c.startsWith(partial));
499
+ }