@bahulam/code 0.1.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 (278) hide show
  1. package/README.md +93 -0
  2. package/package.json +56 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +223 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +312 -0
  136. package/src/commands/agent.mjs +221 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +202 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/model-catalog.mjs +57 -0
  143. package/src/config/settings-loader.mjs +45 -0
  144. package/src/config/settings.mjs +132 -0
  145. package/src/context/ast-parser.mjs +298 -0
  146. package/src/context/bm25.mjs +85 -0
  147. package/src/context/prose-chunker.mjs +255 -0
  148. package/src/context/retriever.mjs +425 -0
  149. package/src/context/skeleton.mjs +134 -0
  150. package/src/context/symbol-indexer.mjs +375 -0
  151. package/src/core/agent-history.mjs +111 -0
  152. package/src/core/agent-loop.mjs +486 -0
  153. package/src/core/approval-log.mjs +145 -0
  154. package/src/core/approval.mjs +700 -0
  155. package/src/core/attachments.mjs +666 -0
  156. package/src/core/backend-url.mjs +68 -0
  157. package/src/core/bundled-runtime.mjs +418 -0
  158. package/src/core/cache-control.mjs +92 -0
  159. package/src/core/cache.mjs +105 -0
  160. package/src/core/callback-client.mjs +180 -0
  161. package/src/core/checkpoints.mjs +142 -0
  162. package/src/core/compact-history.mjs +127 -0
  163. package/src/core/context-envelope.mjs +54 -0
  164. package/src/core/context-manager.mjs +198 -0
  165. package/src/core/error-guidance.mjs +331 -0
  166. package/src/core/file-diff.mjs +217 -0
  167. package/src/core/headless.mjs +460 -0
  168. package/src/core/hooks-manager.mjs +87 -0
  169. package/src/core/jsonl-writer.mjs +449 -0
  170. package/src/core/local-agent.mjs +538 -0
  171. package/src/core/local-store.mjs +836 -0
  172. package/src/core/mode-selector.mjs +51 -0
  173. package/src/core/output-filter.mjs +177 -0
  174. package/src/core/paths.mjs +190 -0
  175. package/src/core/policy-resolver.mjs +156 -0
  176. package/src/core/pricing.mjs +336 -0
  177. package/src/core/project-artifacts.mjs +39 -0
  178. package/src/core/project-context-loader.mjs +139 -0
  179. package/src/core/providers.mjs +219 -0
  180. package/src/core/rate-limit-display.mjs +121 -0
  181. package/src/core/rate-limiter.mjs +119 -0
  182. package/src/core/resume-mode.mjs +192 -0
  183. package/src/core/risk-tier.mjs +388 -0
  184. package/src/core/safety.mjs +260 -0
  185. package/src/core/scheduler.mjs +173 -0
  186. package/src/core/session-manager.mjs +360 -0
  187. package/src/core/session.mjs +143 -0
  188. package/src/core/settings-sync.mjs +85 -0
  189. package/src/core/stagnation.mjs +57 -0
  190. package/src/core/stream-client.mjs +957 -0
  191. package/src/core/streaming.mjs +182 -0
  192. package/src/core/system-prompt.mjs +140 -0
  193. package/src/core/tasks.mjs +196 -0
  194. package/src/core/tool-executor.mjs +2231 -0
  195. package/src/core/trust.mjs +160 -0
  196. package/src/core/work-scope.mjs +248 -0
  197. package/src/hooks/engine.mjs +162 -0
  198. package/src/mcp/client.mjs +253 -0
  199. package/src/mcp/transport-shttp.mjs +130 -0
  200. package/src/mcp/transport-sse.mjs +131 -0
  201. package/src/mcp/transport-ws.mjs +134 -0
  202. package/src/onboarding/preflight.mjs +374 -0
  203. package/src/permissions/checker.mjs +57 -0
  204. package/src/permissions/command-classifier.mjs +700 -0
  205. package/src/permissions/injection-check.mjs +60 -0
  206. package/src/permissions/path-check.mjs +102 -0
  207. package/src/permissions/prompt.mjs +73 -0
  208. package/src/permissions/sandbox.mjs +112 -0
  209. package/src/plugins/loader.mjs +138 -0
  210. package/src/skills/installer.mjs +188 -0
  211. package/src/skills/loader.mjs +252 -0
  212. package/src/skills/runner.mjs +55 -0
  213. package/src/state/orbit.mjs +263 -0
  214. package/src/state/verbosity.mjs +99 -0
  215. package/src/telemetry/index.mjs +122 -0
  216. package/src/terminal/agents.mjs +353 -0
  217. package/src/terminal/analytics.mjs +292 -0
  218. package/src/terminal/ansi.mjs +695 -0
  219. package/src/terminal/init.mjs +145 -0
  220. package/src/terminal/main.mjs +310 -0
  221. package/src/terminal/repl-ask-form.mjs +120 -0
  222. package/src/terminal/repl-explore.mjs +44 -0
  223. package/src/terminal/repl-format.mjs +317 -0
  224. package/src/terminal/repl-model-form.mjs +132 -0
  225. package/src/terminal/repl-render.mjs +833 -0
  226. package/src/terminal/repl-resume.mjs +640 -0
  227. package/src/terminal/repl-state.mjs +120 -0
  228. package/src/terminal/repl-utils.mjs +34 -0
  229. package/src/terminal/repl.mjs +5032 -0
  230. package/src/terminal/skills.mjs +54 -0
  231. package/src/terminal/tool-display.mjs +392 -0
  232. package/src/tools/agent.mjs +137 -0
  233. package/src/tools/ask-user.mjs +61 -0
  234. package/src/tools/bash.mjs +231 -0
  235. package/src/tools/cron-create.mjs +120 -0
  236. package/src/tools/cron-delete.mjs +49 -0
  237. package/src/tools/cron-list.mjs +37 -0
  238. package/src/tools/edit.mjs +82 -0
  239. package/src/tools/enter-worktree.mjs +69 -0
  240. package/src/tools/exit-worktree.mjs +57 -0
  241. package/src/tools/glob.mjs +117 -0
  242. package/src/tools/grep.mjs +129 -0
  243. package/src/tools/lint.mjs +71 -0
  244. package/src/tools/ls.mjs +58 -0
  245. package/src/tools/lsp.mjs +115 -0
  246. package/src/tools/multi-edit.mjs +94 -0
  247. package/src/tools/notebook-edit.mjs +96 -0
  248. package/src/tools/project-overview.mjs +703 -0
  249. package/src/tools/read-mcp-resource.mjs +57 -0
  250. package/src/tools/read.mjs +138 -0
  251. package/src/tools/registry.mjs +116 -0
  252. package/src/tools/remote-trigger.mjs +84 -0
  253. package/src/tools/send-message.mjs +64 -0
  254. package/src/tools/skill.mjs +52 -0
  255. package/src/tools/test-runner.mjs +49 -0
  256. package/src/tools/todo-write.mjs +68 -0
  257. package/src/tools/tool-search.mjs +77 -0
  258. package/src/tools/web-fetch.mjs +65 -0
  259. package/src/tools/web-search.mjs +89 -0
  260. package/src/tools/write.mjs +55 -0
  261. package/src/ui/approval.mjs +510 -0
  262. package/src/ui/banner.mjs +232 -0
  263. package/src/ui/commands.mjs +537 -0
  264. package/src/ui/formatter.mjs +409 -0
  265. package/src/ui/icons.mjs +170 -0
  266. package/src/ui/input-dock.mjs +772 -0
  267. package/src/ui/markdown.mjs +278 -0
  268. package/src/ui/mission-report.mjs +296 -0
  269. package/src/ui/palette.mjs +189 -0
  270. package/src/ui/render-queue.mjs +500 -0
  271. package/src/ui/slash-commands.mjs +257 -0
  272. package/src/ui/spinner.mjs +116 -0
  273. package/src/ui/sub-agent.mjs +167 -0
  274. package/src/ui/term.mjs +174 -0
  275. package/src/ui/text-layout.mjs +127 -0
  276. package/src/ui/tool-card.mjs +740 -0
  277. package/src/ui/tool-details.mjs +504 -0
  278. package/src/ui/transcript-block.mjs +20 -0
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Slash Commands โ€” canonical catalog + input normalizer.
3
+ *
4
+ * This module is the ONE source of truth for the REPL's slash commands.
5
+ * Consumers:
6
+ * - src/terminal/repl.mjs imports COMMANDS, HELP_GROUPS, etc. for its
7
+ * runtime dispatch and slash-hint suggestions.
8
+ * - src/index.mjs iterates COMMANDS for `bahulam-code --help` output.
9
+ *
10
+ * The runtime *handlers* live in repl.mjs (they need session/ctx access
11
+ * that only the REPL has). This module is data-only + pure normalization.
12
+ */
13
+
14
+ // name -> one-line description (bare string).
15
+ export const COMMANDS = {
16
+ '/help': 'Show commands',
17
+ '/login': 'Sign in via browser',
18
+ '/whoami': 'Show logged-in user',
19
+ '/status': 'Session status & system info',
20
+ '/plan': 'Show plan/tasks',
21
+ '/tasks': 'Show or update project tasks',
22
+ '/stats': 'Progress bars & metrics',
23
+ '/new': 'Start a new session',
24
+ '/clear': 'Clear conversation',
25
+ '/git': 'Git status',
26
+ '/diff': 'Git diff',
27
+ '/cost': 'Show session cost',
28
+ '/model': 'Show or set session model overrides',
29
+ '/attach': 'Attach image path or clipboard image to next prompt',
30
+ '/attachments': 'List or clear pending image attachments',
31
+ '/history': 'Show conversation',
32
+ '/settings': 'Show policy/settings',
33
+ '/last': 'Expand last tool output',
34
+ '/expand': 'Expand tool output by index (or "all")',
35
+ '/fold': 'Hide previously expanded tool output',
36
+ '/checkpoint': 'List recent file checkpoints',
37
+ '/undo': 'Restore the last file checkpoint',
38
+ '/preflight': 'Re-run the onboarding diagnostic',
39
+ '/report': 'Save the mission report as markdown',
40
+ '/why': 'Print the agent reasoning for the last decision',
41
+ '/map': 'Show the registered project tree',
42
+ '/budget': 'Set / clear a hard session cost cap',
43
+ '/quiet': 'Verbosity: hide sub-agent inner tools',
44
+ '/verbose': 'Verbosity: show sub-agent inner tools',
45
+ '/surgical': 'Verbosity: show everything (reasoning, expanded tools)',
46
+ '/compact': 'Compact conversation context',
47
+ '/agents': 'List available agents',
48
+ '/run': 'Run a sub-agent or synced workflow',
49
+ '/explore': 'Code explorer agent',
50
+ '/review': 'Code review agent',
51
+ '/architect': 'Feature architect agent',
52
+ '/safety': 'Show safety guardrail status',
53
+ '/auto': 'Session autopilot: auto-approve routine tools (dangerous still prompts)',
54
+ '/approvals': 'List or edit session approval grants',
55
+ '/revoke': 'Revoke auto-approvals',
56
+ '/resume': 'Resume a previous session',
57
+ '/sessions': 'List resumable sessions',
58
+ '/logout': 'Sign out and clear credentials',
59
+ '/exit': 'Exit CLI',
60
+ };
61
+
62
+ // Grouped view for the `/help` interactive display. Each entry is
63
+ // [command-with-args, description] so subcommands appear inline.
64
+ export const HELP_GROUPS = [
65
+ {
66
+ key: 'plan',
67
+ title: 'Plan',
68
+ summary: 'plan and project tasks',
69
+ commands: [
70
+ ['/plan', 'Plan and task overview'],
71
+ ['/plan status', 'Plan owner and task files'],
72
+ ['/plan edit', 'Show editable task/plan paths'],
73
+ ['/tasks', 'List project tasks'],
74
+ ['/tasks add <text>', 'Add backlog task'],
75
+ ['/tasks active|blocked|done <text>', 'Append to a task list'],
76
+ ],
77
+ },
78
+ {
79
+ key: 'status',
80
+ title: 'Status',
81
+ summary: 'session, usage, budget',
82
+ commands: [
83
+ ['/status', 'Session snapshot'],
84
+ ['/status context', 'Loaded .bahulam context'],
85
+ ['/status metrics', 'Progress bars and runtime metrics'],
86
+ ['/status cost', 'Credits and message window'],
87
+ ['/model [role] [model]', 'Show or set session model override'],
88
+ ['/attach <image>', 'Attach image to the next prompt'],
89
+ ['/attach clipboard', 'Attach image currently copied to macOS/Windows clipboard'],
90
+ ['/attachments', 'List pending image attachments'],
91
+ ['/attachments clear', 'Clear pending image attachments'],
92
+ ['/status budget <amount|clear>', 'Set or clear session budget'],
93
+ ],
94
+ },
95
+ {
96
+ key: 'history',
97
+ title: 'History',
98
+ summary: 'transcript, reports, undo',
99
+ commands: [
100
+ ['/history', 'Recent transcript'],
101
+ ['/history approvals', 'Approval log'],
102
+ ['/history last', 'Expand last tool output'],
103
+ ['/history expand [n|all]', 'Expand tool output'],
104
+ ['/history checkpoint', 'List checkpoints'],
105
+ ['/history undo', 'Restore latest checkpoint'],
106
+ ['/history report', 'Save mission report'],
107
+ ],
108
+ },
109
+ {
110
+ key: 'settings',
111
+ title: 'Settings',
112
+ summary: 'auth, policy, verbosity',
113
+ commands: [
114
+ ['/settings policy', 'Effective project policy'],
115
+ ['/settings login', 'Sign in'],
116
+ ['/settings logout', 'Sign out'],
117
+ ['/settings whoami', 'Current user'],
118
+ ['/settings quiet|verbose|surgical', 'Verbosity'],
119
+ ['/settings revoke', 'Revoke auto-approvals'],
120
+ ],
121
+ },
122
+ {
123
+ key: 'worktree',
124
+ title: 'Worktree',
125
+ summary: 'git and files',
126
+ commands: [
127
+ ['/git', 'Git status'],
128
+ ['/diff', 'Git diff'],
129
+ ['/map', 'Registered project tree'],
130
+ ['/preflight', 'Onboarding diagnostic'],
131
+ ['/safety', 'Safety guardrail status'],
132
+ ],
133
+ },
134
+ {
135
+ key: 'agents',
136
+ title: 'Agents',
137
+ summary: 'specialist modes',
138
+ commands: [
139
+ ['/agents', 'List built-in and local agents'],
140
+ ['/agents create <name>', 'Create .bahulam/agents/<name>.yaml'],
141
+ ['/agents edit <name>', 'Open local agent YAML'],
142
+ ['/agents sync [name]', 'Sync all or one local agent to cloud'],
143
+ ['/run <agent> [instruction]', 'Run a local or built-in agent'],
144
+ ['/explore <instruction>', 'Explore code'],
145
+ ['/review <instruction>', 'Review code'],
146
+ ['/architect <instruction>', 'Design an approach'],
147
+ ],
148
+ },
149
+ {
150
+ key: 'workflows',
151
+ title: 'Workflows',
152
+ summary: 'saved automations',
153
+ commands: [
154
+ ['/run <workflow> [instruction]', 'Run a synced workflow if no agent matches'],
155
+ ],
156
+ },
157
+ {
158
+ key: 'session',
159
+ title: 'Session',
160
+ summary: 'resume and clear',
161
+ commands: [
162
+ ['/sessions', 'List resumable sessions'],
163
+ ['/resume [id]', 'Resume a session'],
164
+ ['/compact', 'Compact conversation context'],
165
+ ['/new', 'Start a fresh session'],
166
+ ['/clear', 'Clear conversation'],
167
+ ['/exit', 'Exit CLI'],
168
+ ],
169
+ },
170
+ ];
171
+
172
+ export const HELP_GROUP_ALIASES = new Map(
173
+ HELP_GROUPS.flatMap(group => [[group.key, group], [group.title.toLowerCase(), group]])
174
+ );
175
+
176
+ // Old flat commands that the user might still type. Map them to the new
177
+ // namespaced form so completion + help nudge them toward the current UX.
178
+ export const LEGACY_COMMAND_HINTS = {
179
+ '/stats': '/status metrics',
180
+ '/cost': '/status cost',
181
+ '/budget': '/status budget',
182
+ '/last': '/history last',
183
+ '/expand': '/history expand',
184
+ '/fold': '/history fold',
185
+ '/undo': '/history undo',
186
+ '/checkpoint': '/history checkpoint',
187
+ '/report': '/history report',
188
+ '/login': '/settings login',
189
+ '/logout': '/settings logout',
190
+ '/whoami': '/settings whoami',
191
+ '/quiet': '/settings quiet',
192
+ '/verbose': '/settings verbose',
193
+ '/surgical': '/settings surgical',
194
+ '/revoke': '/settings revoke',
195
+ };
196
+
197
+ // Namespaced subcommands. `/status metrics` -> `/stats`, etc.
198
+ export const NAMESPACED_COMMANDS = {
199
+ '/status': {
200
+ metrics: '/stats',
201
+ stats: '/stats',
202
+ cost: '/cost',
203
+ credits: '/cost',
204
+ budget: '/budget',
205
+ },
206
+ '/history': {
207
+ last: '/last',
208
+ expand: '/expand',
209
+ fold: '/fold',
210
+ undo: '/undo',
211
+ checkpoint: '/checkpoint',
212
+ checkpoints: '/checkpoint',
213
+ report: '/report',
214
+ },
215
+ '/settings': {
216
+ login: '/login',
217
+ logout: '/logout',
218
+ whoami: '/whoami',
219
+ quiet: '/quiet',
220
+ verbose: '/verbose',
221
+ surgical: '/surgical',
222
+ revoke: '/revoke',
223
+ },
224
+ };
225
+
226
+ /**
227
+ * Normalize a raw slash-command input like "/status metrics arg" into
228
+ * { cmd: '/stats', rest: 'arg', rawCmd, aliasTarget }, resolving legacy
229
+ * aliases and namespaced subcommands to their canonical form.
230
+ *
231
+ * - `rawCmd` the lower-cased first token as the user typed it
232
+ * - `cmd` the canonical command (namespaced hit if any, else rawCmd)
233
+ * - `rest` the remaining args joined with spaces
234
+ * - `aliasTarget` for legacy flat commands, the new namespaced form to
235
+ * suggest to the user (null when not aliased)
236
+ */
237
+ export function normalizeCommandInput(input) {
238
+ const parts = String(input || '').trim().split(/\s+/).filter(Boolean);
239
+ const rawCmd = (parts[0] || '').toLowerCase();
240
+ const restParts = parts.slice(1);
241
+ const sub = (restParts[0] || '').toLowerCase();
242
+ const namespaced = NAMESPACED_COMMANDS[rawCmd]?.[sub];
243
+ if (namespaced) {
244
+ return {
245
+ cmd: namespaced,
246
+ rest: restParts.slice(1).join(' '),
247
+ rawCmd,
248
+ aliasTarget: null,
249
+ };
250
+ }
251
+ return {
252
+ cmd: rawCmd,
253
+ rest: restParts.join(' '),
254
+ rawCmd,
255
+ aliasTarget: LEGACY_COMMAND_HINTS[rawCmd] || null,
256
+ };
257
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Single shared spinner โ€” Mission Control (PRD-055 ยง4.4).
3
+ *
4
+ * One spinner instance per process. The repl and status bar consume frames
5
+ * from the same source so the eye never sees two animations out of phase.
6
+ *
7
+ * Usage:
8
+ *
9
+ * const stop = startSpinner('Reading auth.py');
10
+ * await doWork();
11
+ * stop(); // clears the line and stops the timer
12
+ *
13
+ * Or, for a managed line that already exists (e.g. the status bar):
14
+ *
15
+ * const tick = spinnerFrame(); // current frame, advances on next call
16
+ *
17
+ * Behavior:
18
+ * - 120ms per frame.
19
+ * - Suppressed entirely when stdout is not a TTY or when KEPLER_PLAIN=1.
20
+ * The `start`/`stop` API is still safe to call (no-op).
21
+ * - Color follows the current orbit (orchestrated by the caller via the
22
+ * palette token). Default: brand.primary.
23
+ * - ASCII fallback (when no Unicode): 'plain dot rotation'.
24
+ */
25
+
26
+ import { paint } from './palette.mjs';
27
+ import { term } from './term.mjs';
28
+
29
+ // 8-step rotation. The PRD spec calls for โ—ฏ โ†’ โ—” โ†’ โ—‘ โ†’ โ—• โ†’ โ— โ†’ โ—• โ†’ โ—‘ โ†’ โ—”
30
+ // which yields a perceptual "breathing" cycle rather than a left-right spin.
31
+ const FRAMES_UTF = ['โ—ฏ', 'โ—”', 'โ—‘', 'โ—•', 'โ—', 'โ—•', 'โ—‘', 'โ—”'];
32
+ const FRAMES_ASCII = ['.', 'o', 'O', '@', 'O', 'o', '.', ' '];
33
+
34
+ const INTERVAL_MS = 120;
35
+
36
+ let _frame = 0;
37
+
38
+ /**
39
+ * Current spinner glyph. Advances the cursor each call.
40
+ * Honors capability detection automatically.
41
+ */
42
+ export function spinnerFrame(painter = paint.brand.primary) {
43
+ const frames = term().unicode ? FRAMES_UTF : FRAMES_ASCII;
44
+ const ch = frames[_frame % frames.length];
45
+ _frame = (_frame + 1) % frames.length;
46
+ return painter ? painter(ch) : ch;
47
+ }
48
+
49
+ /**
50
+ * Reset to frame 0 โ€” useful at the start of a new turn so consecutive
51
+ * tool calls do not inherit each other's phase.
52
+ */
53
+ export function resetSpinner() {
54
+ _frame = 0;
55
+ }
56
+
57
+ /**
58
+ * Start an inline spinner attached to `text`. Returns a stop function.
59
+ *
60
+ * The line is re-rendered in place using carriage return + erase, so the
61
+ * caller does not need to manage cursor state. If the terminal cannot
62
+ * render in place (non-TTY, dumb terminal, plain mode), the spinner becomes
63
+ * a single static line `"โ€ฆ text"` written once.
64
+ */
65
+ export function startSpinner(text, { stream = process.stderr, painter, color = 'brand.primary' } = {}) {
66
+ const t = term();
67
+ if (!t.isTTY || t.plain || !t.color) {
68
+ try { stream.write(`โ€ฆ ${text}\n`); } catch {}
69
+ return () => {};
70
+ }
71
+
72
+ const paintFn = painter || tokenPainter(color);
73
+ let stopped = false;
74
+
75
+ const render = () => {
76
+ if (stopped) return;
77
+ const glyph = spinnerFrame(paintFn);
78
+ try {
79
+ stream.write(`\r\x1b[2K${glyph} ${paint.text.dim(text)}`);
80
+ } catch {
81
+ // Stream closed mid-spin โ€” stop quietly.
82
+ stop();
83
+ }
84
+ };
85
+
86
+ render();
87
+ const handle = setInterval(render, INTERVAL_MS);
88
+
89
+ function stop() {
90
+ if (stopped) return;
91
+ stopped = true;
92
+ clearInterval(handle);
93
+ try {
94
+ stream.write('\r\x1b[2K');
95
+ } catch {}
96
+ }
97
+
98
+ return stop;
99
+ }
100
+
101
+ /**
102
+ * Look up a painter function from a dotted token name (e.g. 'brand.accent').
103
+ * Falls back to the identity painter when the token does not exist.
104
+ */
105
+ function tokenPainter(tokenPath) {
106
+ const [ns, name] = String(tokenPath || '').split('.');
107
+ const group = paint[ns];
108
+ if (group && typeof group[name] === 'function') return group[name];
109
+ return (s) => String(s ?? '');
110
+ }
111
+
112
+ /**
113
+ * Interval used by the shared spinner. Exposed for the status bar to
114
+ * synchronize its own re-paints with the spinner phase.
115
+ */
116
+ export const SPINNER_INTERVAL_MS = INTERVAL_MS;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Sub-agent block renderer โ€” Mission Control (PRD-055 ยง7).
3
+ *
4
+ * Renders the open/close pair for a sub-agent block, dimmed throughout so
5
+ * the primary agent reads bright by contrast. Inner tool cards are indented
6
+ * via the `subAgentIndent()` helper so they nest visually under the header.
7
+ *
8
+ * ๐Ÿ›ฐ๏ธ explore "JWT lifecycle" โ–ธ running
9
+ * ๐Ÿ”ญ Search code "expire" โ†’ 6 matches
10
+ * ๐Ÿ”ญ Read file auth.py L120-180 โ†’ 60 lines
11
+ * โ”” โœ… returned 3 files identified ยท $0.004 ยท 2.1s
12
+ *
13
+ * Maintains a depth stack so concurrent / nested sub-agents indent further
14
+ * and so callers can ask `inSubAgent()` / `depth()` without threading state.
15
+ *
16
+ * No I/O โ€” caller writes the returned strings to stderr. This keeps the
17
+ * module testable from a plain Node script.
18
+ */
19
+
20
+ import { paint } from './palette.mjs';
21
+ import { icons } from './icons.mjs';
22
+
23
+ const SUB_ICONS = {
24
+ explore: '๐Ÿ”ญ',
25
+ plan: '๐Ÿ“',
26
+ verify: 'โœ…',
27
+ debug: '๐Ÿชฒ',
28
+ refactor:'โ™ป๏ธ',
29
+ };
30
+
31
+ // โ”€โ”€ Active stack โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
32
+
33
+ const _stack = []; // [{ id, type, startedAt }]
34
+
35
+ /** How many sub-agents are currently open. */
36
+ export function depth() { return _stack.length; }
37
+ export function inSubAgent() { return _stack.length > 0; }
38
+
39
+ /**
40
+ * Indent string for a tool card line nested under N sub-agents.
41
+ * 5 cols per level matches the existing `' '` legacy indent.
42
+ */
43
+ export function subAgentIndent(extraDepth = 0) {
44
+ const d = _stack.length + extraDepth;
45
+ if (d <= 0) return ' ';
46
+ return ' '.repeat(2 + d * 3);
47
+ }
48
+
49
+ // โ”€โ”€ Render โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
50
+
51
+ /**
52
+ * Open a sub-agent block. Pushes onto the stack; returns the lines to print.
53
+ *
54
+ * @returns {string} ANSI-styled multi-line block (no trailing newline).
55
+ */
56
+ /**
57
+ * Reduce a sub-agent query to its human-readable core. Handoff envelopes
58
+ * ([User intent], ## Work Scope, Schema:, Active roots:) are machine
59
+ * context โ€” printing them flooded the transcript with 15+ lines per
60
+ * spawn. Full text stays available via /expand on the recorded card.
61
+ */
62
+ export function displayQuery(query, max = 140) {
63
+ let s = String(query || '');
64
+ const cut = s.search(/\n\s*(?:\[User intent\]|##\s*Work Scope|Schema:\s*kepler\.|Active roots:)/);
65
+ if (cut >= 0) s = s.slice(0, cut);
66
+ s = s.replace(/^\[Thoroughness:\s*[^\]]*\]\s*/i, '').replace(/\s+/g, ' ').trim();
67
+ return truncate(s, max);
68
+ }
69
+
70
+ export function renderSubAgentOpen({ id, type, query, parentDepth } = {}) {
71
+ const t = type || 'sub-agent';
72
+ const depthBefore = _stack.length;
73
+ _stack.push({ id: id || `${t}-${depthBefore}-${tag()}`, type: t, startedAt: Date.now() });
74
+
75
+ const indent = ' '.repeat(2 + depthBefore * 3);
76
+ const iconChar = SUB_ICONS[t] || icons.subAgent;
77
+ const shown = displayQuery(query);
78
+ const head = `${indent}${iconChar} ${paint.brand.data(t)} ${paint.text.dim(`"${shown}"`)}`;
79
+ const tag1 = paint.text.dim('โ–ธ running');
80
+
81
+ return shown
82
+ ? `\n${head} ${tag1}`
83
+ : `\n${indent}${iconChar} ${paint.brand.data(t)} ${tag1}`;
84
+ }
85
+
86
+ /**
87
+ * Close the most recent sub-agent block. Pops the stack; returns the close
88
+ * line with optional cost / token / duration attribution per PRD ยง7.3.
89
+ *
90
+ * โ”” โœ… returned 3 files identified ยท 1.2k tok ยท $0.004 ยท 2.1s
91
+ * โ”” โœ— explore agent failed
92
+ *
93
+ * Caller passes `success` (default true), `summary` ("returned N files"),
94
+ * and any of `{ costUsd, tokens, durationS, toolCalls, iterations }`.
95
+ */
96
+ export function renderSubAgentClose({
97
+ type,
98
+ success = true,
99
+ summary = '',
100
+ costUsd,
101
+ tokens,
102
+ durationS,
103
+ toolCalls,
104
+ iterations,
105
+ error,
106
+ } = {}) {
107
+ // Match-pop: if the type doesn't match the top of stack we still pop the
108
+ // top entry โ€” backends never emit interleaved open/close, so this is the
109
+ // safe behavior.
110
+ const opened = _stack.pop();
111
+ const t = type || opened?.type || 'sub-agent';
112
+ const indent = ' '.repeat(2 + _stack.length * 3);
113
+
114
+ if (!success) {
115
+ const line = `${indent}${paint.text.dim('โ””')} ${paint.state.danger('โœ—')} ${paint.text.dim(`${t} agent failed`)}`;
116
+ if (error) {
117
+ return `${line}\n${indent} ${paint.state.danger(truncate(error, 140))}`;
118
+ }
119
+ return line;
120
+ }
121
+
122
+ const parts = [];
123
+ if (toolCalls > 0) parts.push(`${toolCalls} tools`);
124
+ if (iterations > 0) parts.push(`${iterations} iter`);
125
+ if (tokens > 0) parts.push(`${formatTokens(tokens)} tok`);
126
+ if (typeof costUsd === 'number' && costUsd > 0) parts.push(formatCost(costUsd));
127
+ if (durationS != null) parts.push(`${Number(durationS).toFixed(1)}s`);
128
+ const detail = parts.length ? paint.text.dim(' ยท ' + parts.join(' ยท ')) : '';
129
+
130
+ const body = summary
131
+ ? paint.text.dim(summary)
132
+ : paint.text.dim(`${t} returned`);
133
+
134
+ return `${indent}${paint.text.dim('โ””')} ${paint.state.success('โœ…')} ${body}${detail}`;
135
+ }
136
+
137
+ /**
138
+ * Force-clear the stack. Use after a `complete` event or when cancelling so
139
+ * a stale entry doesn't keep indenting future output.
140
+ */
141
+ export function resetSubAgents() { _stack.length = 0; }
142
+
143
+ // โ”€โ”€ helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
144
+
145
+ function truncate(text, n) {
146
+ const s = String(text || '');
147
+ return s.length <= n ? s : s.slice(0, n - 1) + 'โ€ฆ';
148
+ }
149
+
150
+ function formatTokens(n) {
151
+ if (!Number.isFinite(n)) return '0';
152
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
153
+ return String(Math.round(n));
154
+ }
155
+
156
+ function formatCost(usd) {
157
+ if (usd < 0.001) return `$${usd.toFixed(5)}`;
158
+ if (usd < 0.01) return `$${usd.toFixed(4)}`;
159
+ return `$${usd.toFixed(3)}`;
160
+ }
161
+
162
+ function tag() {
163
+ // Avoid Date.now()/Math.random() drift across re-renders โ€” depth+counter is
164
+ // enough to keep ids unique within a process.
165
+ tag._n = (tag._n || 0) + 1;
166
+ return tag._n.toString(36);
167
+ }