@kenkaiiii/gg-boss 4.3.149 → 4.3.151

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.
package/dist/cli.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  use_app_default,
44
44
  use_input_default,
45
45
  use_stdout_default
46
- } from "./chunk-ZNVFGIDI.js";
46
+ } from "./chunk-JFMWBELU.js";
47
47
  import "./chunk-QT366Y52.js";
48
48
  import {
49
49
  source_default
@@ -86,8 +86,49 @@ var import_react2 = __toESM(require_react(), 1);
86
86
  // src/discover.ts
87
87
  init_esm_shims();
88
88
  import fs2 from "fs/promises";
89
+ import { createReadStream } from "fs";
90
+ import readline from "readline";
91
+ import os from "os";
89
92
  import path2 from "path";
90
93
  async function discoverProjects() {
94
+ const [gg, cc, cx] = await Promise.all([
95
+ discoverGgcoderProjects(),
96
+ discoverClaudeProjects(),
97
+ discoverCodexProjects()
98
+ ]);
99
+ const byPath = /* @__PURE__ */ new Map();
100
+ for (const p of [...gg, ...cc, ...cx]) {
101
+ const existing = byPath.get(p.path);
102
+ if (!existing) {
103
+ byPath.set(p.path, p);
104
+ continue;
105
+ }
106
+ byPath.set(p.path, {
107
+ name: existing.name,
108
+ path: existing.path,
109
+ lastActiveMs: Math.max(existing.lastActiveMs, p.lastActiveMs),
110
+ lastActiveDisplay: "",
111
+ // recomputed below
112
+ sources: mergeSources(existing.sources, p.sources)
113
+ });
114
+ }
115
+ const merged = Array.from(byPath.values()).map((p) => ({
116
+ ...p,
117
+ lastActiveDisplay: formatRelativeTime(p.lastActiveMs)
118
+ }));
119
+ merged.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
120
+ return merged;
121
+ }
122
+ var SOURCE_ORDER = {
123
+ ggcoder: 0,
124
+ "claude-code": 1,
125
+ codex: 2
126
+ };
127
+ function mergeSources(a, b) {
128
+ const set = /* @__PURE__ */ new Set([...a, ...b]);
129
+ return Array.from(set).sort((x, y) => SOURCE_ORDER[x] - SOURCE_ORDER[y]);
130
+ }
131
+ async function discoverGgcoderProjects() {
91
132
  const sessionsDir = getAppPaths().sessionsDir;
92
133
  let entries;
93
134
  try {
@@ -98,46 +139,177 @@ async function discoverProjects() {
98
139
  const results = [];
99
140
  for (const entry of entries) {
100
141
  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;
113
- }
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
- }
142
+ const mtime = await maxJsonlMtime(dir);
143
+ if (mtime === null) continue;
124
144
  const decoded = "/" + entry.replace(/_/g, "/");
125
- try {
126
- const pathStat = await fs2.stat(decoded);
127
- if (!pathStat.isDirectory()) continue;
128
- } catch {
129
- continue;
130
- }
145
+ if (!await isDirectory(decoded)) continue;
131
146
  results.push({
132
147
  name: path2.basename(decoded),
133
148
  path: decoded,
134
- lastActiveMs: maxMtime,
135
- lastActiveDisplay: formatRelativeTime(maxMtime)
149
+ lastActiveMs: mtime,
150
+ lastActiveDisplay: formatRelativeTime(mtime),
151
+ sources: ["ggcoder"]
136
152
  });
137
153
  }
138
- results.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
139
154
  return results;
140
155
  }
156
+ async function discoverClaudeProjects() {
157
+ const projectsDir = path2.join(os.homedir(), ".claude", "projects");
158
+ let entries;
159
+ try {
160
+ entries = await fs2.readdir(projectsDir);
161
+ } catch {
162
+ return [];
163
+ }
164
+ const results = await Promise.all(
165
+ entries.map(async (entry) => {
166
+ const dir = path2.join(projectsDir, entry);
167
+ const mtime = await maxJsonlMtime(dir);
168
+ if (mtime === null) return null;
169
+ const cwd = await readFirstFromJsonlDir(dir, claudeCwdExtractor) ?? fallbackDashDecode(entry);
170
+ if (!cwd) return null;
171
+ if (!await isDirectory(cwd)) return null;
172
+ return {
173
+ name: path2.basename(cwd),
174
+ path: cwd,
175
+ lastActiveMs: mtime,
176
+ lastActiveDisplay: formatRelativeTime(mtime),
177
+ sources: ["claude-code"]
178
+ };
179
+ })
180
+ );
181
+ return results.filter((p) => p !== null);
182
+ }
183
+ async function discoverCodexProjects() {
184
+ const sessionsDir = path2.join(os.homedir(), ".codex", "sessions");
185
+ if (!await isDirectory(sessionsDir)) return [];
186
+ const files = await collectJsonlFiles(sessionsDir, 4);
187
+ if (files.length === 0) return [];
188
+ files.sort((a, b) => b.mtime - a.mtime);
189
+ const byCwd = /* @__PURE__ */ new Map();
190
+ for (const f of files) {
191
+ const cwd = await readFirstFromFile(f.path, codexCwdExtractor);
192
+ if (!cwd) continue;
193
+ const prev = byCwd.get(cwd);
194
+ if (prev === void 0 || f.mtime > prev) byCwd.set(cwd, f.mtime);
195
+ }
196
+ const results = [];
197
+ for (const [cwd, mtime] of byCwd) {
198
+ if (!await isDirectory(cwd)) continue;
199
+ results.push({
200
+ name: path2.basename(cwd),
201
+ path: cwd,
202
+ lastActiveMs: mtime,
203
+ lastActiveDisplay: formatRelativeTime(mtime),
204
+ sources: ["codex"]
205
+ });
206
+ }
207
+ return results;
208
+ }
209
+ async function isDirectory(p) {
210
+ try {
211
+ const s = await fs2.stat(p);
212
+ return s.isDirectory();
213
+ } catch {
214
+ return false;
215
+ }
216
+ }
217
+ async function maxJsonlMtime(dir) {
218
+ if (!await isDirectory(dir)) return null;
219
+ const files = await collectJsonlFiles(dir, 2);
220
+ if (files.length === 0) return null;
221
+ let max = 0;
222
+ for (const f of files) if (f.mtime > max) max = f.mtime;
223
+ return max > 0 ? max : null;
224
+ }
225
+ async function collectJsonlFiles(dir, maxDepth) {
226
+ const out = [];
227
+ await walk(dir, 0);
228
+ return out;
229
+ async function walk(current, depth) {
230
+ let entries;
231
+ try {
232
+ entries = await fs2.readdir(current, { withFileTypes: true });
233
+ } catch {
234
+ return;
235
+ }
236
+ for (const e of entries) {
237
+ const full = path2.join(current, e.name);
238
+ if (e.isFile() && e.name.endsWith(".jsonl")) {
239
+ try {
240
+ const s = await fs2.stat(full);
241
+ out.push({ path: full, mtime: s.mtimeMs });
242
+ } catch {
243
+ }
244
+ } else if (e.isDirectory() && depth < maxDepth) {
245
+ await walk(full, depth + 1);
246
+ }
247
+ }
248
+ }
249
+ }
250
+ var claudeCwdExtractor = (line) => {
251
+ try {
252
+ const parsed = JSON.parse(line);
253
+ if (typeof parsed.cwd === "string" && parsed.cwd.startsWith("/")) return parsed.cwd;
254
+ } catch {
255
+ }
256
+ return null;
257
+ };
258
+ var CODEX_CWD_RE = /<cwd>([^<]+)<\/cwd>/;
259
+ var codexCwdExtractor = (line) => {
260
+ try {
261
+ const parsed = JSON.parse(line);
262
+ const cwd = parsed.payload?.cwd;
263
+ if (typeof cwd === "string" && cwd.startsWith("/")) return cwd;
264
+ } catch {
265
+ }
266
+ const m = CODEX_CWD_RE.exec(line);
267
+ if (m && m[1] && m[1].startsWith("/")) return m[1];
268
+ return null;
269
+ };
270
+ async function readFirstFromJsonlDir(dir, extractor) {
271
+ const files = await collectJsonlFiles(dir, 2);
272
+ if (files.length === 0) return null;
273
+ files.sort((a, b) => b.mtime - a.mtime);
274
+ for (const f of files) {
275
+ const v = await readFirstFromFile(f.path, extractor);
276
+ if (v) return v;
277
+ }
278
+ return null;
279
+ }
280
+ async function readFirstFromFile(file, extractor) {
281
+ return new Promise((resolve) => {
282
+ const stream = createReadStream(file, { encoding: "utf-8" });
283
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
284
+ let lines = 0;
285
+ let done = false;
286
+ const MAX_LINES = 200;
287
+ const finish = (value) => {
288
+ if (done) return;
289
+ done = true;
290
+ resolve(value);
291
+ rl.close();
292
+ stream.destroy();
293
+ };
294
+ rl.on("line", (line) => {
295
+ if (done) return;
296
+ lines++;
297
+ if (lines > MAX_LINES) {
298
+ finish(null);
299
+ return;
300
+ }
301
+ const v = extractor(line);
302
+ if (v) finish(v);
303
+ });
304
+ rl.on("close", () => finish(null));
305
+ rl.on("error", () => finish(null));
306
+ stream.on("error", () => finish(null));
307
+ });
308
+ }
309
+ function fallbackDashDecode(entry) {
310
+ if (!entry.startsWith("-")) return null;
311
+ return "/" + entry.slice(1).replace(/-/g, "/");
312
+ }
141
313
  function formatRelativeTime(ms) {
142
314
  if (ms === 0) return "\u2014";
143
315
  const diff = Date.now() - ms;
@@ -164,7 +336,7 @@ init_esm_shims();
164
336
  // package.json
165
337
  var package_default = {
166
338
  name: "@kenkaiiii/gg-boss",
167
- version: "4.3.149",
339
+ version: "4.3.151",
168
340
  type: "module",
169
341
  description: "Orchestrator agent that drives multiple ggcoder sessions across projects from a single chat",
170
342
  license: "MIT",
@@ -334,6 +506,14 @@ function GradientText({ text }) {
334
506
  // src/link-command.tsx
335
507
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
336
508
  var VISIBLE_ROWS = 12;
509
+ function sourceBadge(sources) {
510
+ if (sources.length > 1) return { label: "[mix]", color: COLORS.success };
511
+ const only = sources[0];
512
+ if (only === "ggcoder") return { label: "[gg ]", color: COLORS.accent };
513
+ if (only === "claude-code") return { label: "[cc ]", color: COLORS.warning };
514
+ if (only === "codex") return { label: "[cx ]", color: COLORS.primary };
515
+ return { label: "[?? ]", color: COLORS.textDim };
516
+ }
337
517
  function LinkScreen({ projects, initialSelected, onDone }) {
338
518
  const [cursor, setCursor] = (0, import_react2.useState)(0);
339
519
  const [selected, setSelected] = (0, import_react2.useState)(new Set(initialSelected));
@@ -406,11 +586,14 @@ function LinkScreen({ projects, initialSelected, onDone }) {
406
586
  const arrow = isCursor ? "\u276F" : " ";
407
587
  const nameColor = isCursor ? COLORS.primary : isSelected ? COLORS.success : COLORS.text;
408
588
  const checkboxColor = isSelected ? COLORS.success : COLORS.textDim;
589
+ const badge = sourceBadge(p.sources);
409
590
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
410
591
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.primary, children: arrow }),
411
592
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
412
593
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: checkboxColor, children: checkbox }),
413
594
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: badge.color, children: badge.label }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
414
597
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: nameColor, bold: isCursor, children: p.name }),
415
598
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " " }),
416
599
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: p.lastActiveDisplay })
@@ -1219,13 +1402,13 @@ init_esm_shims();
1219
1402
  import { spawn as spawn2 } from "child_process";
