@kenkaiiii/gg-boss 4.3.140 → 4.3.142

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 (162) hide show
  1. package/dist/{chunk-JVQDTPYR.js → chunk-3EWLK53W.js} +18 -9
  2. package/dist/{chunk-JVQDTPYR.js.map → chunk-3EWLK53W.js.map} +1 -1
  3. package/dist/{chunk-5WNYQQPQ.js → chunk-QT366Y52.js} +4 -3
  4. package/dist/{chunk-5WNYQQPQ.js.map → chunk-QT366Y52.js.map} +1 -1
  5. package/dist/{chunk-PQAHDHVY.js → chunk-WJ4S4TOY.js} +3 -2
  6. package/dist/{chunk-PQAHDHVY.js.map → chunk-WJ4S4TOY.js.map} +1 -1
  7. package/dist/{chunk-B2WQ5E5J.js → chunk-YNWFCUMR.js} +2 -1
  8. package/dist/{chunk-B2WQ5E5J.js.map → chunk-YNWFCUMR.js.map} +1 -1
  9. package/dist/cli.js +2248 -182
  10. package/dist/cli.js.map +1 -1
  11. package/dist/{devtools-VBUDNGEI.js → devtools-4TI4D7F2.js} +3 -2
  12. package/dist/{devtools-VBUDNGEI.js.map → devtools-4TI4D7F2.js.map} +1 -1
  13. package/dist/{dist-7DAPKZGX.js → dist-VXOVSHZ5.js} +3 -2
  14. package/dist/{dist-7DAPKZGX.js.map → dist-VXOVSHZ5.js.map} +1 -1
  15. package/dist/{ignore-3AEIALHQ.js → ignore-76P4EAAU.js} +3 -2
  16. package/dist/{ignore-3AEIALHQ.js.map → ignore-76P4EAAU.js.map} +1 -1
  17. package/dist/index.js +21 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/{out-D65DTPFZ.js → out-XEXARMKS.js} +3 -2
  20. package/dist/{out-D65DTPFZ.js.map → out-XEXARMKS.js.map} +1 -1
  21. package/dist/pixel-WPYTQADG.js +14 -0
  22. package/dist/{pixel-fix-ALWXCLTS.js → pixel-fix-4WGZAJ5W.js} +4 -3
  23. package/dist/{pixel-fix-ALWXCLTS.js.map → pixel-fix-4WGZAJ5W.js.map} +1 -1
  24. package/package.json +10 -11
  25. package/dist/audio.d.ts +0 -21
  26. package/dist/audio.d.ts.map +0 -1
  27. package/dist/audio.js +0 -231
  28. package/dist/audio.js.map +0 -1
  29. package/dist/audio.test.d.ts +0 -2
  30. package/dist/audio.test.d.ts.map +0 -1
  31. package/dist/audio.test.js +0 -13
  32. package/dist/audio.test.js.map +0 -1
  33. package/dist/auto-update.d.ts +0 -24
  34. package/dist/auto-update.d.ts.map +0 -1
  35. package/dist/auto-update.js +0 -231
  36. package/dist/auto-update.js.map +0 -1
  37. package/dist/banner.d.ts +0 -17
  38. package/dist/banner.d.ts.map +0 -1
  39. package/dist/banner.js +0 -25
  40. package/dist/banner.js.map +0 -1
  41. package/dist/boss-footer.d.ts +0 -25
  42. package/dist/boss-footer.d.ts.map +0 -1
  43. package/dist/boss-footer.js +0 -107
  44. package/dist/boss-footer.js.map +0 -1
  45. package/dist/boss-phrases.d.ts +0 -9
  46. package/dist/boss-phrases.d.ts.map +0 -1
  47. package/dist/boss-phrases.js +0 -71
  48. package/dist/boss-phrases.js.map +0 -1
  49. package/dist/boss-store.d.ts +0 -245
  50. package/dist/boss-store.d.ts.map +0 -1
  51. package/dist/boss-store.js +0 -623
  52. package/dist/boss-store.js.map +0 -1
  53. package/dist/boss-system-prompt.d.ts +0 -3
  54. package/dist/boss-system-prompt.d.ts.map +0 -1
  55. package/dist/boss-system-prompt.js +0 -180
  56. package/dist/boss-system-prompt.js.map +0 -1
  57. package/dist/boss-tasks-overlay.d.ts +0 -22
  58. package/dist/boss-tasks-overlay.d.ts.map +0 -1
  59. package/dist/boss-tasks-overlay.js +0 -157
  60. package/dist/boss-tasks-overlay.js.map +0 -1
  61. package/dist/branding.d.ts +0 -32
  62. package/dist/branding.d.ts.map +0 -1
  63. package/dist/branding.js +0 -59
  64. package/dist/branding.js.map +0 -1
  65. package/dist/cli.d.ts +0 -3
  66. package/dist/cli.d.ts.map +0 -1
  67. package/dist/cli.smoke.test.d.ts +0 -2
  68. package/dist/cli.smoke.test.d.ts.map +0 -1
  69. package/dist/cli.smoke.test.js +0 -48
  70. package/dist/cli.smoke.test.js.map +0 -1
  71. package/dist/colors.d.ts +0 -14
  72. package/dist/colors.d.ts.map +0 -1
  73. package/dist/colors.js +0 -31
  74. package/dist/colors.js.map +0 -1
  75. package/dist/discover.d.ts +0 -13
  76. package/dist/discover.d.ts.map +0 -1
  77. package/dist/discover.js +0 -92
  78. package/dist/discover.js.map +0 -1
  79. package/dist/event-queue.d.ts +0 -16
  80. package/dist/event-queue.d.ts.map +0 -1
  81. package/dist/event-queue.js +0 -39
  82. package/dist/event-queue.js.map +0 -1
  83. package/dist/index.d.ts +0 -6
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/link-command.d.ts +0 -2
  86. package/dist/link-command.d.ts.map +0 -1
  87. package/dist/link-command.js +0 -120
  88. package/dist/link-command.js.map +0 -1
  89. package/dist/links.d.ts +0 -11
  90. package/dist/links.d.ts.map +0 -1
  91. package/dist/links.js +0 -22
  92. package/dist/links.js.map +0 -1
  93. package/dist/logger.d.ts +0 -41
  94. package/dist/logger.d.ts.map +0 -1
  95. package/dist/logger.js +0 -112
  96. package/dist/logger.js.map +0 -1
  97. package/dist/orchestrator-app.d.ts +0 -15
  98. package/dist/orchestrator-app.d.ts.map +0 -1
  99. package/dist/orchestrator-app.js +0 -599
  100. package/dist/orchestrator-app.js.map +0 -1
  101. package/dist/orchestrator.d.ts +0 -147
  102. package/dist/orchestrator.d.ts.map +0 -1
  103. package/dist/orchestrator.js +0 -707
  104. package/dist/orchestrator.js.map +0 -1
  105. package/dist/orchestrator.test.d.ts +0 -2
  106. package/dist/orchestrator.test.d.ts.map +0 -1
  107. package/dist/orchestrator.test.js +0 -55
  108. package/dist/orchestrator.test.js.map +0 -1
  109. package/dist/pixel-WB6VRJWP.js +0 -13
  110. package/dist/radio-picker.d.ts +0 -20
  111. package/dist/radio-picker.d.ts.map +0 -1
  112. package/dist/radio-picker.js +0 -31
  113. package/dist/radio-picker.js.map +0 -1
  114. package/dist/radio.d.ts +0 -43
  115. package/dist/radio.d.ts.map +0 -1
  116. package/dist/radio.js +0 -150
  117. package/dist/radio.js.map +0 -1
  118. package/dist/sessions.d.ts +0 -21
  119. package/dist/sessions.d.ts.map +0 -1
  120. package/dist/sessions.js +0 -122
  121. package/dist/sessions.js.map +0 -1
  122. package/dist/settings.d.ts +0 -11
  123. package/dist/settings.d.ts.map +0 -1
  124. package/dist/settings.js +0 -38
  125. package/dist/settings.js.map +0 -1
  126. package/dist/slash-commands.d.ts +0 -19
  127. package/dist/slash-commands.d.ts.map +0 -1
  128. package/dist/slash-commands.js +0 -76
  129. package/dist/slash-commands.js.map +0 -1
  130. package/dist/splash.d.ts +0 -21
  131. package/dist/splash.d.ts.map +0 -1
  132. package/dist/splash.js +0 -137
  133. package/dist/splash.js.map +0 -1
  134. package/dist/task-tools.d.ts +0 -18
  135. package/dist/task-tools.d.ts.map +0 -1
  136. package/dist/task-tools.js +0 -172
  137. package/dist/task-tools.js.map +0 -1
  138. package/dist/tasks-store.d.ts +0 -66
  139. package/dist/tasks-store.d.ts.map +0 -1
  140. package/dist/tasks-store.js +0 -199
  141. package/dist/tasks-store.js.map +0 -1
  142. package/dist/tasks-store.test.d.ts +0 -2
  143. package/dist/tasks-store.test.d.ts.map +0 -1
  144. package/dist/tasks-store.test.js +0 -138
  145. package/dist/tasks-store.test.js.map +0 -1
  146. package/dist/tool-formatters.d.ts +0 -7
  147. package/dist/tool-formatters.d.ts.map +0 -1
  148. package/dist/tool-formatters.js +0 -111
  149. package/dist/tool-formatters.js.map +0 -1
  150. package/dist/tools.d.ts +0 -26
  151. package/dist/tools.d.ts.map +0 -1
  152. package/dist/tools.js +0 -133
  153. package/dist/tools.js.map +0 -1
  154. package/dist/types.d.ts +0 -32
  155. package/dist/types.d.ts.map +0 -1
  156. package/dist/types.js +0 -2
  157. package/dist/types.js.map +0 -1
  158. package/dist/worker.d.ts +0 -47
  159. package/dist/worker.d.ts.map +0 -1
  160. package/dist/worker.js +0 -123
  161. package/dist/worker.js.map +0 -1
  162. /package/dist/{pixel-WB6VRJWP.js.map → pixel-WPYTQADG.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,203 +1,2269 @@
1
1
  #!/usr/bin/env node
2
- import path from "node:path";
3
- import chalk from "chalk";
4
- import { GGBoss } from "./orchestrator.js";
5
- import { loadLinks } from "./links.js";
6
- import { runLinkCommand } from "./link-command.js";
7
- import { COLORS, clearScreen } from "./branding.js";
8
- import { renderBossApp } from "./orchestrator-app.js";
9
- import { loadSettings } from "./settings.js";
10
- import { showSplash } from "./splash.js";
11
- import { initLogger, log } from "./logger.js";
12
- import { VERSION } from "./branding.js";
13
- import { checkAndAutoUpdate } from "./auto-update.js";
14
- import { stopRadio } from "./radio.js";
15
- function parseProjectSpec(raw) {
16
- const eq = raw.indexOf("=");
17
- if (eq > 0) {
18
- const name = raw.slice(0, eq);
19
- const cwd = path.resolve(raw.slice(eq + 1));
20
- return { name, cwd };
2
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
3
+ import {
4
+ ActivityIndicator,
5
+ AnimationProvider,
6
+ AssistantMessage,
7
+ Box_default,
8
+ CompactionDone,
9
+ CompactionSpinner,
10
+ GGBoss,
11
+ InputArea,
12
+ MessageResponse,
13
+ ModelSelector,
14
+ SelectList,
15
+ Static,
16
+ StreamingArea,
17
+ TerminalSizeProvider,
18
+ Text,
19
+ ThemeContext,
20
+ ToolExecution,
21
+ ToolUseLoader,
22
+ UserMessage,
23
+ bossStore,
24
+ getAppPaths,
25
+ getContextWindow,
26
+ getSplashAudioDurationMs,
27
+ initLogger,
28
+ loadSettings,
29
+ loadTheme,
30
+ log,
31
+ playSplashAudio,
32
+ render_default,
33
+ require_jsx_runtime,
34
+ require_react,
35
+ tasksStore,
36
+ useAnimationActive,
37
+ useAnimationTick,
38
+ useBossState,
39
+ useDoublePress,
40
+ useTasksState,
41
+ useTerminalSize,
42
+ useTheme,
43
+ use_app_default,
44
+ use_input_default,
45
+ use_stdout_default
46
+ } from "./chunk-3EWLK53W.js";
47
+ import "./chunk-QT366Y52.js";
48
+ import {
49
+ source_default
50
+ } from "./chunk-WJ4S4TOY.js";
51
+ import {
52
+ __toESM,
53
+ init_esm_shims
54
+ } from "./chunk-YNWFCUMR.js";
55
+
56
+ // src/cli.ts
57
+ init_esm_shims();
58
+ import path4 from "path";
59
+
60
+ // src/links.ts
61
+ init_esm_shims();
62
+ import fs from "fs/promises";
63
+ import path from "path";
64
+ function getLinksPath() {
65
+ return path.join(getAppPaths().agentDir, "boss", "links.json");
66
+ }
67
+ async function loadLinks() {
68
+ try {
69
+ const content = await fs.readFile(getLinksPath(), "utf-8");
70
+ const parsed = JSON.parse(content);
71
+ return { projects: parsed.projects ?? [] };
72
+ } catch {
73
+ return { projects: [] };
74
+ }
75
+ }
76
+ async function saveLinks(links) {
77
+ const p = getLinksPath();
78
+ await fs.mkdir(path.dirname(p), { recursive: true, mode: 448 });
79
+ await fs.writeFile(p, JSON.stringify(links, null, 2), "utf-8");
80
+ }
81
+
82
+ // src/link-command.tsx
83
+ init_esm_shims();
84
+ var import_react2 = __toESM(require_react(), 1);
85
+
86
+ // src/discover.ts
87
+ init_esm_shims();
88
+ import fs2 from "fs/promises";
89
+ import path2 from "path";
90
+ async function discoverProjects() {
91
+ const sessionsDir = getAppPaths().sessionsDir;
92
+ let entries;
93
+ try {
94
+ entries = await fs2.readdir(sessionsDir);
95
+ } catch {
96
+ return [];
97
+ }
98
+ const results = [];
99
+ for (const entry of entries) {
100
+ const dir = path2.join(sessionsDir, entry);
101
+ let stat;
102
+ try {
103
+ stat = await fs2.stat(dir);
104
+ } catch {
105
+ continue;
106
+ }
107
+ if (!stat.isDirectory()) continue;
108
+ let files;
109
+ try {
110
+ files = await fs2.readdir(dir);
111
+ } catch {
112
+ continue;
21
113
  }
22
- const cwd = path.resolve(raw);
23
- return { name: path.basename(cwd), cwd };
114
+ const sessionFiles = files.filter((f) => f.endsWith(".jsonl"));
115
+ if (sessionFiles.length === 0) continue;
116
+ let maxMtime = 0;
117
+ for (const f of sessionFiles) {
118
+ try {
119
+ const s = await fs2.stat(path2.join(dir, f));
120
+ if (s.mtimeMs > maxMtime) maxMtime = s.mtimeMs;
121
+ } catch {
122
+ }
123
+ }
124
+ const decoded = "/" + entry.replace(/_/g, "/");
125
+ try {
126
+ const pathStat = await fs2.stat(decoded);
127
+ if (!pathStat.isDirectory()) continue;
128
+ } catch {
129
+ continue;
130
+ }
131
+ results.push({
132
+ name: path2.basename(decoded),
133
+ path: decoded,
134
+ lastActiveMs: maxMtime,
135
+ lastActiveDisplay: formatRelativeTime(maxMtime)
136
+ });
137
+ }
138
+ results.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
139
+ return results;
24
140
  }
25
- function parseArgs(argv) {
26
- const args = {
27
- projects: [],
28
- };
29
- for (let i = 0; i < argv.length; i++) {
30
- const a = argv[i];
31
- if (a === "--project" || a === "-p") {
32
- const v = argv[++i];
33
- if (!v)
34
- throw new Error("--project requires a value");
35
- args.projects.push(parseProjectSpec(v));
36
- }
37
- else if (a === "--boss-model") {
38
- const v = argv[++i];
39
- if (!v)
40
- throw new Error("--boss-model requires a value");
41
- args.bossModel = v;
141
+ function formatRelativeTime(ms) {
142
+ if (ms === 0) return "\u2014";
143
+ const diff = Date.now() - ms;
144
+ if (diff < 6e4) return "just now";
145
+ const min = 6e4;
146
+ const hour = 60 * min;
147
+ const day = 24 * hour;
148
+ const week = 7 * day;
149
+ const month = 30 * day;
150
+ if (diff < hour) return `${Math.floor(diff / min)}m ago`;
151
+ if (diff < day) return `${Math.floor(diff / hour)}h ago`;
152
+ if (diff < week) return `${Math.floor(diff / day)}d ago`;
153
+ if (diff < month) return `${Math.floor(diff / week)}w ago`;
154
+ return `${Math.floor(diff / month)}mo ago`;
155
+ }
156
+
157
+ // src/banner.tsx
158
+ init_esm_shims();
159
+ var import_react = __toESM(require_react(), 1);
160
+
161
+ // src/branding.ts
162
+ init_esm_shims();
163
+
164
+ // package.json
165
+ var package_default = {
166
+ name: "@kenkaiiii/gg-boss",
167
+ version: "4.3.142",
168
+ type: "module",
169
+ description: "Orchestrator agent that drives multiple ggcoder sessions across projects from a single chat",
170
+ license: "MIT",
171
+ repository: {
172
+ type: "git",
173
+ url: "git+https://github.com/kenkaiiii/gg-framework.git",
174
+ directory: "packages/gg-boss"
175
+ },
176
+ bin: {
177
+ ggboss: "./dist/cli.js"
178
+ },
179
+ exports: {
180
+ ".": {
181
+ import: "./dist/index.js",
182
+ types: "./dist/index.d.ts"
183
+ }
184
+ },
185
+ files: [
186
+ "dist"
187
+ ],
188
+ scripts: {
189
+ build: "tsup && cp -R assets/. dist/ && chmod +x dist/cli.js",
190
+ check: "tsc --noEmit",
191
+ test: "vitest run"
192
+ },
193
+ devDependencies: {
194
+ "@kenkaiiii/gg-agent": "workspace:*",
195
+ "@kenkaiiii/gg-ai": "workspace:*",
196
+ "@kenkaiiii/ggcoder": "workspace:*",
197
+ "@types/node": "^25.6.0",
198
+ "@types/react": "^19.2.14",
199
+ chalk: "^5.6.2",
200
+ ink: "^7.0.2",
201
+ react: "^19.2.5",
202
+ tsup: "^8.5.1",
203
+ typescript: "^6.0.3",
204
+ vitest: "^4.1.4",
205
+ zod: "^4.4.3"
206
+ },
207
+ publishConfig: {
208
+ access: "public"
209
+ }
210
+ };
211
+
212
+ // src/branding.ts
213
+ var VERSION = package_default.version;
214
+ var BRAND = "GG Boss";
215
+ var AUTHOR = "Ken Kai";
216
+ var LOGO_LINES = [" \u2584\u2580\u2580\u2580 \u2584\u2580\u2580\u2580", " \u2588 \u2580\u2588 \u2588 \u2580\u2588", " \u2580\u2584\u2584\u2580 \u2580\u2584\u2584\u2580"];
217
+ var LOGO_GAP = " ";
218
+ var GRADIENT = [
219
+ "#dc2626",
220
+ // red-600
221
+ "#e11d48",
222
+ // rose-600
223
+ "#be185d",
224
+ // pink-700
225
+ "#a21caf",
226
+ // fuchsia-700
227
+ "#c026d3",
228
+ // fuchsia-600
229
+ "#d946ef",
230
+ // fuchsia-500
231
+ "#c026d3",
232
+ // fuchsia-600 (back)
233
+ "#a21caf",
234
+ // fuchsia-700 (back)
235
+ "#be185d",
236
+ // pink-700 (back)
237
+ "#e11d48",
238
+ // rose-600 (back)
239
+ "#dc2626",
240
+ // red-600 (back)
241
+ "#b91c1c"
242
+ // red-700 (slight darker tail)
243
+ ];
244
+ var PULSE_COLORS = [
245
+ "#dc2626",
246
+ // crimson
247
+ "#e11d48",
248
+ // rose
249
+ "#be185d",
250
+ // wine
251
+ "#a21caf",
252
+ // magenta
253
+ "#c026d3",
254
+ // fuchsia
255
+ "#a21caf",
256
+ // back
257
+ "#be185d",
258
+ // back
259
+ "#e11d48"
260
+ // back
261
+ ];
262
+ var COLORS = {
263
+ primary: "#e11d48",
264
+ // crimson-rose — main brand color
265
+ accent: "#d946ef",
266
+ // fuchsia — secondary
267
+ text: "#e2e8f0",
268
+ textDim: "#6b7280",
269
+ success: "#4ade80",
270
+ warning: "#fbbf24",
271
+ error: "#f87171"
272
+ };
273
+ function clearScreen() {
274
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
275
+ }
276
+
277
+ // src/banner.tsx
278
+ var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
279
+ function BossBanner({ subtitle, hint, showShortcuts }) {
280
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[0] }),
283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, bold: true, children: BRAND }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: COLORS.textDim, children: [
286
+ " v",
287
+ VERSION
288
+ ] }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
291
+ ] }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[1] }),
294
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.accent, children: subtitle })
296
+ ] }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
298
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[2] }),
299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
300
+ showShortcuts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
301
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "^T" }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " tasks" }),
303
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "Tab" }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " scope" }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "\u21E7Tab" }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " thinking" }),
309
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "ESC" }),
311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " interrupt" })
312
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: hint ?? "" })
313
+ ] })
314
+ ] });
315
+ }
316
+ function GradientText({ text }) {
317
+ const chars = [];
318
+ let colorIdx = 0;
319
+ for (let i = 0; i < text.length; i++) {
320
+ const ch = text[i];
321
+ if (ch === " ") {
322
+ chars.push(ch);
323
+ } else {
324
+ const color = GRADIENT[colorIdx % GRADIENT.length];
325
+ chars.push(
326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color, children: ch }, i)
327
+ );
328
+ colorIdx++;
329
+ }
330
+ }
331
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: chars });
332
+ }
333
+
334
+ // src/link-command.tsx
335
+ var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
336
+ var VISIBLE_ROWS = 12;
337
+ function LinkScreen({ projects, initialSelected, onDone }) {
338
+ const [cursor, setCursor] = (0, import_react2.useState)(0);
339
+ const [selected, setSelected] = (0, import_react2.useState)(new Set(initialSelected));
340
+ const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
341
+ const visible = projects.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);
342
+ use_input_default((input, key) => {
343
+ if (key.ctrl && input === "c") {
344
+ onDone([], true);
345
+ return;
346
+ }
347
+ if (projects.length === 0) {
348
+ if (key.return || key.escape || input === "q") onDone([], true);
349
+ return;
350
+ }
351
+ if (key.upArrow) {
352
+ const next = Math.max(0, cursor - 1);
353
+ setCursor(next);
354
+ if (next < scrollOffset) setScrollOffset(next);
355
+ } else if (key.downArrow) {
356
+ const next = Math.min(projects.length - 1, cursor + 1);
357
+ setCursor(next);
358
+ if (next >= scrollOffset + VISIBLE_ROWS) setScrollOffset(next - VISIBLE_ROWS + 1);
359
+ } else if (input === " ") {
360
+ const p = projects[cursor];
361
+ if (!p) return;
362
+ const nextSet = new Set(selected);
363
+ if (nextSet.has(p.path)) nextSet.delete(p.path);
364
+ else nextSet.add(p.path);
365
+ setSelected(nextSet);
366
+ } else if (input === "a") {
367
+ const allSelected = projects.every((p) => selected.has(p.path));
368
+ setSelected(allSelected ? /* @__PURE__ */ new Set() : new Set(projects.map((p) => p.path)));
369
+ } else if (key.return) {
370
+ onDone(
371
+ projects.filter((p) => selected.has(p.path)).map((p) => p.path),
372
+ false
373
+ );
374
+ } else if (key.escape || input === "q") {
375
+ onDone([], true);
376
+ }
377
+ });
378
+ const subtitle = projects.length === 0 ? "Link projects" : `Link projects \xB7 ${projects.length} discovered \xB7 ${selected.size} selected`;
379
+ const hint = "\u2191\u2193 navigate \xB7 space toggle \xB7 a all \xB7 enter save \xB7 esc cancel";
380
+ if (projects.length === 0) {
381
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingX: 2, children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BossBanner, { subtitle: "Link projects", hint: "No projects yet" }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginLeft: 2, children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: "No ggcoder projects found in ~/.gg/sessions/." }),
385
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: COLORS.textDim, children: [
386
+ "Run ggcoder in a project at least once, then re-run",
387
+ " ",
388
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.accent, children: "ggboss link" }),
389
+ "."
390
+ ] }),
391
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: "Press any key to exit." }) })
392
+ ] })
393
+ ] });
394
+ }
395
+ const showingTop = scrollOffset > 0;
396
+ const showingBottom = scrollOffset + VISIBLE_ROWS < projects.length;
397
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingX: 2, children: [
398
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BossBanner, { subtitle, hint }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginLeft: 2, children: [
400
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " \u2191 more above" }),
401
+ visible.map((p, i) => {
402
+ const realIndex = scrollOffset + i;
403
+ const isCursor = realIndex === cursor;
404
+ const isSelected = selected.has(p.path);
405
+ const checkbox = isSelected ? "[\u2713]" : "[ ]";
406
+ const arrow = isCursor ? "\u276F" : " ";
407
+ const nameColor = isCursor ? COLORS.primary : isSelected ? COLORS.success : COLORS.text;
408
+ const checkboxColor = isSelected ? COLORS.success : COLORS.textDim;
409
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
410
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.primary, children: arrow }),
411
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: checkboxColor, children: checkbox }),
413
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
414
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: nameColor, bold: isCursor, children: p.name }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " " }),
416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: p.lastActiveDisplay })
417
+ ] }, p.path);
418
+ }),
419
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " \u2193 more below" })
420
+ ] })
421
+ ] });
422
+ }
423
+ function LinkApp({ projects, initialSelected, resolve }) {
424
+ const { exit } = use_app_default();
425
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
426
+ LinkScreen,
427
+ {
428
+ projects,
429
+ initialSelected,
430
+ onDone: (selected, cancelled) => {
431
+ resolve({ selected, cancelled });
432
+ exit();
433
+ }
434
+ }
435
+ );
436
+ }
437
+ async function runLinkCommand() {
438
+ const projects = await discoverProjects();
439
+ const links = await loadLinks();
440
+ const initialSelected = new Set(links.projects.map((p) => p.cwd));
441
+ clearScreen();
442
+ const result = await new Promise((resolve) => {
443
+ const { waitUntilExit } = render_default(
444
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LinkApp, { projects, initialSelected, resolve })
445
+ );
446
+ void waitUntilExit();
447
+ });
448
+ if (result.cancelled) {
449
+ process.stdout.write(source_default.hex(COLORS.textDim)("\nCancelled. No changes saved.\n"));
450
+ return;
451
+ }
452
+ const linked = result.selected.map((path5) => projects.find((p) => p.path === path5)).filter((p) => Boolean(p)).map((p) => ({ name: p.name, cwd: p.path }));
453
+ await saveLinks({ projects: linked });
454
+ process.stdout.write("\n");
455
+ if (linked.length === 0) {
456
+ process.stdout.write(source_default.hex(COLORS.warning)("Cleared linked projects.\n"));
457
+ } else {
458
+ process.stdout.write(
459
+ source_default.hex(COLORS.success)(
460
+ `Linked ${linked.length} project${linked.length === 1 ? "" : "s"}:
461
+ `
462
+ )
463
+ );
464
+ for (const p of linked) {
465
+ process.stdout.write(
466
+ " " + source_default.hex(COLORS.primary)("\xB7") + " " + source_default.hex(COLORS.text)(p.name) + "\n"
467
+ );
468
+ }
469
+ process.stdout.write("\n");
470
+ process.stdout.write(
471
+ source_default.hex(COLORS.textDim)(`Run `) + source_default.hex(COLORS.accent)("ggboss") + source_default.hex(COLORS.textDim)(` to start the orchestrator.
472
+ `)
473
+ );
474
+ }
475
+ }
476
+
477
+ // src/orchestrator-app.tsx
478
+ init_esm_shims();
479
+ var import_react11 = __toESM(require_react(), 1);
480
+
481
+ // ../ggcoder/dist/ui/components/index.js
482
+ init_esm_shims();
483
+
484
+ // ../ggcoder/dist/ui/components/DiffView.js
485
+ init_esm_shims();
486
+ var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
487
+ var import_react3 = __toESM(require_react(), 1);
488
+
489
+ // ../ggcoder/dist/ui/components/Overlay.js
490
+ init_esm_shims();
491
+ var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
492
+ var import_react4 = __toESM(require_react(), 1);
493
+
494
+ // ../ggcoder/dist/ui/components/SessionSelector.js
495
+ init_esm_shims();
496
+ var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
497
+ var import_react5 = __toESM(require_react(), 1);
498
+
499
+ // ../ggcoder/dist/ui/components/SettingsSelector.js
500
+ init_esm_shims();
501
+ var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
502
+ var import_react6 = __toESM(require_react(), 1);
503
+
504
+ // ../ggcoder/dist/ui/components/ThinkingIndicator.js
505
+ init_esm_shims();
506
+ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
507
+ var import_react7 = __toESM(require_react(), 1);
508
+
509
+ // src/boss-footer.tsx
510
+ init_esm_shims();
511
+ var import_react8 = __toESM(require_react(), 1);
512
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
513
+ var PARTIAL_BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
514
+ var LIGHT_SHADE = "\u2591";
515
+ var SHORT_MODELS = {
516
+ "claude-opus-4-7": "Opus",
517
+ "claude-sonnet-4-6": "Sonnet",
518
+ "claude-haiku-4-5": "Haiku",
519
+ "claude-haiku-4-5-20251001": "Haiku",
520
+ "gpt-5.5": "GPT-5.5",
521
+ "gpt-5.5-pro": "GPT-5.5 Pro",
522
+ "gpt-5.4": "GPT-5.4",
523
+ "gpt-5.4-mini": "GPT-5.4 Mini",
524
+ "gpt-5.3-codex": "GPT-5.3 Codex"
525
+ };
526
+ function shortModel(model) {
527
+ return SHORT_MODELS[model] ?? model;
528
+ }
529
+ function getContextPercent(model, tokensIn) {
530
+ const limit = getContextWindow(model);
531
+ if (!limit || tokensIn === 0) return 0;
532
+ return Math.round(tokensIn / limit * 100);
533
+ }
534
+ var SHORT_RADIO = {
535
+ "somafm-groove-salad": "Groove Salad",
536
+ "somafm-drone-zone": "Drone Zone",
537
+ "radio-paradise": "Radio Paradise",
538
+ "george-fm": "George FM"
539
+ };
540
+ function BossFooter({
541
+ bossModel,
542
+ workerModel,
543
+ tokensIn,
544
+ exitPending,
545
+ bossThinkingLevel,
546
+ updatePending,
547
+ currentRadioStationId
548
+ }) {
549
+ const theme = useTheme();
550
+ const { columns } = useTerminalSize();
551
+ if (exitPending) {
552
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.warning, children: "Press Ctrl+C again to exit" }) });
553
+ }
554
+ const contextPct = getContextPercent(bossModel, tokensIn);
555
+ const contextColor = contextPct >= 80 ? theme.error : contextPct >= 50 ? theme.warning : theme.success;
556
+ const sep = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.border, children: " \u2502 " });
557
+ const barWidth = 8;
558
+ const fillFloat = Math.min(contextPct / 100 * barWidth, barWidth);
559
+ const barChars = [];
560
+ for (let i = 0; i < barWidth; i++) {
561
+ const cellFill = Math.max(0, Math.min(1, fillFloat - i));
562
+ const eighths = Math.round(cellFill * 8);
563
+ if (eighths === 8) {
564
+ barChars.push(
565
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[8] }, i)
566
+ );
567
+ } else if (eighths > 0) {
568
+ barChars.push(
569
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[eighths] }, i)
570
+ );
571
+ } else {
572
+ barChars.push(
573
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: LIGHT_SHADE }, i)
574
+ );
575
+ }
576
+ }
577
+ const radioName = currentRadioStationId ? SHORT_RADIO[currentRadioStationId] ?? currentRadioStationId : null;
578
+ const bossM = shortModel(bossModel);
579
+ const wkrM = shortModel(workerModel);
580
+ const estFull = 2 + 12 + // bar + " 99%"
581
+ 3 + 5 + bossM.length + // " │ boss <model>"
582
+ 3 + 8 + wkrM.length + // " │ workers <model>"
583
+ 3 + 12 + // " │ Thinking off"
584
+ (radioName ? 3 + 2 + radioName.length : 0) + // " │ ♪ Name"
585
+ (updatePending ? 3 + 28 : 0);
586
+ const dropLabels = estFull > columns;
587
+ const dropThinking = estFull > columns + 14;
588
+ const useShortUpdate = updatePending && estFull > columns + 6;
589
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { paddingX: 1, width: columns, children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1 }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexShrink: 0, children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: barChars }),
593
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: contextColor, children: [
594
+ " ",
595
+ contextPct,
596
+ "%"
597
+ ] }),
598
+ sep,
599
+ !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "boss " }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.primary, bold: true, children: bossM }),
601
+ sep,
602
+ !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "workers " }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.accent, bold: true, children: wkrM }),
604
+ !dropThinking && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
605
+ sep,
606
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: bossThinkingLevel ? theme.accent : theme.textDim, children: bossThinkingLevel ? "Thinking on" : "Thinking off" })
607
+ ] }),
608
+ radioName && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
609
+ sep,
610
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.secondary ?? theme.accent, children: [
611
+ "\u266A ",
612
+ radioName
613
+ ] })
614
+ ] }),
615
+ updatePending && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
616
+ sep,
617
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.success, bold: true, wrap: "truncate", children: useShortUpdate ? "Update ready" : "Update ready. Restart GG Boss." })
618
+ ] })
619
+ ] })
620
+ ] });
621
+ }
622
+
623
+ // src/slash-commands.ts
624
+ init_esm_shims();
625
+ var BOSS_SLASH_COMMANDS = [
626
+ { name: "help", aliases: ["?"], description: "Show available commands" },
627
+ { name: "model-boss", aliases: [], description: "Switch the orchestrator's model" },
628
+ { name: "model-workers", aliases: [], description: "Switch every worker's model" },
629
+ { name: "compact", aliases: [], description: "Compact the boss's context now" },
630
+ { name: "clear", aliases: [], description: "Clear chat history and terminal" },
631
+ { name: "radio", aliases: [], description: "Stream a free internet radio station" },
632
+ { name: "quit", aliases: ["q", "exit"], description: "Exit gg-boss" }
633
+ ];
634
+ function isSlashCommand(value) {
635
+ return value.startsWith("/") && !value.startsWith("//");
636
+ }
637
+ function parseSlash(value) {
638
+ if (!isSlashCommand(value)) return null;
639
+ const rest = value.slice(1).trim();
640
+ if (!rest) return null;
641
+ const space = rest.indexOf(" ");
642
+ if (space === -1) return { name: rest.toLowerCase(), args: "" };
643
+ return { name: rest.slice(0, space).toLowerCase(), args: rest.slice(space + 1).trim() };
644
+ }
645
+ function canonicalName(name) {
646
+ for (const cmd of BOSS_SLASH_COMMANDS) {
647
+ if (cmd.name === name) return cmd.name;
648
+ if (cmd.aliases.includes(name)) return cmd.name;
649
+ }
650
+ return null;
651
+ }
652
+ function buildHelpText() {
653
+ const lines = ["**gg-boss commands**", ""];
654
+ for (const cmd of BOSS_SLASH_COMMANDS) {
655
+ const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : "";
656
+ lines.push(`- \`/${cmd.name}\`${aliases} \u2014 ${cmd.description}`);
657
+ }
658
+ lines.push("");
659
+ lines.push("**Global keybindings**");
660
+ lines.push("- `Ctrl+T` \u2014 open the Tasks pane");
661
+ lines.push("- `Tab` \u2014 switch project scope (All / per-project pill in the input)");
662
+ lines.push("- `Shift+Tab` \u2014 toggle the boss's extended thinking on/off");
663
+ lines.push("- `Esc` \u2014 interrupt the boss while it's running");
664
+ lines.push("- `Ctrl+C` (twice) \u2014 exit");
665
+ lines.push("");
666
+ lines.push("**Inside the Tasks pane (Ctrl+T)**");
667
+ lines.push("- `\u2191` / `\u2193` (or `k` / `j`) \u2014 navigate tasks");
668
+ lines.push("- `r` \u2014 run all pending and blocked tasks across idle workers");
669
+ lines.push("- `d` \u2014 delete the selected task");
670
+ lines.push("- `Esc` \u2014 close the Tasks pane");
671
+ lines.push("");
672
+ lines.push("**Inside model pickers (`/model-boss`, `/model-workers`)**");
673
+ lines.push("- `\u2191` / `\u2193` \u2014 navigate models");
674
+ lines.push("- `Enter` \u2014 select");
675
+ lines.push("- `Esc` \u2014 cancel");
676
+ lines.push("");
677
+ lines.push("**Radio** (`/radio`)");
678
+ lines.push("- Pick a station to stream while you work, or select `Off` to stop.");
679
+ lines.push("- Requires `mpv` (recommended), `ffplay`, `mpg123`, or `vlc/cvlc` installed.");
680
+ lines.push("");
681
+ lines.push("**Input area**");
682
+ lines.push("- `\u2191` / `\u2193` \u2014 recall previous prompts (when input is empty)");
683
+ lines.push("- `Enter` \u2014 send \xB7 `Shift+Enter` \u2014 newline");
684
+ lines.push("- `/` \u2014 open the slash-command menu (Tab / arrows to pick, Enter to insert)");
685
+ return lines.join("\n");
686
+ }
687
+
688
+ // src/tool-formatters.ts
689
+ init_esm_shims();
690
+
691
+ // src/colors.ts
692
+ init_esm_shims();
693
+ var PROJECT_COLORS = [
694
+ "#60a5fa",
695
+ // blue
696
+ "#a78bfa",
697
+ // violet
698
+ "#4ade80",
699
+ // green
700
+ "#fbbf24",
701
+ // amber
702
+ "#f472b6",
703
+ // pink
704
+ "#22d3ee",
705
+ // cyan
706
+ "#fb923c",
707
+ // orange
708
+ "#34d399"
709
+ // emerald
710
+ ];
711
+ function stableHash(s) {
712
+ let h = 0;
713
+ for (let i = 0; i < s.length; i++) {
714
+ h = h * 31 + s.charCodeAt(i) | 0;
715
+ }
716
+ return Math.abs(h);
717
+ }
718
+ function projectColor(name) {
719
+ return PROJECT_COLORS[stableHash(name) % PROJECT_COLORS.length];
720
+ }
721
+
722
+ // src/tool-formatters.ts
723
+ function truncate(s, max) {
724
+ if (max <= 1) return "\u2026";
725
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
726
+ }
727
+ function promptWorkerDetailLen(project) {
728
+ const cols = process.stdout.columns ?? 80;
729
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
730
+ return Math.max(20, cols - fixed);
731
+ }
732
+ var bossToolFormatters = {
733
+ formatLabel(name) {
734
+ switch (name) {
735
+ case "list_workers":
736
+ return "List Workers";
737
+ case "get_worker_status":
738
+ return "Worker Status";
739
+ case "prompt_worker":
740
+ return "Prompt Worker";
741
+ case "get_worker_summary":
742
+ return "Worker Summary";
743
+ default:
744
+ return void 0;
745
+ }
746
+ },
747
+ formatDetail(name, args) {
748
+ switch (name) {
749
+ case "list_workers":
750
+ return "";
751
+ case "get_worker_status":
752
+ case "get_worker_summary":
753
+ return truncate(String(args.project ?? ""), 40);
754
+ case "prompt_worker": {
755
+ const project = String(args.project ?? "");
756
+ const message = String(args.message ?? "").replace(/\s+/g, " ");
757
+ const fresh = args.fresh === true;
758
+ const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
759
+ const truncMsg = truncate(message, Math.max(15, maxMsg));
760
+ const head = fresh ? "fresh \xB7 " : "";
761
+ return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
762
+ }
763
+ default:
764
+ return void 0;
765
+ }
766
+ },
767
+ formatInline(name, result, isError) {
768
+ if (isError) return void 0;
769
+ switch (name) {
770
+ case "list_workers": {
771
+ const lines = result.split("\n").filter((l) => l.startsWith("-"));
772
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
773
+ }
774
+ case "prompt_worker": {
775
+ if (result.includes("currently working")) {
776
+ return { text: "busy \u2014 skipped", color: "#fbbf24" };
42
777
  }
43
- else if (a === "--worker-model") {
44
- const v = argv[++i];
45
- if (!v)
46
- throw new Error("--worker-model requires a value");
47
- args.workerModel = v;
778
+ if (result.includes("Unknown project")) {
779
+ return { text: "unknown project", color: "#f87171" };
48
780
  }
49
- else if (a === "--resume") {
50
- const v = argv[++i];
51
- if (!v)
52
- throw new Error("--resume requires a session id");
53
- args.resumeSessionId = v;
781
+ const project = String(result.match(/"([^"]+)"/)?.[1] ?? "");
782
+ const color = project ? projectColor(project) : "#e11d48";
783
+ return { text: "dispatched", color };
784
+ }
785
+ case "get_worker_status": {
786
+ const parts = result.split(":");
787
+ if (parts.length < 2) return void 0;
788
+ const status = parts.slice(1).join(":").trim();
789
+ const project = parts[0].trim();
790
+ return { text: status, color: projectColor(project) };
791
+ }
792
+ case "get_worker_summary": {
793
+ const turnMatch = result.match(/Turn:\s*(\d+)/);
794
+ const projectMatch = result.match(/Project:\s*(.+)/);
795
+ const toolsMatch = result.match(/Tools used:\s*(.+)/);
796
+ const tools = toolsMatch ? toolsMatch[1] : "";
797
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
798
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
799
+ const tCount = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
800
+ const summary = [turn, tCount].filter(Boolean).join(" \xB7 ");
801
+ if (!summary) return void 0;
802
+ const project = projectMatch ? projectMatch[1].trim() : "";
803
+ return project ? { text: summary, color: projectColor(project) } : { text: summary, color: "#9ca3af" };
804
+ }
805
+ default:
806
+ return void 0;
807
+ }
808
+ }
809
+ };
810
+
811
+ // src/boss-phrases.ts
812
+ init_esm_shims();
813
+ var BOSS_PHRASES = {
814
+ // Generic between-states fallback. Probably never shown but keep for safety.
815
+ idle: ["Standing by", "Waiting for orders", "On call"],
816
+ // Boss has issued a request, waiting for the LLM to begin streaming.
817
+ waiting: [
818
+ "Briefing",
819
+ "Reviewing the room",
820
+ "Triaging",
821
+ "Lining up the brief",
822
+ "Surveying projects",
823
+ "Reading the room",
824
+ "Picking the right hand",
825
+ "Marshalling thoughts",
826
+ "Checking the board",
827
+ "Sizing up the work"
828
+ ],
829
+ // LLM is mid-thinking-block (extended reasoning).
830
+ thinking: [
831
+ "Strategising",
832
+ "Plotting next move",
833
+ "Weighing options",
834
+ "Reasoning",
835
+ "Deliberating",
836
+ "Thinking it through",
837
+ "Mapping the play",
838
+ "Considering angles",
839
+ "Calculating odds",
840
+ "Drafting the call"
841
+ ],
842
+ // LLM is streaming text — boss is forming its dispatch / response.
843
+ generating: [
844
+ "Drafting",
845
+ "Composing dispatch",
846
+ "Writing the brief",
847
+ "Penning instructions",
848
+ "Wording it up",
849
+ "Putting it on paper",
850
+ "Phrasing the ask",
851
+ "Forming the directive",
852
+ "Scripting the plan"
853
+ ],
854
+ // Boss is invoking a tool — most often prompt_worker.
855
+ tools: [
856
+ "Coordinating",
857
+ "Dispatching",
858
+ "Routing",
859
+ "Delegating",
860
+ "Issuing orders",
861
+ "Handing off",
862
+ "Aligning workers",
863
+ "Conducting",
864
+ "Calling the team",
865
+ "Steering",
866
+ "Pulling levers"
867
+ ],
868
+ // Provider retry (overloaded / rate-limited / etc.).
869
+ retrying: [
870
+ "Reattempting",
871
+ "Course correcting",
872
+ "Trying again",
873
+ "Pushing through",
874
+ "Holding the line"
875
+ ]
876
+ };
877
+
878
+ // src/boss-tasks-overlay.tsx
879
+ init_esm_shims();
880
+ var import_react9 = __toESM(require_react(), 1);
881
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
882
+ function statusGlyph(status) {
883
+ switch (status) {
884
+ case "done":
885
+ return "\u2713";
886
+ case "in_progress":
887
+ return "~";
888
+ case "blocked":
889
+ return "\u2717";
890
+ case "skipped":
891
+ return "\u2014";
892
+ default:
893
+ return " ";
894
+ }
895
+ }
896
+ function BossTasksOverlay({
897
+ boss,
898
+ workers,
899
+ onClose
900
+ }) {
901
+ const theme = useTheme();
902
+ const tasksState = useTasksState();
903
+ const tasks = tasksState.tasks;
904
+ const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
905
+ const [status, setStatusMsg] = (0, import_react9.useState)("");
906
+ const statusTimer = (0, import_react9.useRef)(null);
907
+ const showStatus = (0, import_react9.useCallback)((msg) => {
908
+ setStatusMsg(msg);
909
+ if (statusTimer.current) clearTimeout(statusTimer.current);
910
+ statusTimer.current = setTimeout(() => setStatusMsg(""), 2500);
911
+ }, []);
912
+ const groupedTasks = workers.map((w) => ({
913
+ project: w.name,
914
+ tasks: tasks.filter((t) => t.project === w.name).sort((a, b) => a.createdAt.localeCompare(b.createdAt))
915
+ }));
916
+ const flatTasks = groupedTasks.flatMap((g) => g.tasks);
917
+ (0, import_react9.useEffect)(() => {
918
+ if (flatTasks.length === 0) {
919
+ setSelectedIndex(0);
920
+ } else if (selectedIndex >= flatTasks.length) {
921
+ setSelectedIndex(flatTasks.length - 1);
922
+ }
923
+ }, [flatTasks.length, selectedIndex]);
924
+ const selected = flatTasks[selectedIndex];
925
+ const MAX_VISIBLE = 12;
926
+ const startIdx = Math.max(
927
+ 0,
928
+ Math.min(flatTasks.length - MAX_VISIBLE, selectedIndex - MAX_VISIBLE + 1, selectedIndex)
929
+ );
930
+ const endIdx = Math.min(flatTasks.length, startIdx + MAX_VISIBLE);
931
+ const visibleIdSet = new Set(flatTasks.slice(startIdx, endIdx).map((t) => t.id));
932
+ const showingTop = startIdx > 0;
933
+ const showingBottom = endIdx < flatTasks.length;
934
+ use_input_default((input, key) => {
935
+ if (key.escape) {
936
+ onClose();
937
+ return;
938
+ }
939
+ if (key.upArrow || input === "k") {
940
+ setSelectedIndex((i) => Math.max(0, i - 1));
941
+ return;
942
+ }
943
+ if (key.downArrow || input === "j") {
944
+ setSelectedIndex((i) => Math.min(flatTasks.length - 1, i + 1));
945
+ return;
946
+ }
947
+ if (input === "d" && selected) {
948
+ void tasksStore.remove(selected.id).then(() => showStatus("Deleted"));
949
+ return;
950
+ }
951
+ if (input === "r") {
952
+ onClose();
953
+ void (async () => {
954
+ const dispatched = [];
955
+ for (const w of workers) {
956
+ const next = tasksStore.nextDispatchable(w.name);
957
+ if (!next) continue;
958
+ if (next.status === "blocked") {
959
+ await tasksStore.update(next.id, { status: "pending", notes: void 0 });
960
+ }
961
+ const res = await boss.dispatchTaskById(next.id);
962
+ if (res.ok) dispatched.push({ project: w.name, title: next.title });
54
963
  }
55
- else if (a === "--help" || a === "-h") {
56
- printHelpAndExit();
964
+ if (dispatched.length === 0) {
965
+ bossStore.appendInfo("No pending or blocked tasks to run.", "info");
966
+ } else {
967
+ bossStore.appendTaskDispatch(dispatched);
57
968
  }
58
- else {
59
- throw new Error(`Unknown argument: ${a}`);
969
+ })();
970
+ return;
971
+ }
972
+ });
973
+ const doneCount = tasks.filter((t) => t.status === "done").length;
974
+ const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
975
+ const pendingCount = tasks.filter((t) => t.status === "pending").length;
976
+ const blockedCount = tasks.filter((t) => t.status === "blocked").length;
977
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
978
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: COLORS.primary, bold: true, children: "Tasks" }),
980
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${tasks.length} total \xB7 ` }),
981
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
982
+ CountsRow,
983
+ {
984
+ theme,
985
+ done: doneCount,
986
+ active: inProgressCount,
987
+ pending: pendingCount,
988
+ blocked: blockedCount
60
989
  }
990
+ )
991
+ ] }),
992
+ flatTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
993
+ " No tasks yet. Ask the boss to plan some \u2014 e.g. ",
994
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.text, children: '"plan some work"' }),
995
+ "."
996
+ ] }) }),
997
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
998
+ groupedTasks.map((group, gIdx) => {
999
+ const startInFlat = groupedTasks.slice(0, gIdx).reduce((acc, g) => acc + g.tasks.length, 0);
1000
+ const visibleInSection = group.tasks.filter((t) => visibleIdSet.has(t.id));
1001
+ if (visibleInSection.length === 0) return null;
1002
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
1003
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
1004
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: projectColor(group.project), bold: true, children: group.project }),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${group.tasks.length}` })
1006
+ ] }),
1007
+ visibleInSection.map((task) => {
1008
+ const realIdx = startInFlat + group.tasks.indexOf(task);
1009
+ const isSelected = realIdx === selectedIndex;
1010
+ const prefix = isSelected ? "\u276F " : " ";
1011
+ const glyph = statusGlyph(task.status);
1012
+ const color = isSelected ? theme.primary : task.status === "done" ? theme.success : task.status === "in_progress" ? theme.warning : task.status === "blocked" ? theme.error : theme.text;
1013
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color, bold: isSelected, children: [
1014
+ prefix,
1015
+ "[",
1016
+ glyph,
1017
+ "] ",
1018
+ task.title
1019
+ ] }, task.id);
1020
+ })
1021
+ ] }, group.project);
1022
+ }),
1023
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2193 ${flatTasks.length - endIdx} more below` }),
1024
+ status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.success, children: " " + status }) }),
1025
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
1026
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "\u2191\u2193" }),
1027
+ " move \xB7 (",
1028
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "d" }),
1029
+ ")elete \xB7 (",
1030
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "r" }),
1031
+ ")un pending \xB7 ",
1032
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "ESC" }),
1033
+ " close"
1034
+ ] }) })
1035
+ ] });
1036
+ }
1037
+ function CountsRow({
1038
+ theme,
1039
+ done,
1040
+ active,
1041
+ pending,
1042
+ blocked
1043
+ }) {
1044
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
1045
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.success, children: [
1046
+ done,
1047
+ " done"
1048
+ ] }),
1049
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.warning, children: [
1051
+ active,
1052
+ " active"
1053
+ ] }),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1055
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.text, children: [
1056
+ pending,
1057
+ " pending"
1058
+ ] }),
1059
+ blocked > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1060
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1061
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.error, children: [
1062
+ blocked,
1063
+ " blocked"
1064
+ ] })
1065
+ ] })
1066
+ ] });
1067
+ }
1068
+
1069
+ // src/radio-picker.tsx
1070
+ init_esm_shims();
1071
+ var import_react10 = __toESM(require_react(), 1);
1072
+
1073
+ // src/radio.ts
1074
+ init_esm_shims();
1075
+ import { spawn } from "child_process";
1076
+ var RADIO_STATIONS = [
1077
+ {
1078
+ id: "somafm-groove-salad",
1079
+ name: "SomaFM \xB7 Groove Salad",
1080
+ description: "Chilled downtempo, ambient grooves",
1081
+ url: "http://ice1.somafm.com/groovesalad-128-mp3"
1082
+ },
1083
+ {
1084
+ id: "somafm-drone-zone",
1085
+ name: "SomaFM \xB7 Drone Zone",
1086
+ description: "Atmospheric textures with minimal beats",
1087
+ url: "http://ice1.somafm.com/dronezone-128-mp3"
1088
+ },
1089
+ {
1090
+ id: "radio-paradise",
1091
+ name: "Radio Paradise",
1092
+ description: "Eclectic mix \u2014 rock, electronica, jazz",
1093
+ url: "http://stream.radioparadise.com/mp3-128"
1094
+ },
1095
+ {
1096
+ id: "george-fm",
1097
+ name: "George FM",
1098
+ description: "NZ dance + electronic",
1099
+ url: "https://mediaworks.streamguys1.com/george_net_icy"
1100
+ }
1101
+ ];
1102
+ var PLAYERS = [
1103
+ { cmd: "mpv", args: (u) => ["--really-quiet", "--no-video", "--no-terminal", u] },
1104
+ {
1105
+ cmd: "ffplay",
1106
+ args: (u) => ["-nodisp", "-autoexit", "-loglevel", "quiet", u]
1107
+ },
1108
+ { cmd: "mpg123", args: (u) => ["-q", u] },
1109
+ { cmd: "cvlc", args: (u) => ["--play-and-exit", "--quiet", u] }
1110
+ ];
1111
+ var currentChild = null;
1112
+ var currentStationId = null;
1113
+ function getCurrentStation() {
1114
+ return currentStationId;
1115
+ }
1116
+ function stopRadio() {
1117
+ if (!currentChild) return;
1118
+ try {
1119
+ if (process.platform !== "win32" && currentChild.pid) {
1120
+ try {
1121
+ process.kill(-currentChild.pid, "SIGTERM");
1122
+ } catch {
1123
+ currentChild.kill("SIGTERM");
1124
+ }
1125
+ } else {
1126
+ currentChild.kill("SIGTERM");
61
1127
  }
62
- return args;
1128
+ } catch {
1129
+ }
1130
+ currentChild = null;
1131
+ currentStationId = null;
1132
+ log("INFO", "radio", "stopped");
63
1133
  }
64
- function printHelpAndExit() {
65
- const c = (color, text) => chalk.hex(color)(text);
66
- process.stdout.write("\n" +
67
- c(COLORS.primary, "GG Boss") +
68
- c(COLORS.textDim, " orchestrator that drives multiple ggcoder workers from one chat.\n\n") +
69
- c(COLORS.text, "Usage\n") +
70
- " " +
71
- c(COLORS.accent, "ggboss") +
72
- c(COLORS.textDim, " start orchestrator using linked projects\n") +
73
- " " +
74
- c(COLORS.accent, "ggboss link") +
75
- c(COLORS.textDim, " pick which projects to link (interactive)\n") +
76
- " " +
77
- c(COLORS.accent, "ggboss continue") +
78
- c(COLORS.textDim, " resume the most recent boss session\n") +
79
- " " +
80
- c(COLORS.accent, "ggboss --resume <id>") +
81
- c(COLORS.textDim, " resume a specific boss session\n") +
82
- " " +
83
- c(COLORS.accent, "ggboss --project <spec> [...]") +
84
- c(COLORS.textDim, " override links with explicit project(s)\n\n") +
85
- c(COLORS.text, "Options\n") +
86
- " " +
87
- c(COLORS.primary, "--project, -p <spec>") +
88
- c(COLORS.textDim, ' project to manage. spec is "cwd" or "name=cwd". repeatable.\n') +
89
- " " +
90
- c(COLORS.primary, "--boss-model <id>") +
91
- c(COLORS.textDim, " model for the orchestrator (default: claude-opus-4-7)\n") +
92
- " " +
93
- c(COLORS.primary, "--worker-model <id>") +
94
- c(COLORS.textDim, " model for workers (default: claude-sonnet-4-6)\n") +
95
- " " +
96
- c(COLORS.primary, "--help, -h") +
97
- c(COLORS.textDim, " show this help\n\n") +
98
- c(COLORS.textDim, "Talk to the boss at the prompt. Press ") +
99
- c(COLORS.accent, "Ctrl+C") +
100
- c(COLORS.textDim, " twice to exit.\n\n"));
101
- process.exit(0);
1134
+ function playRadio(stationId) {
1135
+ const station = RADIO_STATIONS.find((s) => s.id === stationId);
1136
+ if (!station) return { ok: false, error: `Unknown station: ${stationId}` };
1137
+ stopRadio();
1138
+ for (const player of PLAYERS) {
1139
+ try {
1140
+ const child = spawn(player.cmd, player.args(station.url), {
1141
+ detached: process.platform !== "win32",
1142
+ stdio: "ignore"
1143
+ });
1144
+ let errored = false;
1145
+ child.once("error", () => {
1146
+ errored = true;
1147
+ });
1148
+ if (child.pid && !errored) {
1149
+ currentChild = child;
1150
+ currentStationId = stationId;
1151
+ log("INFO", "radio", "playing", {
1152
+ station: station.id,
1153
+ player: player.cmd,
1154
+ url: station.url
1155
+ });
1156
+ child.unref();
1157
+ return { ok: true };
1158
+ }
1159
+ } catch {
1160
+ }
1161
+ }
1162
+ log("WARN", "radio", "no compatible player found", { platform: process.platform });
1163
+ return {
1164
+ ok: false,
1165
+ error: buildInstallHint()
1166
+ };
102
1167
  }
103
- async function runOrchestrator(args) {
104
- if (args.projects.length === 0) {
105
- const links = await loadLinks();
106
- if (links.projects.length === 0) {
107
- process.stderr.write("\n" +
108
- chalk.hex(COLORS.warning)("No linked projects.") +
109
- chalk.hex(COLORS.textDim)(" Run ") +
110
- chalk.hex(COLORS.accent)("ggboss link") +
111
- chalk.hex(COLORS.textDim)(" to choose, or pass ") +
112
- chalk.hex(COLORS.accent)("--project") +
113
- chalk.hex(COLORS.textDim)(".\n\n"));
114
- process.exit(1);
115
- }
116
- args.projects = links.projects.map((p) => ({ name: p.name, cwd: p.cwd }));
117
- }
118
- clearScreen();
119
- // Splash — Ink-rendered ASCII logo with shimmering gradient, shown while
120
- // the boss spins up its workers. dismiss() blocks until min-visible-time
121
- // has elapsed AND Ink has flushed the unmount, so the chat UI never
122
- // overlaps with the splash on screen.
123
- const splash = showSplash({
124
- caption: `Spinning up ${args.projects.length} worker${args.projects.length === 1 ? "" : "s"}…`,
1168
+ function buildInstallHint() {
1169
+ const base = "Radio needs a streaming player. Install one of: mpv (recommended), ffplay, mpg123, or vlc.";
1170
+ switch (process.platform) {
1171
+ case "darwin":
1172
+ return `${base} On macOS: \`brew install mpv\` (or \`brew install ffmpeg\` for ffplay).`;
1173
+ case "linux":
1174
+ return `${base} On Linux (Debian/Ubuntu): \`sudo apt install mpv\`. Fedora: \`sudo dnf install mpv\`. Arch: \`sudo pacman -S mpv\`.`;
1175
+ case "win32":
1176
+ return `${base} On Windows: \`winget install mpv.mpv\` (or download from https://mpv.io).`;
1177
+ default:
1178
+ return `${base} See https://mpv.io for platform installation instructions.`;
1179
+ }
1180
+ }
1181
+
1182
+ // src/radio-picker.tsx
1183
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
1184
+ function RadioPicker({
1185
+ currentStationId: currentStationId2,
1186
+ onSelect,
1187
+ onCancel
1188
+ }) {
1189
+ const items = [
1190
+ ...RADIO_STATIONS.map((s) => ({
1191
+ label: `${currentStationId2 === s.id ? "* " : " "}${s.name}`,
1192
+ value: s.id,
1193
+ description: s.description
1194
+ })),
1195
+ {
1196
+ label: `${currentStationId2 === null ? "* " : " "}Off`,
1197
+ value: "off",
1198
+ description: "Stop the radio"
1199
+ }
1200
+ ];
1201
+ const initialIndex = Math.max(
1202
+ 0,
1203
+ items.findIndex((i) => i.value === (currentStationId2 ?? "off"))
1204
+ );
1205
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1206
+ SelectList,
1207
+ {
1208
+ items,
1209
+ onSelect: (v) => onSelect(v === "off" ? "off" : v),
1210
+ onCancel,
1211
+ initialIndex,
1212
+ windowSize: 6
1213
+ }
1214
+ );
1215
+ }
1216
+
1217
+ // src/auto-update.ts
1218
+ init_esm_shims();
1219
+ import { spawn as spawn2 } from "child_process";
1220
+ import fs3 from "fs";
1221
+ import path3 from "path";
1222
+ import os from "os";
1223
+ var PACKAGE_NAME = "@kenkaiiii/gg-boss";
1224
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
1225
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
1226
+ var FETCH_TIMEOUT_MS = 1e4;
1227
+ function getStateFilePath() {
1228
+ return path3.join(os.homedir(), ".gg", "boss", "update-state.json");
1229
+ }
1230
+ function readState() {
1231
+ try {
1232
+ const raw = fs3.readFileSync(getStateFilePath(), "utf-8");
1233
+ return JSON.parse(raw);
1234
+ } catch {
1235
+ return null;
1236
+ }
1237
+ }
1238
+ function writeState(state) {
1239
+ try {
1240
+ const dir = path3.dirname(getStateFilePath());
1241
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
1242
+ fs3.writeFileSync(getStateFilePath(), JSON.stringify(state));
1243
+ } catch {
1244
+ }
1245
+ }
1246
+ function compareVersions(a, b) {
1247
+ const pa = a.split(".").map(Number);
1248
+ const pb = b.split(".").map(Number);
1249
+ for (let i = 0; i < 3; i++) {
1250
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
1251
+ if (diff !== 0) return diff;
1252
+ }
1253
+ return 0;
1254
+ }
1255
+ function detectInstallInfo() {
1256
+ const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/");
1257
+ if (scriptPath.includes("/_npx/")) {
1258
+ return { packageManager: "unknown" /* UNKNOWN */, updateCommand: null };
1259
+ }
1260
+ if (scriptPath.includes("/.pnpm") || scriptPath.includes("/pnpm/global")) {
1261
+ return {
1262
+ packageManager: "pnpm" /* PNPM */,
1263
+ updateCommand: `pnpm add -g ${PACKAGE_NAME}@latest`
1264
+ };
1265
+ }
1266
+ if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
1267
+ return {
1268
+ packageManager: "yarn" /* YARN */,
1269
+ updateCommand: `yarn global add ${PACKAGE_NAME}@latest`
1270
+ };
1271
+ }
1272
+ return {
1273
+ packageManager: "npm" /* NPM */,
1274
+ updateCommand: `npm install -g ${PACKAGE_NAME}@latest`
1275
+ };
1276
+ }
1277
+ async function fetchLatestVersion() {
1278
+ try {
1279
+ const controller = new AbortController();
1280
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
1281
+ const response = await fetch(REGISTRY_URL, { signal: controller.signal });
1282
+ clearTimeout(timeout);
1283
+ const data = await response.json();
1284
+ const version = data.version?.trim();
1285
+ return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
1286
+ } catch {
1287
+ return null;
1288
+ }
1289
+ }
1290
+ function performUpdateInBackground(command) {
1291
+ try {
1292
+ const parts = command.split(" ");
1293
+ const child = spawn2(parts[0], parts.slice(1), {
1294
+ detached: true,
1295
+ stdio: "ignore",
1296
+ env: { ...process.env, npm_config_loglevel: "silent" }
125
1297
  });
126
- // Resolve final boss/worker models: CLI flags > saved settings > defaults.
127
- // Settings persist user choices made via /model boss / /model workers across
128
- // restarts so the user doesn't have to re-pick every session.
129
- const settings = await loadSettings();
130
- const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
131
- const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-7";
132
- const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
133
- const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
134
- // Open ~/.gg/boss/debug.log in append mode and stamp a startup line so
135
- // future tail/grep diagnoses have the full session context up front.
136
- initLogger({
137
- version: VERSION,
138
- bossProvider: finalBossProvider,
139
- bossModel: finalBossModel,
140
- bossThinking: settings.bossThinkingLevel,
141
- workerProvider: finalWorkerProvider,
142
- workerModel: finalWorkerModel,
143
- projectCount: args.projects.length,
1298
+ child.unref();
1299
+ } catch {
1300
+ }
1301
+ }
1302
+ function checkAndAutoUpdate(currentVersion) {
1303
+ try {
1304
+ const state = readState();
1305
+ let message = null;
1306
+ if (state?.updatePending && state.latestVersion) {
1307
+ if (compareVersions(state.latestVersion, currentVersion) > 0) {
1308
+ const info = detectInstallInfo();
1309
+ if (info.updateCommand) {
1310
+ performUpdateInBackground(info.updateCommand);
1311
+ message = `Ken just shipped ${state.latestVersion}! Installing in the background \u2014 takes effect next launch.`;
1312
+ writeState({
1313
+ ...state,
1314
+ lastCheckedAt: Date.now(),
1315
+ updatePending: false,
1316
+ lastUpdateAttempt: Date.now()
1317
+ });
1318
+ }
1319
+ } else {
1320
+ writeState({ ...state, updatePending: false });
1321
+ }
1322
+ }
1323
+ const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
1324
+ if (shouldCheck) scheduleBackgroundCheck(currentVersion);
1325
+ return message;
1326
+ } catch {
1327
+ return null;
1328
+ }
1329
+ }
1330
+ function getPendingUpdate(currentVersion) {
1331
+ try {
1332
+ const state = readState();
1333
+ if (!state?.latestVersion) return null;
1334
+ if (compareVersions(state.latestVersion, currentVersion) <= 0) return null;
1335
+ return { latestVersion: state.latestVersion };
1336
+ } catch {
1337
+ return null;
1338
+ }
1339
+ }
1340
+ function scheduleBackgroundCheck(currentVersion) {
1341
+ fetchLatestVersion().then((latestVersion) => {
1342
+ const newState = {
1343
+ lastCheckedAt: Date.now(),
1344
+ latestVersion: latestVersion ?? void 0,
1345
+ updatePending: false
1346
+ };
1347
+ if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
1348
+ newState.updatePending = true;
1349
+ }
1350
+ writeState(newState);
1351
+ }).catch(() => {
1352
+ });
1353
+ }
1354
+ var periodicTimer = null;
1355
+ function startPeriodicUpdateCheck(currentVersion, onUpdate) {
1356
+ if (periodicTimer) return;
1357
+ periodicTimer = setInterval(() => {
1358
+ fetchLatestVersion().then((latestVersion) => {
1359
+ if (!latestVersion) return;
1360
+ if (compareVersions(latestVersion, currentVersion) <= 0) return;
1361
+ const info = detectInstallInfo();
1362
+ if (!info.updateCommand) return;
1363
+ writeState({
1364
+ lastCheckedAt: Date.now(),
1365
+ latestVersion,
1366
+ updatePending: true
1367
+ });
1368
+ onUpdate(
1369
+ `Ken just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart ggboss to grab it (or run ${info.updateCommand} if you can't wait).`
1370
+ );
1371
+ stopPeriodicUpdateCheck();
1372
+ }).catch(() => {
144
1373
  });
145
- log("INFO", "cli", "linked projects", {
146
- projects: args.projects.map((p) => p.name).join(","),
1374
+ }, CHECK_INTERVAL_MS);
1375
+ periodicTimer.unref();
1376
+ }
1377
+ function stopPeriodicUpdateCheck() {
1378
+ if (periodicTimer) {
1379
+ clearInterval(periodicTimer);
1380
+ periodicTimer = null;
1381
+ }
1382
+ }
1383
+
1384
+ // src/orchestrator-app.tsx
1385
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
1386
+ function BossApp({ boss }) {
1387
+ const theme = loadTheme("dark");
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TerminalSizeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AnimationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossAppInner, { boss }) }) }) });
1389
+ }
1390
+ function BossAppInner({ boss }) {
1391
+ const state = useBossState();
1392
+ const { exit } = use_app_default();
1393
+ const { stdout } = use_stdout_default();
1394
+ const { resizeKey, columns } = useTerminalSize();
1395
+ const runStartRef = (0, import_react11.useRef)(null);
1396
+ runStartRef.current = state.runStartMs;
1397
+ const charCountRef = (0, import_react11.useRef)(0);
1398
+ charCountRef.current = state.streaming?.text.length ?? 0;
1399
+ const realTokensAccumRef = (0, import_react11.useRef)(0);
1400
+ realTokensAccumRef.current = state.bossInputTokens;
1401
+ const [lastUserMessage, setLastUserMessage] = (0, import_react11.useState)("");
1402
+ const [overlay, setOverlay] = (0, import_react11.useState)(
1403
+ null
1404
+ );
1405
+ const [currentRadio, setCurrentRadio] = (0, import_react11.useState)(() => getCurrentStation());
1406
+ const [staticKey, setStaticKey] = (0, import_react11.useState)(0);
1407
+ const [updatePending, setUpdatePending] = (0, import_react11.useState)(
1408
+ () => getPendingUpdate(VERSION) !== null
1409
+ );
1410
+ (0, import_react11.useEffect)(() => {
1411
+ startPeriodicUpdateCheck(VERSION, (msg) => {
1412
+ bossStore.appendInfo(msg, "info");
1413
+ setUpdatePending(true);
147
1414
  });
148
- // Auto-update: instantly applies any pending install from the prior run
149
- // (background spawn, takes effect next launch) and schedules a fresh
150
- // registry check. Returns a one-liner if an install just kicked off so
151
- // we can surface it before the splash takes over.
152
- const updateMessage = checkAndAutoUpdate(VERSION);
153
- if (updateMessage)
154
- log("INFO", "auto_update", updateMessage);
155
- const boss = new GGBoss({
156
- bossProvider: finalBossProvider,
157
- bossModel: finalBossModel,
158
- bossThinkingLevel: settings.bossThinkingLevel,
159
- workerProvider: finalWorkerProvider,
160
- workerModel: finalWorkerModel,
161
- projects: args.projects,
162
- continueRecent: args.continueRecent,
163
- resumeSessionId: args.resumeSessionId,
1415
+ return () => stopPeriodicUpdateCheck();
1416
+ }, []);
1417
+ const workersRunning = state.workers.filter((w) => w.status === "working").length;
1418
+ const titlePrevRef = (0, import_react11.useRef)("");
1419
+ (0, import_react11.useEffect)(() => {
1420
+ if (!stdout) return;
1421
+ let title;
1422
+ if (workersRunning > 0) {
1423
+ const label = `${workersRunning} worker${workersRunning === 1 ? "" : "s"} running`;
1424
+ title = `\u25CF ${label} \xB7 GG Boss`;
1425
+ } else if (state.phase === "working") {
1426
+ title = "\u25CF GG Boss";
1427
+ } else {
1428
+ title = "GG Boss";
1429
+ }
1430
+ if (title !== titlePrevRef.current) {
1431
+ titlePrevRef.current = title;
1432
+ stdout.write(`\x1B]0;${title}\x1B\\`);
1433
+ }
1434
+ }, [stdout, workersRunning, state.phase]);
1435
+ (0, import_react11.useEffect)(() => {
1436
+ return () => {
1437
+ stdout?.write(`\x1B]0;GG Boss\x1B\\`);
1438
+ };
1439
+ }, [stdout]);
1440
+ const staticItems = (0, import_react11.useMemo)(
1441
+ () => [{ kind: "banner", id: "banner" }, ...state.history],
1442
+ [state.history]
1443
+ );
1444
+ const openOverlay = (0, import_react11.useCallback)(
1445
+ (next) => {
1446
+ setOverlay(next);
1447
+ },
1448
+ []
1449
+ );
1450
+ const closeOverlay = (0, import_react11.useCallback)(() => {
1451
+ setOverlay(null);
1452
+ }, []);
1453
+ void stdout;
1454
+ const handleDoubleExit = useDoublePress(
1455
+ (pending) => bossStore.setExitPending(pending),
1456
+ () => exit()
1457
+ );
1458
+ (0, import_react11.useEffect)(() => {
1459
+ if (state.pendingFlush.length > 0) {
1460
+ bossStore.commitPendingFlush();
1461
+ }
1462
+ }, [state.flushGeneration, state.pendingFlush.length]);
1463
+ use_input_default((input, key) => {
1464
+ if (key.ctrl && input === "t") {
1465
+ if (overlay === "tasks") closeOverlay();
1466
+ else openOverlay("tasks");
1467
+ return;
1468
+ }
1469
+ if (key.escape && state.phase === "working") {
1470
+ boss.abort();
1471
+ }
1472
+ });
1473
+ const handleSlashCommand = async (value) => {
1474
+ const parsed = parseSlash(value);
1475
+ if (!parsed) return false;
1476
+ const name = canonicalName(parsed.name);
1477
+ if (!name) {
1478
+ bossStore.appendInfo(`Unknown command: /${parsed.name}`, "warning");
1479
+ return true;
1480
+ }
1481
+ switch (name) {
1482
+ case "help":
1483
+ bossStore.appendUser(value);
1484
+ bossStore.appendInfo(buildHelpText(), "info");
1485
+ return true;
1486
+ case "clear":
1487
+ stdout?.write("\x1B[2J\x1B[3J\x1B[H");
1488
+ bossStore.clearHistory();
1489
+ await boss.resetConversation();
1490
+ setStaticKey((k) => k + 1);
1491
+ bossStore.appendInfo("Session cleared.", "info");
1492
+ return true;
1493
+ case "model-boss":
1494
+ openOverlay("model-boss");
1495
+ return true;
1496
+ case "model-workers":
1497
+ openOverlay("model-workers");
1498
+ return true;
1499
+ case "compact":
1500
+ bossStore.appendUser(value);
1501
+ await boss.manualCompact();
1502
+ return true;
1503
+ case "radio":
1504
+ openOverlay("radio");
1505
+ return true;
1506
+ case "quit":
1507
+ exit();
1508
+ return true;
1509
+ }
1510
+ return false;
1511
+ };
1512
+ const handleModelSelect = (value) => {
1513
+ const colon = value.indexOf(":");
1514
+ if (colon < 0) {
1515
+ closeOverlay();
1516
+ return;
1517
+ }
1518
+ const provider = value.slice(0, colon);
1519
+ const model = value.slice(colon + 1);
1520
+ if (overlay === "model-boss") {
1521
+ void boss.switchBossModel(provider, model);
1522
+ } else if (overlay === "model-workers") {
1523
+ void boss.switchWorkerModel(provider, model);
1524
+ }
1525
+ closeOverlay();
1526
+ };
1527
+ const handleSubmit = (value) => {
1528
+ const trimmed = value.trim();
1529
+ if (!trimmed) return;
1530
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
1531
+ void handleSlashCommand(trimmed);
1532
+ return;
1533
+ }
1534
+ bossStore.appendUser(trimmed);
1535
+ setLastUserMessage(trimmed);
1536
+ const scoped = scopePrefix(state.scope) + trimmed;
1537
+ boss.enqueueUserMessage(scoped);
1538
+ };
1539
+ const handleAbort = () => {
1540
+ if (state.phase === "working") {
1541
+ boss.abort();
1542
+ return;
1543
+ }
1544
+ handleDoubleExit();
1545
+ };
1546
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: columns, children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Static, { items: staticItems, style: { width: "100%" }, children: (item) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", paddingRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StaticRowView, { row: item }) }, item.id) }, `${resizeKey}-${staticKey}`),
1548
+ overlay === "tasks" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossTasksOverlay, { boss, workers: state.workers, onClose: closeOverlay }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1549
+ state.streaming && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingTurnView, { turn: state.streaming, isRunning: state.phase === "working" }),
1550
+ state.phase === "working" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1551
+ ActivityIndicator,
1552
+ {
1553
+ phase: state.activityPhase,
1554
+ elapsedMs: state.runStartMs ? Date.now() - state.runStartMs : 0,
1555
+ runStartRef,
1556
+ thinkingMs: state.streaming?.thinkingMs ?? 0,
1557
+ isThinking: state.activityPhase === "thinking",
1558
+ tokenEstimate: state.bossInputTokens,
1559
+ charCountRef,
1560
+ realTokensAccumRef,
1561
+ userMessage: lastUserMessage,
1562
+ activeToolNames: (state.streaming?.tools ?? []).filter((t) => t.status === "running").map((t) => t.name),
1563
+ retryInfo: state.retryInfo,
1564
+ phrases: BOSS_PHRASES,
1565
+ pulseColors: PULSE_COLORS
1566
+ }
1567
+ ) }),
1568
+ state.compaction?.state === "running" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CompactionSpinner, {}),
1569
+ state.compaction?.state === "done" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1570
+ CompactionDone,
1571
+ {
1572
+ originalCount: state.compaction.originalCount,
1573
+ newCount: state.compaction.newCount,
1574
+ tokensBefore: state.compaction.tokensBefore,
1575
+ tokensAfter: state.compaction.tokensAfter
1576
+ }
1577
+ ),
1578
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1579
+ InputArea,
1580
+ {
1581
+ onSubmit: handleSubmit,
1582
+ onAbort: handleAbort,
1583
+ disabled: state.phase === "working",
1584
+ isActive: !overlay,
1585
+ cwd: process.cwd(),
1586
+ commands: BOSS_SLASH_COMMANDS,
1587
+ scopeBadge: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopePill, { scope: state.scope }),
1588
+ disableMouseTracking: true,
1589
+ onTab: () => bossStore.cycleScope(),
1590
+ onShiftTab: () => {
1591
+ const next = state.bossThinkingLevel ? void 0 : "medium";
1592
+ void boss.setBossThinking(next);
1593
+ }
1594
+ }
1595
+ ),
1596
+ overlay === "model-boss" || overlay === "model-workers" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1597
+ ModelSelector,
1598
+ {
1599
+ onSelect: handleModelSelect,
1600
+ onCancel: closeOverlay,
1601
+ loggedInProviders: state.loggedInProviders,
1602
+ currentModel: overlay === "model-boss" ? state.bossModel : state.workerModel,
1603
+ currentProvider: overlay === "model-boss" ? state.bossProvider : state.workerProvider
1604
+ }
1605
+ ) : overlay === "radio" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1606
+ RadioPicker,
1607
+ {
1608
+ currentStationId: currentRadio,
1609
+ onCancel: closeOverlay,
1610
+ onSelect: (value) => {
1611
+ if (value === "off") {
1612
+ stopRadio();
1613
+ setCurrentRadio(null);
1614
+ bossStore.appendInfo("Radio off.", "info");
1615
+ } else {
1616
+ const result = playRadio(value);
1617
+ if (result.ok) {
1618
+ setCurrentRadio(value);
1619
+ const station = RADIO_STATIONS.find((s) => s.id === value);
1620
+ bossStore.appendInfo(`Now playing: ${station?.name ?? value}`, "info");
1621
+ } else {
1622
+ bossStore.appendInfo(result.error ?? "Radio failed to start.", "warning");
1623
+ }
1624
+ }
1625
+ closeOverlay();
1626
+ }
1627
+ }
1628
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1629
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1630
+ BossFooter,
1631
+ {
1632
+ bossModel: state.bossModel,
1633
+ workerModel: state.workerModel,
1634
+ tokensIn: state.bossInputTokens,
1635
+ exitPending: state.exitPending,
1636
+ bossThinkingLevel: state.bossThinkingLevel,
1637
+ updatePending,
1638
+ currentRadioStationId: currentRadio
1639
+ }
1640
+ ),
1641
+ !state.exitPending && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1642
+ WorkerStatusBar,
1643
+ {
1644
+ workers: state.workers,
1645
+ pendingMessages: state.pendingUserMessages
1646
+ }
1647
+ )
1648
+ ] })
1649
+ ] })
1650
+ ] });
1651
+ }
1652
+ function ScopePill({ scope }) {
1653
+ const theme = useTheme();
1654
+ const isAll = scope === "all";
1655
+ const bg = isAll ? COLORS.accent : projectColor(scope);
1656
+ const label = isAll ? "All" : scope;
1657
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
1658
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: "Project " }),
1659
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "black", backgroundColor: bg, bold: true, children: ` ${label} ` }),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
1661
+ " ",
1662
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.primary, children: "Tab" }),
1663
+ " to switch"
1664
+ ] })
1665
+ ] });
1666
+ }
1667
+ function scopePrefix(scope) {
1668
+ if (scope === "all") return "[scope:all] ";
1669
+ return `[scope:${scope}] `;
1670
+ }
1671
+ var SHIMMER_WIDTH = 3;
1672
+ function formatElapsed(ms) {
1673
+ const total = Math.floor(ms / 1e3);
1674
+ const m = Math.floor(total / 60);
1675
+ const s = total % 60;
1676
+ return `${m}:${s.toString().padStart(2, "0")}`;
1677
+ }
1678
+ function AnimationActiveSentinel() {
1679
+ useAnimationActive();
1680
+ return null;
1681
+ }
1682
+ function ShimmerName({
1683
+ name,
1684
+ color,
1685
+ tick
1686
+ }) {
1687
+ const cycle = name.length + SHIMMER_WIDTH * 2;
1688
+ const shimmerPos = tick % cycle - SHIMMER_WIDTH;
1689
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: name.split("").map((ch, i) => {
1690
+ const isBright = Math.abs(i - shimmerPos) <= SHIMMER_WIDTH;
1691
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color, bold: isBright, dimColor: !isBright, children: ch }, i);
1692
+ }) });
1693
+ }
1694
+ function WorkerStatusBar({
1695
+ workers,
1696
+ pendingMessages
1697
+ }) {
1698
+ const theme = useTheme();
1699
+ const working = workers.filter((w) => w.status === "working");
1700
+ const errored = workers.filter((w) => w.status === "error");
1701
+ const idleCount = workers.length - working.length - errored.length;
1702
+ const anyWorking = working.length > 0;
1703
+ const tick = useAnimationTick();
1704
+ const now = Date.now();
1705
+ if (workers.length === 0) return null;
1706
+ const slots = [];
1707
+ for (const w of working) {
1708
+ const projectHue = projectColor(w.name);
1709
+ const elapsed = w.workStartedAt ? formatElapsed(now - w.workStartedAt) : null;
1710
+ slots.push(
1711
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
1712
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ShimmerName, { name: w.name, color: projectHue, tick }),
1713
+ elapsed && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
1714
+ " ",
1715
+ elapsed
1716
+ ] })
1717
+ ] }, `w-${w.name}`)
1718
+ );
1719
+ }
1720
+ for (const w of errored) {
1721
+ slots.push(
1722
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.error, children: [
1723
+ "\u2717 ",
1724
+ w.name
1725
+ ] }) }, `e-${w.name}`)
1726
+ );
1727
+ }
1728
+ if (idleCount > 0) {
1729
+ slots.push(
1730
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
1731
+ "\u25CB ",
1732
+ idleCount,
1733
+ " idle"
1734
+ ] }) }, "idle")
1735
+ );
1736
+ }
1737
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { paddingX: 1, children: [
1738
+ anyWorking && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AnimationActiveSentinel, {}),
1739
+ slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
1740
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.border, children: " \u2502 " }),
1741
+ slot
1742
+ ] }, i)),
1743
+ pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1744
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " " }),
1745
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.warning, children: [
1746
+ pendingMessages,
1747
+ " message",
1748
+ pendingMessages === 1 ? "" : "s",
1749
+ " queued"
1750
+ ] })
1751
+ ] })
1752
+ ] });
1753
+ }
1754
+ function StaticRowView({ row }) {
1755
+ if (row.kind === "banner") {
1756
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossBanner, { subtitle: "Orchestrator", showShortcuts: true }) });
1757
+ }
1758
+ if (row.kind === "user") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UserMessage, { text: row.text });
1759
+ if (row.kind === "assistant") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssistantRow, { item: row });
1760
+ if (row.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolHistoryRow, { item: row });
1761
+ if (row.kind === "worker_event") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(WorkerEventRow, { item: row });
1762
+ if (row.kind === "worker_error") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(WorkerErrorRow, { item: row });
1763
+ if (row.kind === "info") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(InfoRow, { text: row.text, level: row.level ?? "info" });
1764
+ if (row.kind === "task_dispatch") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TaskDispatchRow, { tasks: row.tasks });
1765
+ return null;
1766
+ }
1767
+ function TaskDispatchRow({
1768
+ tasks
1769
+ }) {
1770
+ const theme = useTheme();
1771
+ const count = tasks.length;
1772
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
1773
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
1774
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, bold: true, children: "\u23FA " }),
1775
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.text, bold: true, children: [
1776
+ "Running ",
1777
+ count,
1778
+ " task",
1779
+ count === 1 ? "" : "s",
1780
+ ":"
1781
+ ] })
1782
+ ] }),
1783
+ tasks.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
1784
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " \u2022 " }),
1785
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: projectColor(t.project), bold: true, children: t.project }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: ": " }),
1787
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: t.title })
1788
+ ] }, `${t.project}-${i}`))
1789
+ ] });
1790
+ }
1791
+ var SHORTCUT_PATTERNS = [
1792
+ // Modifier+Key combos: Ctrl+T, Shift+Tab, Cmd+K, Ctrl+Shift+P, Ctrl+C
1793
+ /\b(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super)(?:\s*\+\s*(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super))*\s*\+\s*(?:Tab|Enter|Esc|Escape|Space|Backspace|Delete|Del|Home|End|PageUp|PageDown|Up|Down|Left|Right|F[1-9]|F1[0-2]|[A-Z0-9]|\/|\?|\.|,|;|=|-)\b/g,
1794
+ // Bare named keys (only when surrounded by clear key context)
1795
+ /\b(?:Ctrl-[A-Z]|F[1-9]|F1[0-2])\b/g
1796
+ ];
1797
+ function highlightShortcuts(text) {
1798
+ if (!text) return text;
1799
+ const SENTINEL = "\uE000";
1800
+ const masks = [];
1801
+ let masked = text.replace(/```[\s\S]*?```|`[^`]+`/g, (m) => {
1802
+ const idx = masks.push(m) - 1;
1803
+ return `${SENTINEL}${idx}${SENTINEL}`;
1804
+ });
1805
+ for (const re of SHORTCUT_PATTERNS) {
1806
+ masked = masked.replace(re, (m) => `\`${m}\``);
1807
+ }
1808
+ return masked.replace(
1809
+ new RegExp(`${SENTINEL}(\\d+)${SENTINEL}`, "g"),
1810
+ (_, i) => masks[Number(i)]
1811
+ );
1812
+ }
1813
+ function AssistantRow({ item }) {
1814
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1815
+ AssistantMessage,
1816
+ {
1817
+ text: highlightShortcuts(item.text),
1818
+ thinking: item.thinking,
1819
+ thinkingMs: item.thinkingMs
1820
+ }
1821
+ );
1822
+ }
1823
+ function ToolHistoryRow({ item }) {
1824
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1825
+ ToolExecution,
1826
+ {
1827
+ status: "done",
1828
+ name: item.name,
1829
+ args: item.args,
1830
+ result: item.result,
1831
+ isError: item.isError,
1832
+ details: item.details,
1833
+ formatters: bossToolFormatters
1834
+ }
1835
+ );
1836
+ }
1837
+ function parseStatusGrade(text) {
1838
+ const matches = [...text.matchAll(/^\s*Status:\s*(DONE|UNVERIFIED|PARTIAL|BLOCKED|INFO)\b/gim)];
1839
+ const last = matches[matches.length - 1];
1840
+ if (!last) return null;
1841
+ return last[1].toUpperCase();
1842
+ }
1843
+ function parseWorkerTrailer(text) {
1844
+ const out = {};
1845
+ const grab = (label) => {
1846
+ const re = new RegExp(
1847
+ `^\\s*${label}:\\s*([\\s\\S]*?)(?=^\\s*(?:Changed|Skipped|Verified|Notes|Status):|$)`,
1848
+ "im"
1849
+ );
1850
+ const m = re.exec(text);
1851
+ if (!m) return void 0;
1852
+ const v = m[1].replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
1853
+ return v.length > 0 ? v : void 0;
1854
+ };
1855
+ out.changed = grab("Changed");
1856
+ out.skipped = grab("Skipped");
1857
+ out.verified = grab("Verified");
1858
+ out.notes = grab("Notes");
1859
+ return out;
1860
+ }
1861
+ function clip(text, maxLen) {
1862
+ return text.length <= maxLen ? text : text.slice(0, Math.max(1, maxLen - 1)) + "\u2026";
1863
+ }
1864
+ function summarizeFinalText(text, maxLen) {
1865
+ if (!text) return "";
1866
+ const trailer = parseWorkerTrailer(text);
1867
+ const parts = [];
1868
+ if (trailer.changed) parts.push(`Changed: ${trailer.changed}`);
1869
+ if (trailer.verified) parts.push(`Verified: ${trailer.verified}`);
1870
+ if (trailer.skipped) parts.push(`Skipped: ${trailer.skipped}`);
1871
+ if (trailer.notes) parts.push(`Notes: ${trailer.notes}`);
1872
+ if (parts.length > 0) return clip(parts.join(" \xB7 "), maxLen);
1873
+ const beforeSummary = text.split(/^Changed:|^Skipped:|^Verified:|^Notes:|^Status:/im)[0];
1874
+ const stripped = beforeSummary.replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^\s*[-*]\s+/gm, "").replace(/^#+\s+/gm, "").replace(/\s+/g, " ").trim();
1875
+ if (!stripped) return "";
1876
+ const firstSentence = stripped.match(/^[^.!?\n]+[.!?]/);
1877
+ return clip(firstSentence ? firstSentence[0] : stripped, maxLen);
1878
+ }
1879
+ function statusGradeColor(grade, theme) {
1880
+ switch (grade) {
1881
+ case "DONE":
1882
+ return theme.success;
1883
+ case "UNVERIFIED":
1884
+ case "PARTIAL":
1885
+ return theme.warning;
1886
+ case "BLOCKED":
1887
+ return theme.error;
1888
+ case "INFO":
1889
+ return theme.textDim;
1890
+ default:
1891
+ return theme.textDim;
1892
+ }
1893
+ }
1894
+ function WorkerEventRow({ item }) {
1895
+ const theme = useTheme();
1896
+ const { columns } = useTerminalSize();
1897
+ const failedCount = item.toolsUsed.filter((t) => !t.ok).length;
1898
+ const total = item.toolsUsed.length;
1899
+ const grade = parseStatusGrade(item.finalText);
1900
+ const loaderStatus = grade === "BLOCKED" || failedCount > 0 ? "error" : grade === "UNVERIFIED" || grade === "PARTIAL" ? "queued" : "done";
1901
+ const headerColor = loaderStatus === "error" ? theme.toolError : projectColor(item.project);
1902
+ const toolSummary = total === 0 ? "no tools" : failedCount > 0 ? `${total} tools (${failedCount} failed)` : `${total} tool${total === 1 ? "" : "s"}`;
1903
+ const fieldMaxLen = Math.max(20, columns - 14);
1904
+ const trailer = parseWorkerTrailer(item.finalText);
1905
+ const hasTrailer = !!(trailer.changed || trailer.skipped || trailer.verified || trailer.notes);
1906
+ const fallbackSummary = hasTrailer ? "" : summarizeFinalText(item.finalText, fieldMaxLen);
1907
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
1908
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "row", children: [
1909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: loaderStatus }),
1910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: headerColor, bold: true, children: item.project }),
1912
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: ` turn ${item.turnIndex}` }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${toolSummary}` }),
1914
+ grade && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: statusGradeColor(grade, theme), bold: true, children: grade })
1917
+ ] })
1918
+ ] }) })
1919
+ ] }),
1920
+ hasTrailer ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1921
+ trailer.changed && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrailerLine, { label: "Changed", value: trailer.changed, maxLen: fieldMaxLen }),
1922
+ trailer.verified && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1923
+ TrailerLine,
1924
+ {
1925
+ label: "Verified",
1926
+ value: trailer.verified,
1927
+ maxLen: fieldMaxLen,
1928
+ labelColor: theme.success
1929
+ }
1930
+ ),
1931
+ trailer.skipped && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1932
+ TrailerLine,
1933
+ {
1934
+ label: "Skipped",
1935
+ value: trailer.skipped,
1936
+ maxLen: fieldMaxLen,
1937
+ labelColor: theme.warning
1938
+ }
1939
+ ),
1940
+ trailer.notes && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrailerLine, { label: "Notes", value: trailer.notes, maxLen: fieldMaxLen })
1941
+ ] }) : fallbackSummary && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: fallbackSummary }) })
1942
+ ] });
1943
+ }
1944
+ function TrailerLine({
1945
+ label,
1946
+ value,
1947
+ maxLen,
1948
+ labelColor
1949
+ }) {
1950
+ const theme = useTheme();
1951
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "truncate", children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: labelColor ?? theme.textDim, bold: true, children: [
1953
+ label,
1954
+ ":"
1955
+ ] }),
1956
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: ` ${clip(value, maxLen - label.length - 2)}` })
1957
+ ] }) });
1958
+ }
1959
+ function WorkerErrorRow({ item }) {
1960
+ const theme = useTheme();
1961
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
1962
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "row", children: [
1963
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: "error" }),
1964
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
1965
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.toolError, bold: true, children: item.project }),
1966
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " worker error" })
1967
+ ] }) })
1968
+ ] }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.error, wrap: "wrap", children: item.message }) })
1970
+ ] });
1971
+ }
1972
+ function InfoRow({
1973
+ text,
1974
+ level
1975
+ }) {
1976
+ if (level === "info") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssistantMessage, { text });
1977
+ const theme = useTheme();
1978
+ const color = level === "error" ? theme.error : theme.warning;
1979
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { marginTop: 1, flexDirection: "row", children: [
1980
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: level === "error" ? "error" : "queued" }),
1981
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color, wrap: "wrap", children: text }) })
1982
+ ] });
1983
+ }
1984
+ function StreamingTurnView({
1985
+ turn,
1986
+ isRunning
1987
+ }) {
1988
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
1989
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1990
+ StreamingArea,
1991
+ {
1992
+ isRunning,
1993
+ streamingText: turn.text,
1994
+ streamingThinking: turn.thinking,
1995
+ thinkingMs: turn.thinkingMs
1996
+ }
1997
+ ),
1998
+ turn.tools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingToolRow, { tool: t }, t.toolCallId))
1999
+ ] });
2000
+ }
2001
+ function StreamingToolRow({ tool }) {
2002
+ if (tool.status === "running") {
2003
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2004
+ ToolExecution,
2005
+ {
2006
+ status: "running",
2007
+ name: tool.name,
2008
+ args: tool.args,
2009
+ formatters: bossToolFormatters
2010
+ }
2011
+ );
2012
+ }
2013
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2014
+ ToolExecution,
2015
+ {
2016
+ status: "done",
2017
+ name: tool.name,
2018
+ args: tool.args,
2019
+ result: tool.result ?? "",
2020
+ isError: tool.status === "error",
2021
+ details: tool.details,
2022
+ formatters: bossToolFormatters
2023
+ }
2024
+ );
2025
+ }
2026
+ function renderBossApp(opts) {
2027
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossApp, { boss: opts.boss }), { exitOnCtrlC: false });
2028
+ return {
2029
+ waitUntilExit: async () => {
2030
+ await instance.waitUntilExit();
2031
+ },
2032
+ unmount: () => instance.unmount()
2033
+ };
2034
+ }
2035
+
2036
+ // src/splash.tsx
2037
+ init_esm_shims();
2038
+ var import_react12 = __toESM(require_react(), 1);
2039
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
2040
+ var SPLASH_LINES = [
2041
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ",
2042
+ " \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 ",
2043
+ " \u2588\u2588\u2588 \u2591\u2591\u2591 \u2588\u2588\u2588 \u2591\u2591\u2591 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 ",
2044
+ "\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591 \u2588\u2588\u2588\u2591\u2591 ",
2045
+ "\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2591\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588 ",
2046
+ "\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2588\u2588\u2588",
2047
+ " \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 ",
2048
+ " \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 "
2049
+ ];
2050
+ var SPLASH_WIDTH = SPLASH_LINES[0].length;
2051
+ function colorForLine(lineIdx, totalLines, offset) {
2052
+ const t = totalLines <= 1 ? 0 : (lineIdx + offset) % totalLines;
2053
+ const idx = Math.floor(t / totalLines * GRADIENT.length) % GRADIENT.length;
2054
+ return GRADIENT[idx];
2055
+ }
2056
+ function SplashLogo({ offset }) {
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
2058
+ const hue = colorForLine(i, SPLASH_LINES.length, offset);
2059
+ const segments = [];
2060
+ let buf = "";
2061
+ let bufDim = false;
2062
+ for (const ch of line) {
2063
+ const dim = ch === "\u2591";
2064
+ if (segments.length === 0 && buf.length === 0) {
2065
+ buf = ch;
2066
+ bufDim = dim;
2067
+ continue;
2068
+ }
2069
+ if (dim === bufDim) {
2070
+ buf += ch;
2071
+ } else {
2072
+ segments.push({ text: buf, dim: bufDim });
2073
+ buf = ch;
2074
+ bufDim = dim;
2075
+ }
2076
+ }
2077
+ if (buf) segments.push({ text: buf, dim: bufDim });
2078
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: segments.map((seg, j) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: hue, dimColor: seg.dim, children: seg.text }, j)) }, i);
2079
+ }) });
2080
+ }
2081
+ function SplashScreen({ caption }) {
2082
+ const [offset, setOffset] = (0, import_react12.useState)(0);
2083
+ (0, import_react12.useEffect)(() => {
2084
+ const timer = setInterval(() => {
2085
+ setOffset((o) => o + 1);
2086
+ }, 120);
2087
+ return () => {
2088
+ clearInterval(timer);
2089
+ };
2090
+ }, []);
2091
+ const [size, setSize] = (0, import_react12.useState)(() => ({
2092
+ columns: process.stdout.columns ?? 80,
2093
+ rows: process.stdout.rows ?? 24
2094
+ }));
2095
+ (0, import_react12.useEffect)(() => {
2096
+ const handler = () => setSize({
2097
+ columns: process.stdout.columns ?? 80,
2098
+ rows: process.stdout.rows ?? 24
164
2099
  });
165
- await boss.initialize();
166
- await splash.dismiss();
167
- clearScreen();
168
- const ink = renderBossApp({ boss });
169
- // Don't register process.on("SIGINT") here. Ink puts stdin in raw mode, so
170
- // Ctrl+C is delivered as a byte (0x03) to InputArea — not as a process
171
- // signal. Registering SIGINT would race InputArea's onAbort and exit
172
- // immediately on the first press, breaking the double-press exit flow.
173
- // Run boss in background; await Ink unmount (triggered by useApp().exit()
174
- // in BossApp when the user double-presses Ctrl+C).
175
- const runPromise = boss.run();
176
- await ink.waitUntilExit();
177
- await boss.dispose();
178
- // Kill any in-flight radio stream before exiting otherwise the detached
179
- // mpv/ffplay child keeps playing after the user closed gg-boss.
180
- stopRadio();
181
- await runPromise.catch(() => { });
182
- process.exit(0);
2100
+ process.stdout.on("resize", handler);
2101
+ return () => {
2102
+ process.stdout.off("resize", handler);
2103
+ };
2104
+ }, []);
2105
+ const SPLASH_BLOCK_HEIGHT = SPLASH_LINES.length + 3;
2106
+ const verticalPad = Math.max(0, Math.floor((size.rows - SPLASH_BLOCK_HEIGHT) / 2));
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", width: size.columns, height: size.rows, alignItems: "center", children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { height: verticalPad, flexShrink: 0 }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", alignItems: "flex-start", flexShrink: 0, children: [
2110
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SplashLogo, { offset }),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { width: SPLASH_WIDTH, marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
2112
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.text, bold: true, children: BRAND }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { color: COLORS.textDim, children: [
2114
+ " v",
2115
+ VERSION
2116
+ ] }),
2117
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
2119
+ ] }) }),
2120
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { width: SPLASH_WIDTH, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.textDim, children: caption ?? "Spinning up the orchestrator\u2026" }) })
2121
+ ] })
2122
+ ] });
2123
+ }
2124
+ function showSplash(opts) {
2125
+ const start = Date.now();
2126
+ void playSplashAudio();
2127
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SplashScreen, { caption: opts.caption }));
2128
+ const audioDurationMs = getSplashAudioDurationMs();
2129
+ const defaultMinMs = audioDurationMs + 200;
2130
+ return {
2131
+ dismiss: async () => {
2132
+ const minMs = opts.minMs ?? defaultMinMs;
2133
+ const elapsed = Date.now() - start;
2134
+ const remaining = Math.max(0, minMs - elapsed);
2135
+ if (remaining > 0) {
2136
+ await new Promise((r) => setTimeout(r, remaining));
2137
+ }
2138
+ instance.unmount();
2139
+ await new Promise((r) => setImmediate(r));
2140
+ }
2141
+ };
2142
+ }
2143
+
2144
+ // src/cli.ts
2145
+ function parseProjectSpec(raw) {
2146
+ const eq = raw.indexOf("=");
2147
+ if (eq > 0) {
2148
+ const name = raw.slice(0, eq);
2149
+ const cwd2 = path4.resolve(raw.slice(eq + 1));
2150
+ return { name, cwd: cwd2 };
2151
+ }
2152
+ const cwd = path4.resolve(raw);
2153
+ return { name: path4.basename(cwd), cwd };
2154
+ }
2155
+ function parseArgs(argv) {
2156
+ const args = {
2157
+ projects: []
2158
+ };
2159
+ for (let i = 0; i < argv.length; i++) {
2160
+ const a = argv[i];
2161
+ if (a === "--project" || a === "-p") {
2162
+ const v = argv[++i];
2163
+ if (!v) throw new Error("--project requires a value");
2164
+ args.projects.push(parseProjectSpec(v));
2165
+ } else if (a === "--boss-model") {
2166
+ const v = argv[++i];
2167
+ if (!v) throw new Error("--boss-model requires a value");
2168
+ args.bossModel = v;
2169
+ } else if (a === "--worker-model") {
2170
+ const v = argv[++i];
2171
+ if (!v) throw new Error("--worker-model requires a value");
2172
+ args.workerModel = v;
2173
+ } else if (a === "--resume") {
2174
+ const v = argv[++i];
2175
+ if (!v) throw new Error("--resume requires a session id");
2176
+ args.resumeSessionId = v;
2177
+ } else if (a === "--help" || a === "-h") {
2178
+ printHelpAndExit();
2179
+ } else {
2180
+ throw new Error(`Unknown argument: ${a}`);
2181
+ }
2182
+ }
2183
+ return args;
2184
+ }
2185
+ function printHelpAndExit() {
2186
+ const c = (color, text) => source_default.hex(color)(text);
2187
+ process.stdout.write(
2188
+ "\n" + c(COLORS.primary, "GG Boss") + c(COLORS.textDim, " \u2014 orchestrator that drives multiple ggcoder workers from one chat.\n\n") + c(COLORS.text, "Usage\n") + " " + c(COLORS.accent, "ggboss") + c(
2189
+ COLORS.textDim,
2190
+ " start orchestrator using linked projects\n"
2191
+ ) + " " + c(COLORS.accent, "ggboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "ggboss continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "ggboss --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "ggboss --project <spec> [...]") + c(COLORS.textDim, " override links with explicit project(s)\n\n") + c(COLORS.text, "Options\n") + " " + c(COLORS.primary, "--project, -p <spec>") + c(COLORS.textDim, ' project to manage. spec is "cwd" or "name=cwd". repeatable.\n') + " " + c(COLORS.primary, "--boss-model <id>") + c(COLORS.textDim, " model for the orchestrator (default: claude-opus-4-7)\n") + " " + c(COLORS.primary, "--worker-model <id>") + c(COLORS.textDim, " model for workers (default: claude-sonnet-4-6)\n") + " " + c(COLORS.primary, "--help, -h") + c(COLORS.textDim, " show this help\n\n") + c(COLORS.textDim, "Talk to the boss at the prompt. Press ") + c(COLORS.accent, "Ctrl+C") + c(COLORS.textDim, " twice to exit.\n\n")
2192
+ );
2193
+ process.exit(0);
2194
+ }
2195
+ async function runOrchestrator(args) {
2196
+ if (args.projects.length === 0) {
2197
+ const links = await loadLinks();
2198
+ if (links.projects.length === 0) {
2199
+ process.stderr.write(
2200
+ "\n" + source_default.hex(COLORS.warning)("No linked projects.") + source_default.hex(COLORS.textDim)(" Run ") + source_default.hex(COLORS.accent)("ggboss link") + source_default.hex(COLORS.textDim)(" to choose, or pass ") + source_default.hex(COLORS.accent)("--project") + source_default.hex(COLORS.textDim)(".\n\n")
2201
+ );
2202
+ process.exit(1);
2203
+ }
2204
+ args.projects = links.projects.map((p) => ({ name: p.name, cwd: p.cwd }));
2205
+ }
2206
+ clearScreen();
2207
+ const splash = showSplash({
2208
+ caption: `Spinning up ${args.projects.length} worker${args.projects.length === 1 ? "" : "s"}\u2026`
2209
+ });
2210
+ const settings = await loadSettings();
2211
+ const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
2212
+ const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-7";
2213
+ const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
2214
+ const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
2215
+ initLogger({
2216
+ version: VERSION,
2217
+ bossProvider: finalBossProvider,
2218
+ bossModel: finalBossModel,
2219
+ bossThinking: settings.bossThinkingLevel,
2220
+ workerProvider: finalWorkerProvider,
2221
+ workerModel: finalWorkerModel,
2222
+ projectCount: args.projects.length
2223
+ });
2224
+ log("INFO", "cli", "linked projects", {
2225
+ projects: args.projects.map((p) => p.name).join(",")
2226
+ });
2227
+ const updateMessage = checkAndAutoUpdate(VERSION);
2228
+ if (updateMessage) log("INFO", "auto_update", updateMessage);
2229
+ const boss = new GGBoss({
2230
+ bossProvider: finalBossProvider,
2231
+ bossModel: finalBossModel,
2232
+ bossThinkingLevel: settings.bossThinkingLevel,
2233
+ workerProvider: finalWorkerProvider,
2234
+ workerModel: finalWorkerModel,
2235
+ projects: args.projects,
2236
+ continueRecent: args.continueRecent,
2237
+ resumeSessionId: args.resumeSessionId
2238
+ });
2239
+ await boss.initialize();
2240
+ await splash.dismiss();
2241
+ clearScreen();
2242
+ const ink = renderBossApp({ boss });
2243
+ const runPromise = boss.run();
2244
+ await ink.waitUntilExit();
2245
+ await boss.dispose();
2246
+ stopRadio();
2247
+ await runPromise.catch(() => {
2248
+ });
2249
+ process.exit(0);
183
2250
  }
184
2251
  async function main() {
185
- const argv = process.argv.slice(2);
186
- if (argv[0] === "link") {
187
- await runLinkCommand();
188
- process.exit(0);
189
- }
190
- // `ggboss continue` is a subcommand alias for "resume the most recent session".
191
- // Accept any flags after `continue` as normal flag args.
192
- const isContinue = argv[0] === "continue";
193
- const args = parseArgs(isContinue ? argv.slice(1) : argv);
194
- if (isContinue)
195
- args.continueRecent = true;
196
- await runOrchestrator(args);
2252
+ const argv = process.argv.slice(2);
2253
+ if (argv[0] === "link") {
2254
+ await runLinkCommand();
2255
+ process.exit(0);
2256
+ }
2257
+ const isContinue = argv[0] === "continue";
2258
+ const args = parseArgs(isContinue ? argv.slice(1) : argv);
2259
+ if (isContinue) args.continueRecent = true;
2260
+ await runOrchestrator(args);
197
2261
  }
198
2262
  main().catch((err) => {
199
- const message = err instanceof Error ? err.message : String(err);
200
- process.stderr.write(chalk.hex(COLORS.error)(`\ngg-boss failed: ${message}\n`));
201
- process.exit(1);
2263
+ const message = err instanceof Error ? err.message : String(err);
2264
+ process.stderr.write(source_default.hex(COLORS.error)(`
2265
+ gg-boss failed: ${message}
2266
+ `));
2267
+ process.exit(1);
202
2268
  });
203
2269
  //# sourceMappingURL=cli.js.map