@kenkaiiii/gg-boss 4.3.148 → 4.3.150

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
@@ -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"]
152
+ });
153
+ }
154
+ return results;
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"]
136
205
  });
137
206
  }
138
- results.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
139
207
  return results;
140
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.148",
339
+ version: "4.3.150",
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 {
@@ -1766,10 +1949,9 @@ function StaticRowView({ row }) {
1766
1949
  return null;
1767
1950
  }
1768
1951
  function UpdateNoticeRow({ text }) {
1769
- const theme = useTheme();
1770
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: theme.success, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.success, bold: true, wrap: "wrap", children: [
1771
- "\u2728 ",
1772
- text
1952
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: COLORS.accent, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.accent, bold: true, children: "\u2728 " }),
1954
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, bold: true, children: text })
1773
1955
  ] }) });
1774
1956
  }
1775
1957
  function TaskDispatchRow({