1220
1403
  import fs3 from "fs";
1221
1404
  import path3 from "path";
1222
- import os from "os";
1405
+ import os2 from "os";
1223
1406
  var PACKAGE_NAME = "@kenkaiiii/gg-boss";
1224
1407
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
1225
1408
  var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
1226
1409
  var FETCH_TIMEOUT_MS = 1e4;
1227
1410
  function getStateFilePath() {
1228
- return path3.join(os.homedir(), ".gg", "boss", "update-state.json");
1411
+ return path3.join(os2.homedir(), ".gg", "boss", "update-state.json");
1229
1412
  }
1230
1413
  function readState() {
1231
1414
  try {
@@ -1391,7 +1574,7 @@ function BossAppInner({ boss, resetUI }) {
1391
1574
  const state = useBossState();
1392
1575
  const { exit } = use_app_default();
1393
1576
  const { stdout } = use_stdout_default();
1394
- const { resizeKey, columns } = useTerminalSize();
1577
+ const { resizeKey, columns, rows } = useTerminalSize();
1395
1578
  const runStartRef = (0, import_react11.useRef)(null);
1396
1579
  runStartRef.current = state.runStartMs;
1397
1580
  const charCountRef = (0, import_react11.useRef)(0);
@@ -1399,11 +1582,8 @@ function BossAppInner({ boss, resetUI }) {
1399
1582
  const realTokensAccumRef = (0, import_react11.useRef)(0);
1400
1583
  realTokensAccumRef.current = state.bossInputTokens;
1401
1584
  const [lastUserMessage, setLastUserMessage] = (0, import_react11.useState)("");
1402
- const [overlay, setOverlay] = (0, import_react11.useState)(
1403
- null
1404
- );
1585
+ const overlay = state.overlay;
1405
1586
  const [currentRadio, setCurrentRadio] = (0, import_react11.useState)(() => getCurrentStation());
1406
- const [staticKey, setStaticKey] = (0, import_react11.useState)(0);
1407
1587
  const [updatePending, setUpdatePending] = (0, import_react11.useState)(
1408
1588
  () => getPendingUpdate(VERSION) !== null
1409
1589
  );
@@ -1443,13 +1623,15 @@ function BossAppInner({ boss, resetUI }) {
1443
1623
  );
1444
1624
  const openOverlay = (0, import_react11.useCallback)(
1445
1625
  (next) => {
1446
- setOverlay(next);
1626
+ bossStore.setOverlay(next);
1627
+ if (resetUI) resetUI();
1447
1628
  },
1448
- []
1629
+ [resetUI]
1449
1630
  );
1450
1631
  const closeOverlay = (0, import_react11.useCallback)(() => {
1451
- setOverlay(null);
1452
- }, []);
1632
+ bossStore.setOverlay(null);
1633
+ if (resetUI) resetUI();
1634
+ }, [resetUI]);
1453
1635
  void stdout;
1454
1636
  const handleDoubleExit = useDoublePress(
1455
1637
  (pending) => bossStore.setExitPending(pending),
@@ -1484,10 +1666,9 @@ function BossAppInner({ boss, resetUI }) {
1484
1666
  bossStore.appendInfo(buildHelpText(), "info");
1485
1667
  return true;
1486
1668
  case "clear":
1487
- resetUI?.();
1488
1669
  bossStore.clearHistory();
1670
+ resetUI?.();
1489
1671
  await boss.resetConversation();
1490
- setStaticKey((k) => k + 1);
1491
1672
  bossStore.appendInfo("Session cleared.", "info");
1492
1673
  return true;
1493
1674
  case "model-boss":
@@ -1543,8 +1724,14 @@ function BossAppInner({ boss, resetUI }) {
1543
1724
  }
1544
1725
  handleDoubleExit();
1545
1726
  };
1727
+ if (rows < 14) {
1728
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: columns, paddingX: 1, marginTop: 1, children: [
1729
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: COLORS.accent, children: "Terminal too small" }),
1730
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, children: `Resize to at least 14 rows to use GG Boss (currently ${rows}).` })
1731
+ ] });
1732
+ }
1546
1733
  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}`),
1734
+ /* @__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),
1548
1735
  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
1736
  state.streaming && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingTurnView, { turn: state.streaming, isRunning: state.phase === "working" }),
1550
1737
  state.phase === "working" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
@@ -1696,6 +1883,7 @@ function WorkerStatusBar({
1696
1883
  pendingMessages
1697
1884
  }) {
1698
1885
  const theme = useTheme();
1886
+ const { columns } = useTerminalSize();
1699
1887
  const working = workers.filter((w) => w.status === "working");
1700
1888
  const errored = workers.filter((w) => w.status === "error");
1701
1889
  const idleCount = workers.length - working.length - errored.length;
@@ -1734,19 +1922,21 @@ function WorkerStatusBar({
1734
1922
  ] }) }, "idle")
1735
1923
  );
1736
1924
  }
1737
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { paddingX: 1, children: [
1925
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { paddingX: 1, width: columns, flexShrink: 1, children: [
1738
1926
  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"
1927
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "truncate", children: [
1928
+ slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
1929
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.border, children: " \u2502 " }),
1930
+ slot
1931
+ ] }, i)),
1932
+ pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1933
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " " }),
1934
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.warning, children: [
1935
+ pendingMessages,
1936
+ " message",
1937
+ pendingMessages === 1 ? "" : "s",
1938
+ " queued"
1939
+ ] })
1750
1940
  ] })
1751
1941
  ] })
1752
1942
  ] });