@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,54 @@
1
+ import { SkillInstaller } from '../skills/installer.mjs';
2
+ import { SkillsLoader } from '../skills/loader.mjs';
3
+
4
+ function has(args, flag) {
5
+ return args.includes(flag);
6
+ }
7
+
8
+ function scopeFrom(args) {
9
+ return has(args, '--project') ? 'project' : 'global';
10
+ }
11
+
12
+ function print(value) {
13
+ process.stdout.write(typeof value === 'string' ? `${value}\n` : `${JSON.stringify(value, null, 2)}\n`);
14
+ }
15
+
16
+ export async function runSkillsCommand(args, { cwd = process.cwd() } = {}) {
17
+ const action = args[0] || 'list';
18
+ const rest = args.slice(1);
19
+ const scope = scopeFrom(rest);
20
+ const installer = new SkillInstaller({ cwd });
21
+ const loader = new SkillsLoader().load(cwd);
22
+
23
+ if (action === 'list') {
24
+ const rows = loader.list({ scope: has(rest, '--all') ? '' : scope });
25
+ if (has(rest, '--json')) print(rows);
26
+ else if (!rows.length) print('No skills found.');
27
+ else for (const row of rows) print(`${row.name}\t${row.scope}\t${row.source}\t${row.description}`);
28
+ return;
29
+ }
30
+ if (action === 'view') {
31
+ const name = rest.find(arg => !arg.startsWith('--'));
32
+ if (!name) throw new Error('Usage: bahulam skills view <name> [resource-path]');
33
+ const nameIndex = rest.indexOf(name);
34
+ const resource = rest.slice(nameIndex + 1).find(arg => !arg.startsWith('--')) || null;
35
+ print(loader.view(name, resource));
36
+ return;
37
+ }
38
+ if (action === 'install') {
39
+ const source = rest.find(arg => !arg.startsWith('--'));
40
+ print(installer.install(source, { scope, force: has(rest, '--force') }));
41
+ return;
42
+ }
43
+ if (action === 'remove') {
44
+ const name = rest.find(arg => !arg.startsWith('--'));
45
+ print(installer.remove(name, { scope }));
46
+ return;
47
+ }
48
+ if (action === 'update') {
49
+ const name = rest.find(arg => !arg.startsWith('--'));
50
+ print(installer.update(name, { scope }));
51
+ return;
52
+ }
53
+ throw new Error(`Unknown skills command: ${action}`);
54
+ }
@@ -0,0 +1,392 @@
1
+ // Present-progressive verbs — read more conversationally than "Read file":
2
+ // "Reading auth.py — 47 lines" reads like the agent narrating, not a log.
3
+ import { isSensitiveConfigPath } from '../core/safety.mjs';
4
+
5
+ const TOOL_LABELS = Object.freeze({
6
+ shell: 'Running',
7
+ read_file: 'Reading',
8
+ read_files: 'Reading',
9
+ read_batch: 'Reading batch',
10
+ write_file: 'Writing',
11
+ write_project: 'Writing files',
12
+ edit_file: 'Editing',
13
+ delete_file: 'Deleting',
14
+ list_files: 'Listing',
15
+ search_code: 'Searching',
16
+ search_files: 'Searching files',
17
+ grep: 'Searching for',
18
+ get_file_info: 'Inspecting',
19
+ validate_file: 'Validating',
20
+ validate_build: 'Validating build',
21
+ validate_structure: 'Checking structure',
22
+ lint_check: 'Linting',
23
+ run_tests: 'Running tests',
24
+ git_diff: 'Reviewing changes',
25
+ git_status: 'Checking git',
26
+ analyze_code: 'Analyzing',
27
+ get_project_overview: 'Indexing project',
28
+ skills_list: 'Listing skills',
29
+ skill_view: 'Loading skill',
30
+ skill_install: 'Installing skill',
31
+ skill_update: 'Updating skill',
32
+ skill_remove: 'Removing skill',
33
+ agents_list: 'Listing agents',
34
+ agent_create: 'Creating agent',
35
+ agent_sync: 'Syncing agents',
36
+ workflow_list: 'Listing workflows',
37
+ workflow_create_multi: 'Creating workflow',
38
+ workflow_sync_multi: 'Syncing workflows',
39
+ workflow_run_multi: 'Running workflow',
40
+ Agent: 'Delegating',
41
+ agent: 'Delegating',
42
+ task: 'Delegating',
43
+ sub_agent_tools: 'Sub-agent tools',
44
+ explore: 'Exploring',
45
+ plan: 'Planning',
46
+ verify: 'Verifying',
47
+ debug: 'Debugging',
48
+ refactor: 'Refactoring',
49
+ ask_user: 'Asking',
50
+ });
51
+
52
+ function labelKey(tool) {
53
+ const raw = String(tool || '');
54
+ return TOOL_LABELS[raw] ? raw : raw.toLowerCase();
55
+ }
56
+
57
+ export function toolDisplayLabel(tool) {
58
+ if (!tool) return 'Use tool';
59
+ const key = labelKey(tool);
60
+ if (TOOL_LABELS[key]) return TOOL_LABELS[key];
61
+ return tool
62
+ .replace(/^mcp[_-]?/i, '')
63
+ .split(/[_-]+/)
64
+ .filter(Boolean)
65
+ .map((part, index) => index === 0
66
+ ? part.charAt(0).toUpperCase() + part.slice(1)
67
+ : part.toLowerCase())
68
+ .join(' ');
69
+ }
70
+
71
+ function currentWorkingDirectory() {
72
+ try {
73
+ return process.cwd();
74
+ } catch {
75
+ return '';
76
+ }
77
+ }
78
+
79
+ function shortPath(filePath, cwd = currentWorkingDirectory()) {
80
+ const value = String(filePath || '');
81
+ if (cwd && value.startsWith(`${cwd}/`)) return value.slice(cwd.length + 1);
82
+ return value;
83
+ }
84
+
85
+ // Sub-agent prompts (explore/plan/verify/debug/refactor) are often multi-
86
+ // paragraph briefs with bullet lists — dumping the whole thing into a tool
87
+ // card head made the terminal render N lines of noise per call. Show just
88
+ // the first paragraph (up to the first blank line), whitespace-collapsed to
89
+ // a single line, and add an ellipsis if more content was hidden.
90
+ function firstParagraph(text) {
91
+ const raw = String(text || '').replace(/\r\n?/g, '\n').trim();
92
+ if (!raw) return '';
93
+ const paraEnd = raw.indexOf('\n\n');
94
+ const head = paraEnd === -1 ? raw : raw.slice(0, paraEnd);
95
+ const collapsed = head.replace(/\s+/g, ' ').trim();
96
+ return raw.length > head.length ? `${collapsed} …` : collapsed;
97
+ }
98
+
99
+ export function toolDisplaySummary(tool, args = {}, { cwd } = {}) {
100
+ const key = String(tool || '').toLowerCase();
101
+ switch (key) {
102
+ case 'shell':
103
+ return args.command || '(empty command)';
104
+ case 'read_file': {
105
+ const filePath = shortPath(args.file_path || args.path, cwd);
106
+ if (args.start_line && args.end_line) {
107
+ return `${filePath} · lines ${args.start_line}-${args.end_line}`;
108
+ }
109
+ if (args.start_line) return `${filePath} · from line ${args.start_line}`;
110
+ return filePath;
111
+ }
112
+ case 'read_files':
113
+ case 'read_batch': {
114
+ const files = (args.file_paths || args.paths || [])
115
+ .concat((args.items || []).map(item => typeof item === 'string' ? item : item?.file_path || item?.path).filter(Boolean))
116
+ .map(filePath => shortPath(filePath, cwd));
117
+ if (files.length <= 6) return files.join(', ');
118
+ return `${files.slice(0, 3).join(', ')} · +${files.length - 3} more`;
119
+ }
120
+ case 'write_file': {
121
+ const filePath = shortPath(args.file_path || args.path, cwd);
122
+ const lineCount = typeof args.content === 'string'
123
+ ? args.content.split('\n').length
124
+ : null;
125
+ return lineCount ? `${filePath} · ${lineCount} lines` : filePath;
126
+ }
127
+ case 'write_project':
128
+ return (args.files || [])
129
+ .map(file => shortPath(file.path || file.file_path, cwd))
130
+ .filter(Boolean)
131
+ .join(', ') || 'Project files';
132
+ case 'edit_file': {
133
+ const filePath = shortPath(args.file_path || args.path, cwd);
134
+ if (isSensitiveConfigPath(filePath)) return `${filePath} · match [redacted]`;
135
+ const search = String(args.search || '').trim();
136
+ return search ? `${filePath} · match "${search.slice(0, 40)}${search.length > 40 ? '...' : ''}"` : filePath;
137
+ }
138
+ case 'delete_file':
139
+ return shortPath(args.file_path || args.path, cwd);
140
+ case 'search_code':
141
+ case 'search_files':
142
+ case 'grep':
143
+ return `"${args.query || args.pattern || ''}"${args.path ? ` in ${shortPath(args.path, cwd)}` : ''}`;
144
+ case 'list_files':
145
+ return `${args.pattern || '*'}${args.path ? ` in ${shortPath(args.path, cwd)}` : ''}`;
146
+ case 'run_tests':
147
+ case 'validate_build':
148
+ case 'lint_check':
149
+ return args.command || args.path || args.file_path || '';
150
+ case 'git_diff':
151
+ case 'git_status':
152
+ return args.path ? shortPath(args.path, cwd) : '';
153
+ case 'skills_list':
154
+ return [args.query ? `"${args.query}"` : '', args.scope || '', args.source || ''].filter(Boolean).join(' · ');
155
+ case 'skill_view':
156
+ return [args.name || '', args.path || '', args.source_id || ''].filter(Boolean).join(' · ');
157
+ case 'skill_install':
158
+ return `${args.source || ''}${args.scope ? ` → ${args.scope}` : ''}${args.force ? ' · force' : ''}`;
159
+ case 'skill_update':
160
+ case 'skill_remove':
161
+ return `${args.name || ''}${args.scope ? ` · ${args.scope}` : ''}`;
162
+ case 'agents_list':
163
+ return [args.query ? `"${args.query}"` : '', args.scope || ''].filter(Boolean).join(' · ');
164
+ case 'agent_create':
165
+ return [args.name || '', args.role || '', args.model || ''].filter(Boolean).join(' · ');
166
+ case 'agent_sync':
167
+ return args.name || args.slug || 'all local agents';
168
+ case 'workflow_list':
169
+ return [args.query ? `"${args.query}"` : '', args.scope || ''].filter(Boolean).join(' · ');
170
+ case 'workflow_create_multi':
171
+ return [args.name || '', args.pattern || 'sequential', Array.isArray(args.agents) ? `${args.agents.length} agents` : ''].filter(Boolean).join(' · ');
172
+ case 'workflow_sync_multi':
173
+ return args.name || args.slug || 'all local workflows';
174
+ case 'workflow_run_multi':
175
+ return [args.workflow_id || args.workflowId || args.name || '', args.pattern || 'sequential'].filter(Boolean).join(' · ');
176
+ case 'agent':
177
+ case 'task': {
178
+ const agentName = args.subagent_type || args.agent || args.name || args.type || '';
179
+ const task = firstParagraph(args.prompt || args.task || args.query || args.description || args.instruction || '');
180
+ return [agentName, task].filter(Boolean).join(' · ');
181
+ }
182
+ case 'sub_agent_tools': {
183
+ const total = Number(args.total || args.count || 0);
184
+ const agent = args.agent || args.type || '';
185
+ return [agent, total > 0 ? `${total} tool use${total === 1 ? '' : 's'}` : ''].filter(Boolean).join(' · ');
186
+ }
187
+ case 'explore':
188
+ case 'plan':
189
+ case 'verify':
190
+ case 'debug':
191
+ case 'refactor':
192
+ return firstParagraph(args.query || args.task || args.prompt || '');
193
+ case 'ask_user':
194
+ return args.question || args.prompt || '';
195
+ default:
196
+ return Object.values(args)
197
+ .filter(value => typeof value === 'string' && value)
198
+ .join(', ')
199
+ .slice(0, 120);
200
+ }
201
+ }
202
+
203
+ export function shellCommandDisplay(command, { cwd = currentWorkingDirectory() } = {}) {
204
+ const parsed = parseLeadingCd(command);
205
+ if (!parsed) return { command: String(command || '(empty command)'), cwdLabel: '' };
206
+
207
+ return {
208
+ command: parsed.command || '(empty command)',
209
+ cwdLabel: compactShellCwd(parsed.cwd, cwd),
210
+ };
211
+ }
212
+
213
+ const COMPACT_SHELL_CHARS = 320;
214
+ const COMPACT_SHELL_LINES = 2;
215
+
216
+ export function shellCommandProfile(command, {
217
+ cwd = currentWorkingDirectory(),
218
+ compactChars = COMPACT_SHELL_CHARS,
219
+ compactLines = COMPACT_SHELL_LINES,
220
+ } = {}) {
221
+ const original = String(command || '');
222
+ const display = shellCommandDisplay(original, { cwd });
223
+ const normalized = String(display.command || '').replace(/\r\n?/g, '\n');
224
+ const body = normalized || '(empty command)';
225
+ const lines = normalized ? normalized.split('\n') : [];
226
+ const commandLineCount = Math.max(1, lines.filter(line => line.trim()).length || lines.length);
227
+ const commandByteCount = byteLength(normalized || body);
228
+ const script = detectShellScript(normalized);
229
+ const lineCount = script?.body ? physicalLineCount(script.body) : commandLineCount;
230
+ const byteCount = script?.body ? byteLength(script.body) : commandByteCount;
231
+ const compact = Boolean(script)
232
+ || commandLineCount >= compactLines
233
+ || commandByteCount > compactChars;
234
+ const kind = script?.kind || (lineCount > 1 ? 'shell script' : 'shell command');
235
+ const preview = script?.body ? scriptBodyPreview(script.body) : '';
236
+ const summary = compact
237
+ ? [
238
+ `${kind} · ${lineCount} line${lineCount === 1 ? '' : 's'} · ${formatBytes(byteCount)}`,
239
+ preview ? `preview: ${preview}` : '',
240
+ ].filter(Boolean).join(' · ')
241
+ : body;
242
+
243
+ return {
244
+ original,
245
+ command: body,
246
+ cwdLabel: display.cwdLabel,
247
+ lineCount,
248
+ byteCount,
249
+ commandLineCount,
250
+ commandByteCount,
251
+ compact,
252
+ kind,
253
+ summary,
254
+ preview,
255
+ script,
256
+ detailHint: compact ? 'details: F2 or /last' : '',
257
+ };
258
+ }
259
+
260
+ function detectShellScript(command) {
261
+ const text = String(command || '').replace(/\r\n?/g, '\n');
262
+ if (!text) return null;
263
+
264
+ const heredoc = text.match(/\b(python3?|node|ruby|perl|bash|sh)\b[^\n]*<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\2[^\n]*\n([\s\S]*?)\n\3(?:\s*$|\s)/);
265
+ if (heredoc) {
266
+ const invocation = text.slice(0, text.indexOf('\n')).trim();
267
+ return {
268
+ kind: interpreterKind(heredoc[1]),
269
+ interpreter: heredoc[1],
270
+ marker: heredoc[3],
271
+ invocation,
272
+ body: heredoc[4],
273
+ };
274
+ }
275
+
276
+ const inline = text.match(/\b(python3?|node|ruby|perl)\b\s+(?:-[A-Za-z]*[ce][A-Za-z]*|--command|--eval)\s+(['"])([\s\S]{120,})\2/);
277
+ if (inline) {
278
+ return {
279
+ kind: interpreterKind(inline[1]),
280
+ interpreter: inline[1],
281
+ invocation: text.slice(0, inline.index + inline[0].indexOf(inline[2])).trim(),
282
+ body: inline[3],
283
+ };
284
+ }
285
+
286
+ const tempScript = text.match(/\b(python3?|node|ruby|perl|bash|sh)\b\s+((?:\/(?:private\/)?tmp|\/private\/var\/folders|\/var\/folders)[^\s;&|]+\.(?:py|mjs|js|rb|pl|sh))\b/);
287
+ if (tempScript) {
288
+ return {
289
+ kind: interpreterKind(tempScript[1]),
290
+ interpreter: tempScript[1],
291
+ invocation: `${tempScript[1]} ${tempScript[2]}`,
292
+ path: tempScript[2],
293
+ };
294
+ }
295
+
296
+ return null;
297
+ }
298
+
299
+ function interpreterKind(value) {
300
+ const name = String(value || '').toLowerCase();
301
+ if (name.startsWith('python')) return 'python script';
302
+ if (name === 'node') return 'node script';
303
+ if (name === 'ruby') return 'ruby script';
304
+ if (name === 'perl') return 'perl script';
305
+ return 'shell script';
306
+ }
307
+
308
+ function scriptBodyPreview(body, maxChars = 20) {
309
+ const line = String(body || '')
310
+ .replace(/\r\n?/g, '\n')
311
+ .split('\n')
312
+ .map(value => value.trim())
313
+ .find(Boolean) || '';
314
+ const compact = line.replace(/\s+/g, ' ');
315
+ if (compact.length <= maxChars) return compact;
316
+ return `${compact.slice(0, Math.max(0, maxChars - 1))}…`;
317
+ }
318
+
319
+ function byteLength(value) {
320
+ try {
321
+ return Buffer.byteLength(String(value || ''), 'utf8');
322
+ } catch {
323
+ return String(value || '').length;
324
+ }
325
+ }
326
+
327
+ function physicalLineCount(value) {
328
+ const text = String(value || '');
329
+ if (!text) return 0;
330
+ return text.split('\n').length;
331
+ }
332
+
333
+ function formatBytes(bytes) {
334
+ const n = Number(bytes) || 0;
335
+ if (n < 1024) return `${n} B`;
336
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(n < 10 * 1024 ? 1 : 0)} KB`;
337
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
338
+ }
339
+
340
+ export function formatShellCommand(command, colors) {
341
+ const tokens = String(command || '').match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|&&|\|\||[|;<>]|[^\s]+|\s+/g) || [];
342
+ let expectsCommand = true;
343
+
344
+ return tokens.map(token => {
345
+ if (/^\s+$/.test(token)) return token;
346
+ if (/^(?:&&|\|\||[|;<>])$/.test(token)) {
347
+ expectsCommand = true;
348
+ return colors.red(token);
349
+ }
350
+ if (expectsCommand) {
351
+ expectsCommand = false;
352
+ return colors.blue(token);
353
+ }
354
+ if (/^-{1,2}[\w-]+(?:=.*)?$/.test(token) || /^["']/.test(token)) {
355
+ return colors.yellow(token);
356
+ }
357
+ return colors.white(token);
358
+ }).join('');
359
+ }
360
+
361
+ function parseLeadingCd(command) {
362
+ const text = String(command || '').trim();
363
+ const match = text.match(/^cd\s+((?:"(?:\\.|[^"\\])*")|'(?:\\.|[^'\\])*'|(?:\\.|[^\s&])+)\s*&&\s*([\s\S]+)$/);
364
+ if (!match) return null;
365
+ return {
366
+ cwd: unquoteShellToken(match[1]),
367
+ command: match[2].trim(),
368
+ };
369
+ }
370
+
371
+ function unquoteShellToken(token) {
372
+ const text = String(token || '');
373
+ if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
374
+ const body = text.slice(1, -1);
375
+ return text.startsWith('"')
376
+ ? body.replace(/\\(["\\$`])/g, '$1')
377
+ : body;
378
+ }
379
+ return text.replace(/\\(.)/g, '$1');
380
+ }
381
+
382
+ function compactShellCwd(dir, cwd) {
383
+ const value = String(dir || '');
384
+ if (!value) return '';
385
+ if (cwd && value === cwd) return '';
386
+ if (cwd && value.startsWith(`${cwd}/`)) return value.slice(cwd.length + 1);
387
+
388
+ const parts = value.split('/').filter(Boolean);
389
+ if (parts.length <= 2) return value || dir;
390
+ if (/\s/.test(parts[parts.length - 2] || '')) return parts[parts.length - 1];
391
+ return parts.slice(-2).join('/');
392
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Agent Tool — spawn a subagent with its own agent loop.
3
+ *
4
+ * Features:
5
+ * - subagent_type parameter
6
+ * - isolation: "worktree" option
7
+ * - run_in_background option
8
+ * - model override
9
+ */
10
+
11
+ import { createAgentLoop } from '../core/agent-loop.mjs';
12
+ import { createToolRegistry } from './registry.mjs';
13
+ import { createPermissionChecker } from '../permissions/checker.mjs';
14
+
15
+ export const AgentTool = {
16
+ name: 'Agent',
17
+ description: 'Spawn a subagent to handle a task. The subagent has its own context and tools.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ prompt: {
22
+ type: 'string',
23
+ description: 'The task for the subagent to perform',
24
+ },
25
+ allowed_tools: {
26
+ type: 'array',
27
+ items: { type: 'string' },
28
+ description: 'List of tool names the subagent can use (default: all)',
29
+ },
30
+ subagent_type: {
31
+ type: 'string',
32
+ description: 'Type of subagent (e.g. coder, reviewer, researcher)',
33
+ },
34
+ isolation: {
35
+ type: 'string',
36
+ enum: ['default', 'worktree'],
37
+ description: 'Isolation mode. "worktree" uses a git worktree.',
38
+ },
39
+ run_in_background: {
40
+ type: 'boolean',
41
+ description: 'Run in background and return immediately',
42
+ },
43
+ model: {
44
+ type: 'string',
45
+ description: 'Override model for this subagent',
46
+ },
47
+ },
48
+ required: ['prompt'],
49
+ },
50
+
51
+ validateInput(input) {
52
+ const errors = [];
53
+ if (!input.prompt) errors.push('prompt is required');
54
+ return errors;
55
+ },
56
+
57
+ // Track background subagents
58
+ _backgroundAgents: new Map(),
59
+ _nextBgId: 0,
60
+
61
+ async call(input) {
62
+ const model = input.model || process.env.SUBAGENT_MODEL || 'claude-sonnet-4-6';
63
+ const tools = createToolRegistry();
64
+ const permissions = createPermissionChecker({ defaultMode: 'bypassPermissions' });
65
+
66
+ // Build type-specific system prompt prefix
67
+ let systemPrefix = '';
68
+ if (input.subagent_type) {
69
+ const typePrompts = {
70
+ coder: 'You are a coding agent. Write clean, tested code.',
71
+ reviewer: 'You are a code reviewer. Analyze code for bugs and improvements.',
72
+ researcher: 'You are a research agent. Find and summarize information.',
73
+ tester: 'You are a testing agent. Write and run tests.',
74
+ planner: 'You are a planning agent. Break down tasks into steps.',
75
+ };
76
+ systemPrefix = typePrompts[input.subagent_type] || `You are a ${input.subagent_type} agent.`;
77
+ }
78
+
79
+ const fullPrompt = systemPrefix
80
+ ? `${systemPrefix}\n\nTask: ${input.prompt}`
81
+ : input.prompt;
82
+
83
+ const loop = createAgentLoop({
84
+ model,
85
+ tools,
86
+ permissions,
87
+ settings: {
88
+ stream: false,
89
+ stagnationDetection: !['0', 'false', 'no', 'off'].includes(
90
+ (process.env.KEPLER_STAGNATION_DETECTION ?? '0').toLowerCase(),
91
+ ),
92
+ stagnationThreshold: Number.parseInt(
93
+ process.env.KEPLER_STAGNATION_THRESHOLD
94
+ ?? '3',
95
+ 10,
96
+ ),
97
+ },
98
+ });
99
+
100
+ if (input.run_in_background) {
101
+ const bgId = ++AgentTool._nextBgId;
102
+ const entry = { id: bgId, status: 'running', result: null, prompt: input.prompt };
103
+ AgentTool._backgroundAgents.set(bgId, entry);
104
+
105
+ // Run in background
106
+ runSubagent(loop, fullPrompt).then(result => {
107
+ entry.status = 'completed';
108
+ entry.result = result;
109
+ }).catch(err => {
110
+ entry.status = 'error';
111
+ entry.result = err.message;
112
+ });
113
+
114
+ return `Subagent started in background: id=${bgId}`;
115
+ }
116
+
117
+ return runSubagent(loop, fullPrompt);
118
+ },
119
+ };
120
+
121
+ async function runSubagent(loop, prompt) {
122
+ const results = [];
123
+ try {
124
+ for await (const event of loop.run(prompt)) {
125
+ if (event.type === 'assistant' && event.content) {
126
+ results.push(event.content);
127
+ }
128
+ if (event.type === 'result') {
129
+ results.push(`[tool:${event.tool}] ${String(event.result).slice(0, 500)}`);
130
+ }
131
+ }
132
+ } catch (err) {
133
+ return `Subagent error: ${err.message}`;
134
+ }
135
+
136
+ return results.join('\n') || 'Subagent completed with no output.';
137
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AskUser Tool — prompt the user with a question and return their response.
3
+ *
4
+ * Used when the agent needs clarification or confirmation from the user.
5
+ * In non-interactive mode, returns a default or times out.
6
+ */
7
+
8
+ import readline from 'readline';
9
+
10
+ export const AskUserTool = {
11
+ name: 'AskUser',
12
+ description: 'Ask the user a question and wait for their response.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ question: {
17
+ type: 'string',
18
+ description: 'The question to ask the user',
19
+ },
20
+ default_value: {
21
+ type: 'string',
22
+ description: 'Default value if user provides no input',
23
+ },
24
+ timeout: {
25
+ type: 'number',
26
+ description: 'Timeout in milliseconds (default: 60000)',
27
+ },
28
+ },
29
+ required: ['question'],
30
+ },
31
+
32
+ validateInput(input) {
33
+ return input.question ? [] : ['question is required'];
34
+ },
35
+
36
+ async call(input) {
37
+ // In non-interactive mode, return default
38
+ if (!process.stdin.isTTY) {
39
+ return input.default_value || '[non-interactive: no user input available]';
40
+ }
41
+
42
+ return new Promise((resolve) => {
43
+ const rl = readline.createInterface({
44
+ input: process.stdin,
45
+ output: process.stderr,
46
+ });
47
+
48
+ const timeout = setTimeout(() => {
49
+ rl.close();
50
+ resolve(input.default_value || '[timeout: no response]');
51
+ }, input.timeout || 60000);
52
+
53
+ process.stderr.write(`\n\x1b[36m? ${input.question}\x1b[0m\n> `);
54
+ rl.question('', (answer) => {
55
+ clearTimeout(timeout);
56
+ rl.close();
57
+ resolve(answer.trim() || input.default_value || '');
58
+ });
59
+ });
60
+ },
61
+ };