@bahulam/code 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -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 +222 -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 +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -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/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,581 @@
1
+ /**
2
+ * Workflow CLI Commands — create, list, get, run, sync, delete workflows.
3
+ *
4
+ * User-facing term is "workflow" everywhere. Backend stores these as
5
+ * templates (POST /api/templates, table "templates").
6
+ *
7
+ * Commands:
8
+ * bahulam-code workflow create --file <path>
9
+ * bahulam-code workflow create-multi --file <path>
10
+ * bahulam-code workflow run <name> [--instruction "..."] [--config key=val]
11
+ * bahulam-code workflow run-multi <id> [--instruction "..."] [--pattern sequential|parallel]
12
+ * bahulam-code workflow sync [--dir <path>]
13
+ * bahulam-code workflow list
14
+ * bahulam-code workflow get <name>
15
+ * bahulam-code workflow delete <name>
16
+ */
17
+
18
+ import { resolveBackendUrl } from '../core/backend-url.mjs';
19
+ import { TarangAuth } from '../auth/tarang-auth.mjs';
20
+ import { TarangStreamClient } from '../core/stream-client.mjs';
21
+ import { createToolExecutor } from '../core/tool-executor.mjs';
22
+ import { ApprovalManager } from '../core/approval.mjs';
23
+ import { EventFormatter } from '../ui/formatter.mjs';
24
+ import { loadWorkflowFromFile, loadWorkflowsFromDir, workflowToTemplatePayload } from '../agents/workflow_loader.mjs';
25
+ import { loadMultiWorkflowFromFile } from '../agents/multi_workflow_loader.mjs';
26
+ import { buildWorkScope } from '../core/work-scope.mjs';
27
+
28
+ const RESET = '\x1b[0m';
29
+ const BOLD = '\x1b[1m';
30
+ const DIM = '\x1b[2m';
31
+ const RED = '\x1b[31m';
32
+ const GREEN = '\x1b[32m';
33
+ const YELLOW = '\x1b[33m';
34
+ const CYAN = '\x1b[36m';
35
+
36
+ /**
37
+ * Main entry point for all `bahulam-code workflow` subcommands.
38
+ * @param {object} args - parsed CLI args
39
+ */
40
+ export async function handleWorkflowCommand(args) {
41
+ const sub = args.workflowSubcommand || 'help';
42
+
43
+ switch (sub) {
44
+ case 'create':
45
+ await handleCreate(args);
46
+ break;
47
+ case 'create-multi':
48
+ await handleCreateMulti(args);
49
+ break;
50
+ case 'run':
51
+ await handleRun(args);
52
+ break;
53
+ case 'sync':
54
+ await handleSync(args);
55
+ break;
56
+ case 'list':
57
+ await handleList(args);
58
+ break;
59
+ case 'get':
60
+ await handleGet(args);
61
+ break;
62
+ case 'run-multi':
63
+ await handleRunMulti(args);
64
+ break;
65
+ case 'delete':
66
+ await handleDelete(args);
67
+ break;
68
+ default:
69
+ printWorkflowUsage();
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ function printWorkflowUsage() {
75
+ process.stderr.write(`${BOLD}WORKFLOW COMMANDS${RESET}\n`);
76
+ process.stderr.write(` ${CYAN}bahulam-code workflow create --file <path>${RESET} Create single-agent workflow from YAML\n`);
77
+ process.stderr.write(` ${CYAN}bahulam-code workflow create-multi --file <path>${RESET} Create multi-agent workflow from YAML\n`);
78
+ process.stderr.write(` ${CYAN}bahulam-code workflow run <name> [opts]${RESET} Run a workflow adhoc\n`);
79
+ process.stderr.write(` ${CYAN}bahulam-code workflow run-multi <id> [opts]${RESET} Run a multi-agent workflow\n`);
80
+ process.stderr.write(` ${CYAN}bahulam-code workflow sync [--dir <path>]${RESET} Sync single-agent workflow YAML files\n`);
81
+ process.stderr.write(` ${CYAN}bahulam-code workflow list${RESET} List all workflows\n`);
82
+ process.stderr.write(` ${CYAN}bahulam-code workflow get <name>${RESET} Show workflow details\n`);
83
+ process.stderr.write(` ${CYAN}bahulam-code workflow delete <name>${RESET} Delete a workflow\n`);
84
+ process.stderr.write('\n');
85
+ }
86
+
87
+ // ── Helpers ────────────────────────────────────────────────────
88
+
89
+ function getAuthHeaders() {
90
+ const auth = new TarangAuth();
91
+ const creds = auth.loadCredentials();
92
+ if (!creds.token) {
93
+ process.stderr.write(`${RED}✗ Not authenticated. Run `bahulam-code login` first.${RESET}\n`);
94
+ process.exit(1);
95
+ }
96
+ return {
97
+ 'Authorization': `Bearer ${creds.token}`,
98
+ 'Content-Type': 'application/json',
99
+ };
100
+ }
101
+
102
+ function getBaseUrl() {
103
+ const auth = new TarangAuth();
104
+ return auth.loadCredentials().backendUrl || resolveBackendUrl();
105
+ }
106
+
107
+ async function apiFetch(path, options = {}) {
108
+ const baseUrl = getBaseUrl();
109
+ const headers = { ...getAuthHeaders(), ...options.headers };
110
+ const url = `${baseUrl}${path}`;
111
+ const response = await fetch(url, { ...options, headers });
112
+
113
+ if (response.status === 401) {
114
+ process.stderr.write(`${RED}✗ Authentication failed. Run `bahulam-code login` to re-authenticate.${RESET}\n`);
115
+ process.exit(1);
116
+ }
117
+
118
+ const text = await response.text();
119
+ let payload;
120
+ try {
121
+ payload = text ? JSON.parse(text) : {};
122
+ } catch {
123
+ payload = { detail: text };
124
+ }
125
+
126
+ if (!response.ok) {
127
+ const msg = payload?.detail || payload?.message || `HTTP ${response.status}`;
128
+ throw new Error(msg);
129
+ }
130
+
131
+ return payload;
132
+ }
133
+
134
+ // ── Create ─────────────────────────────────────────────────────
135
+
136
+ async function handleCreate(args) {
137
+ const filePath = args.workflowFile;
138
+ if (!filePath) {
139
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow create --file <path>${RESET}\n`);
140
+ process.exit(1);
141
+ }
142
+
143
+ process.stderr.write(`${DIM}Loading workflow from ${filePath}...${RESET}\n`);
144
+ const payload = loadWorkflowFromFile(filePath);
145
+
146
+ process.stderr.write(`${DIM}Creating workflow '${payload.slug}'...${RESET}\n`);
147
+ const result = await apiFetch('/api/templates', {
148
+ method: 'POST',
149
+ body: JSON.stringify(payload),
150
+ });
151
+
152
+ const template = result.template || {};
153
+ const upserted = result.upserted;
154
+ if (upserted) {
155
+ process.stderr.write(`${GREEN}✓ Updated workflow '${template.slug || payload.slug}'${RESET}\n`);
156
+ } else {
157
+ process.stderr.write(`${GREEN}✓ Created workflow '${template.slug || payload.slug}'${RESET}\n`);
158
+ }
159
+ process.stdout.write(JSON.stringify({ slug: template.slug || payload.slug, upserted }, null, 2) + '\n');
160
+ }
161
+
162
+ // ── Create Multi ────────────────────────────────────────────────
163
+
164
+ async function handleCreateMulti(args) {
165
+ const filePath = args.workflowFile;
166
+ if (!filePath) {
167
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow create-multi --file <path>${RESET}\n`);
168
+ process.exit(1);
169
+ }
170
+
171
+ process.stderr.write(`${DIM}Loading multi-agent workflow from ${filePath}...${RESET}\n`);
172
+ const payload = loadMultiWorkflowFromFile(filePath);
173
+
174
+ process.stderr.write(`${DIM}Creating multi-agent workflow '${payload.name}'...${RESET}\n`);
175
+ const result = await apiFetch('/api/workflows', {
176
+ method: 'POST',
177
+ body: JSON.stringify(payload),
178
+ });
179
+
180
+ const workflow = result.workflow || {};
181
+ process.stderr.write(`${GREEN}✓ Created multi-agent workflow '${workflow.name || payload.name}'${RESET}\n`);
182
+ process.stderr.write(` ${DIM}ID: ${workflow.id}${RESET}\n`);
183
+ process.stderr.write(` ${DIM}Pattern: ${payload.pattern}${RESET}\n`);
184
+ process.stderr.write(` ${DIM}Agents: ${payload.graph.nodes.filter(n => n.type === 'agent').length}${RESET}\n`);
185
+ process.stdout.write(JSON.stringify({ id: workflow.id, name: workflow.name || payload.name }, null, 2) + '\n');
186
+ }
187
+
188
+ // ── Run ─────────────────────────────────────────────────────────
189
+
190
+ async function handleRun(args) {
191
+ const slug = args.workflowSlug;
192
+ if (!slug) {
193
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow run <name> [--instruction \"...\"]${RESET}\n`);
194
+ process.exit(1);
195
+ }
196
+
197
+ const instruction = args.instruction || '';
198
+ if (!instruction) {
199
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow run <name> --instruction \"...\"${RESET}\n`);
200
+ process.exit(1);
201
+ }
202
+
203
+ const baseUrl = getBaseUrl();
204
+ const headers = getAuthHeaders();
205
+ const url = `${baseUrl}/api/run`;
206
+
207
+ const body = JSON.stringify({
208
+ template: slug,
209
+ instruction,
210
+ config: {},
211
+ mode: 'stream',
212
+ });
213
+
214
+ process.stderr.write(`${DIM}Running workflow '${slug}'...${RESET}\n`);
215
+
216
+ const formatter = new EventFormatter({ verbose: args.verbose });
217
+
218
+ try {
219
+ const response = await fetch(url, {
220
+ method: 'POST',
221
+ headers: { ...headers, Accept: 'text/event-stream' },
222
+ body,
223
+ });
224
+
225
+ if (response.status === 401) {
226
+ process.stderr.write(`${RED}✗ Authentication failed. Run `bahulam-code login` to re-authenticate.${RESET}\n`);
227
+ process.exit(1);
228
+ }
229
+ if (response.status === 404) {
230
+ process.stderr.write(`${RED}✗ Workflow '${slug}' not found.${RESET}\n`);
231
+ process.exit(1);
232
+ }
233
+ if (!response.ok) {
234
+ const text = await response.text().catch(() => 'Unknown error');
235
+ process.stderr.write(`${RED}✗ Backend error ${response.status}: ${text}${RESET}\n`);
236
+ process.exit(1);
237
+ }
238
+
239
+ const reader = response.body.getReader();
240
+ const decoder = new TextDecoder();
241
+ let buffer = '';
242
+ let currentEvent = 'message';
243
+ let currentData = [];
244
+
245
+ while (true) {
246
+ const { done, value } = await reader.read();
247
+ if (done) break;
248
+
249
+ buffer += decoder.decode(value, { stream: true });
250
+ const lines = buffer.split('\n');
251
+ buffer = lines.pop();
252
+
253
+ for (const line of lines) {
254
+ const raw = line.endsWith('\r') ? line.slice(0, -1) : line;
255
+ const trimmed = raw.trim();
256
+
257
+ if (!trimmed) {
258
+ // Empty line = event boundary
259
+ if (currentData.length > 0) {
260
+ const rawData = currentData.join('\n');
261
+ let parsed;
262
+ try {
263
+ parsed = JSON.parse(rawData);
264
+ } catch {
265
+ parsed = { message: rawData };
266
+ }
267
+ const event = { type: currentEvent, data: parsed };
268
+ formatter.render(event);
269
+ if (event.type === 'run_error' || event.type === 'error') {
270
+ const errData = event.data || {};
271
+ if (errData.fatal) {
272
+ process.stderr.write(`\n${RED}✗ ${errData.message || 'Run failed'}${RESET}\n`);
273
+ }
274
+ }
275
+ }
276
+ currentEvent = 'message';
277
+ currentData = [];
278
+ } else if (trimmed.startsWith('event:')) {
279
+ currentEvent = trimmed.slice(6).trim();
280
+ } else if (trimmed.startsWith('data:')) {
281
+ currentData.push(trimmed.slice(5).trim());
282
+ }
283
+ }
284
+ }
285
+
286
+ // Flush remaining
287
+ if (currentData.length > 0) {
288
+ const rawData = currentData.join('\n');
289
+ let parsed;
290
+ try {
291
+ parsed = JSON.parse(rawData);
292
+ } catch {
293
+ parsed = { message: rawData };
294
+ }
295
+ formatter.render({ type: currentEvent, data: parsed });
296
+ }
297
+
298
+ process.stdout.write('\n');
299
+ process.stderr.write(`${GREEN}✓ Run complete${RESET}\n`);
300
+ } catch (err) {
301
+ if (err.name === 'AbortError') {
302
+ process.stderr.write(`\n${YELLOW}⚠ Run cancelled${RESET}\n`);
303
+ } else {
304
+ process.stderr.write(`${RED}✗ ${err.message}${RESET}\n`);
305
+ process.exit(1);
306
+ }
307
+ }
308
+ }
309
+
310
+ // ── Sync ───────────────────────────────────────────────────────
311
+
312
+ async function handleSync(args) {
313
+ const dir = args.workflowDir || '.bahulam/workflows';
314
+
315
+ process.stderr.write(`${DIM}Scanning ${dir} for workflow YAML files...${RESET}\n`);
316
+ const workflows = loadWorkflowsFromDir(dir);
317
+
318
+ if (workflows.length === 0) {
319
+ process.stderr.write(`${YELLOW}⚠ No workflow YAML files found in ${dir}${RESET}\n`);
320
+ process.exit(0);
321
+ }
322
+
323
+ process.stderr.write(`Found ${workflows.length} workflow(s)\n\n`);
324
+
325
+ let created = 0;
326
+ let updated = 0;
327
+ let failed = 0;
328
+
329
+ for (const { file, payload } of workflows) {
330
+ const fileName = file.split('/').pop();
331
+ process.stderr.write(` ${DIM}${fileName}${RESET} → `);
332
+ try {
333
+ const result = await apiFetch('/api/templates', {
334
+ method: 'POST',
335
+ body: JSON.stringify(payload),
336
+ });
337
+ if (result.upserted) {
338
+ process.stderr.write(`${GREEN}updated${RESET}\n`);
339
+ updated++;
340
+ } else {
341
+ process.stderr.write(`${GREEN}created${RESET}\n`);
342
+ created++;
343
+ }
344
+ } catch (err) {
345
+ process.stderr.write(`${RED}failed: ${err.message}${RESET}\n`);
346
+ failed++;
347
+ }
348
+ }
349
+
350
+ process.stderr.write(`\n${GREEN}✓ Done: ${created} created, ${updated} updated${failed ? `, ${RED}${failed} failed${RESET}` : ''}${RESET}\n`);
351
+ }
352
+
353
+ // ── List ───────────────────────────────────────────────────────
354
+
355
+ async function handleList(args) {
356
+ const [templateResult, workflowResult] = await Promise.all([
357
+ apiFetch('/api/templates').catch(() => ({ templates: [] })),
358
+ apiFetch('/api/workflows').catch(() => ({ workflows: [] })),
359
+ ]);
360
+
361
+ const templates = templateResult.templates || [];
362
+ const workflows = workflowResult.workflows || [];
363
+ if (templates.length === 0 && workflows.length === 0) {
364
+ process.stderr.write(`${DIM}No workflows found.${RESET}\n`);
365
+ process.exit(0);
366
+ }
367
+
368
+ process.stderr.write(`${BOLD}Workflows:${RESET}\n`);
369
+ for (const w of workflows) {
370
+ const pattern = w.orchestration_pattern || w.pattern || 'sequential';
371
+ process.stderr.write(` ${CYAN}${w.id}${RESET} ${w.name} ${DIM}(graph · ${pattern})${RESET}\n`);
372
+ if (w.description) {
373
+ process.stderr.write(` ${DIM}${w.description}${RESET}\n`);
374
+ }
375
+ }
376
+ for (const t of templates) {
377
+ const owner = t.owner_type === 'platform' ? `${DIM}(platform)${RESET}` : `${DIM}(user)${RESET}`;
378
+ process.stderr.write(` ${CYAN}${t.slug}${RESET} ${t.name} ${owner}${DIM} · single-agent${RESET}\n`);
379
+ if (t.description) {
380
+ process.stderr.write(` ${DIM}${t.description}${RESET}\n`);
381
+ }
382
+ }
383
+ process.stdout.write(JSON.stringify({
384
+ workflows: workflows.map(w => ({
385
+ id: w.id,
386
+ name: w.name,
387
+ orchestration_pattern: w.orchestration_pattern || w.pattern || 'sequential',
388
+ status: w.status,
389
+ })),
390
+ templates: templates.map(t => ({ slug: t.slug, name: t.name, owner: t.owner_type })),
391
+ }, null, 2) + '\n');
392
+ }
393
+
394
+ // ── Get ────────────────────────────────────────────────────────
395
+
396
+ async function handleGet(args) {
397
+ const slug = args.workflowSlug;
398
+ if (!slug) {
399
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow get <name>${RESET}\n`);
400
+ process.exit(1);
401
+ }
402
+
403
+ const result = isUuid(slug)
404
+ ? await apiFetch(`/api/workflows/${encodeURIComponent(slug)}`)
405
+ : await apiFetch(`/api/templates/${encodeURIComponent(slug)}`);
406
+ if (result.workflow) {
407
+ const workflow = result.workflow;
408
+ process.stderr.write(`${BOLD}${workflow.name}${RESET} (${CYAN}${workflow.id}${RESET})\n`);
409
+ if (workflow.description) process.stderr.write(` ${DIM}${workflow.description}${RESET}\n`);
410
+ process.stderr.write(` Type: graph\n`);
411
+ process.stderr.write(` Pattern: ${workflow.orchestration_pattern || workflow.pattern || 'sequential'}\n`);
412
+ process.stderr.write(` Agents: ${(workflow.graph?.nodes || []).filter((n) => n.type === 'agent').length}\n`);
413
+ process.stdout.write(JSON.stringify(workflow, null, 2) + '\n');
414
+ return;
415
+ }
416
+
417
+ const template = result.template || {};
418
+ process.stderr.write(`${BOLD}${template.name}${RESET} (${CYAN}${template.slug}${RESET})\n`);
419
+ if (template.description) process.stderr.write(` ${DIM}${template.description}${RESET}\n`);
420
+ process.stderr.write(` Owner: ${template.owner_type || 'user'}\n`);
421
+ process.stderr.write(` Model: ${template.model || 'default'}\n`);
422
+ process.stderr.write(` Max iterations: ${template.max_iterations || 20}\n`);
423
+ process.stderr.write(` Channel: ${template.channel || 'server'}\n`);
424
+ if (template.tags?.length) process.stderr.write(` Tags: ${template.tags.join(', ')}\n`);
425
+ if (template.system_prompt) {
426
+ const preview = template.system_prompt.slice(0, 200);
427
+ process.stderr.write(` System prompt: ${DIM}${preview}${preview.length < template.system_prompt.length ? '...' : ''}${RESET}\n`);
428
+ }
429
+ process.stdout.write(JSON.stringify(template, null, 2) + '\n');
430
+ }
431
+
432
+ // ── Delete ─────────────────────────────────────────────────────
433
+
434
+ async function handleDelete(args) {
435
+ const slug = args.workflowSlug;
436
+ if (!slug) {
437
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow delete <name>${RESET}\n`);
438
+ process.exit(1);
439
+ }
440
+
441
+ if (!args.yes) {
442
+ process.stderr.write(`${YELLOW}⚠ Are you sure you want to delete workflow '${slug}'?${RESET} Use ${CYAN}--yes${RESET} to confirm.\n`);
443
+ process.exit(1);
444
+ }
445
+
446
+ const path = isUuid(slug)
447
+ ? `/api/workflows/${encodeURIComponent(slug)}`
448
+ : `/api/templates/${encodeURIComponent(slug)}`;
449
+ await apiFetch(path, {
450
+ method: 'DELETE',
451
+ });
452
+
453
+ process.stderr.write(`${GREEN}✓ Deleted workflow '${slug}'${RESET}\n`);
454
+ process.stdout.write(JSON.stringify({ slug, deleted: true }) + '\n');
455
+ }
456
+
457
+
458
+ // ── Run Multi (multi-agent) ─────────────────────────────────
459
+
460
+ async function handleRunMulti(args) {
461
+ const workflowId = args.workflowSlug;
462
+ if (!workflowId) {
463
+ process.stderr.write(`${RED}✗ Usage: bahulam-code workflow run-multi <id> [--instruction "..."] [--pattern sequential|parallel]${RESET}\n`);
464
+ process.exit(1);
465
+ }
466
+
467
+ const instruction = args.instruction || '';
468
+ const pattern = args.pattern || 'sequential';
469
+ const workScope = buildWorkScope({
470
+ instruction,
471
+ cwd: process.cwd(),
472
+ projectResources: [],
473
+ });
474
+
475
+ const baseUrl = getBaseUrl();
476
+ const headers = getAuthHeaders();
477
+ const url = `${baseUrl}/api/workflows/${encodeURIComponent(workflowId)}/run-multi`;
478
+ const approvalAllowedTools = [
479
+ 'get_project_overview',
480
+ 'write_file',
481
+ 'write_project',
482
+ 'edit_file',
483
+ 'shell',
484
+ 'lint_check',
485
+ 'validate_build',
486
+ 'run_tests',
487
+ 'agent_create',
488
+ 'agent_sync',
489
+ 'workflow_create_multi',
490
+ 'workflow_sync_multi',
491
+ 'workflow_run_multi',
492
+ ];
493
+
494
+ const body = JSON.stringify({
495
+ trigger_input: {
496
+ instruction,
497
+ cwd: process.cwd(),
498
+ project_root: process.cwd(),
499
+ work_scope: workScope,
500
+ },
501
+ global_params: {
502
+ instruction,
503
+ cwd: process.cwd(),
504
+ project_root: process.cwd(),
505
+ project_resources: [],
506
+ work_scope: workScope,
507
+ },
508
+ orchestration_pattern: pattern,
509
+ pattern,
510
+ mode: 'stream',
511
+ approval_scope: {
512
+ approved: true,
513
+ source: 'cli_command',
514
+ scope: 'workflow_run',
515
+ workflow_id: workflowId,
516
+ allowed_tools: approvalAllowedTools,
517
+ allow_destructive: false,
518
+ reason: 'User invoked workflow run command',
519
+ },
520
+ });
521
+
522
+ process.stderr.write(`${DIM}Running multi-agent workflow '${workflowId}' (${pattern})...${RESET}\n`);
523
+
524
+ const formatter = new EventFormatter({ verbose: args.verbose });
525
+
526
+ try {
527
+ const response = await fetch(url, {
528
+ method: 'POST',
529
+ headers: { ...headers, Accept: 'text/event-stream', 'Content-Type': 'application/json' },
530
+ body,
531
+ });
532
+
533
+ if (response.status === 401) {
534
+ process.stderr.write(`${RED}✗ Authentication failed. Run `bahulam-code login` to re-authenticate.${RESET}\n`);
535
+ process.exit(1);
536
+ }
537
+ if (!response.ok) {
538
+ const text = await response.text().catch(() => 'Unknown error');
539
+ process.stderr.write(`${RED}✗ Backend error ${response.status}: ${text}${RESET}\n`);
540
+ process.exit(1);
541
+ }
542
+
543
+ const client = new TarangStreamClient({
544
+ baseUrl,
545
+ token: headers.Authorization.replace(/^Bearer\s+/i, ''),
546
+ toolExecutor: createToolExecutor(),
547
+ verbose: args.verbose,
548
+ approvalManager: new ApprovalManager({ autoApprove: args.yes }),
549
+ });
550
+
551
+ for await (const event of client.consumeEventStream(response)) {
552
+ formatter.render(event);
553
+
554
+ if (event.type === 'agent_complete') {
555
+ const parsed = event.data || {};
556
+ const s = parsed.status || 'completed';
557
+ const icon = s === 'completed' ? GREEN : RED;
558
+ process.stderr.write(` ${icon}${s === 'completed' ? '✓' : '✗'}${RESET} ${parsed.agent_slug} (${parsed.duration_s || '?'}s, ${parsed.tokens || 0} tok)\n`);
559
+ } else if (event.type === 'orchestration_complete') {
560
+ const parsed = event.data || {};
561
+ process.stderr.write(`\n${GREEN}✓ Multi-agent run complete${RESET} (${parsed.duration_s}s, ${parsed.total_tokens} tok, ${parsed.total_cost || '0'})\n`);
562
+ } else if (event.type === 'run_error') {
563
+ const parsed = event.data || {};
564
+ process.stderr.write(`\n${RED}✗ ${parsed.error || 'Run failed'}${RESET}\n`);
565
+ }
566
+ }
567
+
568
+ process.stdout.write('\n');
569
+ } catch (err) {
570
+ if (err.name === 'AbortError') {
571
+ process.stderr.write(`\n${YELLOW}⚠ Run cancelled${RESET}\n`);
572
+ } else {
573
+ process.stderr.write(`${RED}✗ ${err.message}${RESET}\n`);
574
+ process.exit(1);
575
+ }
576
+ }
577
+ }
578
+
579
+ function isUuid(value) {
580
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value || '');
581
+ }