groundcrew-cli 0.16.6 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +57 -67
  2. package/package.json +1 -1
  3. package/src/index.ts +81 -80
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import{createRequire}from'module';const require=createRequire(import.meta.url);
4
4
  import fs from "fs/promises";
5
5
  import { existsSync } from "fs";
6
6
  import path from "path";
7
+ import os from "os";
7
8
  import readline from "readline";
8
9
  import { execFile, execFileSync } from "child_process";
9
10
  import { promisify } from "util";
@@ -73,35 +74,21 @@ close access fp`], { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] });
73
74
  return null;
74
75
  }
75
76
  }
76
- var GROUNDCREW_DIR = ".groundcrew";
77
- var SESSIONS_DIR = path.join(GROUNDCREW_DIR, "sessions");
78
- var ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_DIR, "active-sessions.json");
79
- var HISTORY_FILE = path.join(GROUNDCREW_DIR, "history.json");
77
+ var GROUNDCREW_HOME = path.join(os.homedir(), ".groundcrew");
78
+ var SESSIONS_DIR = path.join(GROUNDCREW_HOME, "sessions");
79
+ var ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_HOME, "active-sessions.json");
80
+ var HISTORY_FILE = path.join(GROUNDCREW_HOME, "history.json");
81
+ var REPO_NAME = "";
80
82
  async function resolveRoot() {
81
83
  let root = null;
82
84
  try {
83
85
  const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"]);
84
- const gitRoot = stdout.trim();
85
- if (gitRoot && existsSync(path.join(gitRoot, ".groundcrew"))) {
86
- root = gitRoot;
87
- }
86
+ root = stdout.trim() || null;
88
87
  } catch {
89
88
  }
90
- if (!root) {
91
- let dir = process.cwd();
92
- while (dir !== path.dirname(dir)) {
93
- if (existsSync(path.join(dir, ".groundcrew"))) {
94
- root = dir;
95
- break;
96
- }
97
- dir = path.dirname(dir);
98
- }
99
- }
100
89
  if (!root) root = process.cwd();
101
- GROUNDCREW_DIR = path.join(root, ".groundcrew");
102
- SESSIONS_DIR = path.join(GROUNDCREW_DIR, "sessions");
103
- ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_DIR, "active-sessions.json");
104
- HISTORY_FILE = path.join(GROUNDCREW_DIR, "history.json");
90
+ REPO_NAME = path.basename(root).replace(/[^a-zA-Z0-9_-]/g, "_") || "unknown";
91
+ await fs.mkdir(SESSIONS_DIR, { recursive: true });
105
92
  }
106
93
  async function readActiveSessions() {
107
94
  try {
@@ -110,6 +97,9 @@ async function readActiveSessions() {
110
97
  return {};
111
98
  }
112
99
  }
100
+ function isRepoSession(sessionId) {
101
+ return sessionId.startsWith(REPO_NAME + "-");
102
+ }
113
103
  async function resolveSessionDir(explicitSession) {
114
104
  if (explicitSession) {
115
105
  const dir = path.join(SESSIONS_DIR, explicitSession);
@@ -119,10 +109,10 @@ async function resolveSessionDir(explicitSession) {
119
109
  return dir;
120
110
  }
121
111
  const sessions2 = await readActiveSessions();
122
- const ids = Object.keys(sessions2);
112
+ const ids = Object.keys(sessions2).filter(isRepoSession);
123
113
  if (ids.length === 0) {
124
114
  try {
125
- const dirs = await fs.readdir(SESSIONS_DIR);
115
+ const dirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession);
126
116
  if (dirs.length === 1) return path.join(SESSIONS_DIR, dirs[0]);
127
117
  if (dirs.length > 1) {
128
118
  let latest2 = { dir: dirs[0], mtime: 0 };
@@ -139,7 +129,7 @@ async function resolveSessionDir(explicitSession) {
139
129
  }
140
130
  } catch {
141
131
  }
142
- throw new Error("No active sessions. Start Copilot with groundcrew first.");
132
+ throw new Error(`No active sessions for repo "${REPO_NAME}". Start Copilot with groundcrew first.`);
143
133
  }
144
134
  if (ids.length === 1) {
145
135
  return path.join(SESSIONS_DIR, ids[0]);
@@ -185,10 +175,6 @@ var cyan = (t) => color(36, t);
185
175
  var dim = (t) => color(2, t);
186
176
  var bold = (t) => color(1, t);
187
177
  var red = (t) => color(31, t);
188
- async function init() {
189
- await fs.mkdir(SESSIONS_DIR, { recursive: true });
190
- console.log(green("Groundcrew initialized.") + ` ${dim(GROUNDCREW_DIR + "/ created")}`);
191
- }
192
178
  async function add(taskText, priority, sessionDir) {
193
179
  const queue = await readQueue(sessionDir);
194
180
  const task = {
@@ -308,23 +294,35 @@ async function sessions() {
308
294
  console.log(dim("No sessions found."));
309
295
  return;
310
296
  }
311
- console.log(bold("Sessions:\n"));
297
+ const byRepo = /* @__PURE__ */ new Map();
312
298
  for (const dir of allDirs) {
313
- const isActive = ids.includes(dir);
314
- const sessionDir = path.join(SESSIONS_DIR, dir);
315
- let info = "";
316
- try {
317
- const session = JSON.parse(await fs.readFile(path.join(sessionDir, "session.json"), "utf-8"));
318
- const startTime = new Date(session.started).getTime();
319
- const minutes = Math.round((Date.now() - startTime) / 6e4);
320
- const statusColor = session.status === "active" ? green : session.status === "parked" ? yellow : dim;
321
- info = `${statusColor(session.status)} | ${minutes}min | ${session.tasksCompleted || 0} tasks done`;
322
- } catch {
323
- info = dim("no session data");
299
+ const dashIdx = dir.lastIndexOf("-");
300
+ const repo = dashIdx > 0 ? dir.substring(0, dashIdx) : "unknown";
301
+ if (!byRepo.has(repo)) byRepo.set(repo, []);
302
+ byRepo.get(repo).push(dir);
303
+ }
304
+ console.log(bold("Sessions:\n"));
305
+ for (const [repo, dirs] of byRepo) {
306
+ const isCurrent = repo === REPO_NAME;
307
+ console.log(` ${isCurrent ? green(repo) : dim(repo)}`);
308
+ for (const dir of dirs) {
309
+ const isActive = ids.includes(dir);
310
+ const sessionDir = path.join(SESSIONS_DIR, dir);
311
+ let info = "";
312
+ try {
313
+ const session = JSON.parse(await fs.readFile(path.join(sessionDir, "session.json"), "utf-8"));
314
+ const startTime = new Date(session.started).getTime();
315
+ const minutes = Math.round((Date.now() - startTime) / 6e4);
316
+ const statusColor = session.status === "active" ? green : session.status === "parked" ? yellow : dim;
317
+ info = `${statusColor(session.status)} | ${minutes}min | ${session.tasksCompleted || 0} tasks done`;
318
+ } catch {
319
+ info = dim("no session data");
320
+ }
321
+ const queue = await readQueue(sessionDir);
322
+ const marker = isActive ? green("*") : " ";
323
+ const shortId = dir.substring(repo.length + 1);
324
+ console.log(` ${marker} ${cyan(shortId)} ${info} | ${queue.tasks.length} queued`);
324
325
  }
325
- const queue = await readQueue(sessionDir);
326
- const marker = isActive ? green("*") : " ";
327
- console.log(` ${marker} ${cyan(dir)} ${info} | ${queue.tasks.length} queued`);
328
326
  }
329
327
  if (ids.length > 0) {
330
328
  console.log(dim(`
@@ -381,14 +379,14 @@ async function destroyOne(sessionId) {
381
379
  }
382
380
  async function stopAll() {
383
381
  const active = await readActiveSessions();
384
- const ids = Object.keys(active);
382
+ const ids = Object.keys(active).filter(isRepoSession);
385
383
  let allDirs = [];
386
384
  try {
387
- allDirs = await fs.readdir(SESSIONS_DIR);
385
+ allDirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession);
388
386
  } catch {
389
387
  }
390
388
  if (ids.length === 0 && allDirs.length === 0) {
391
- console.log(dim("No sessions to stop."));
389
+ console.log(dim(`No sessions to stop for repo "${REPO_NAME}".`));
392
390
  return;
393
391
  }
394
392
  for (const id of ids) {
@@ -404,36 +402,32 @@ async function stopAll() {
404
402
  console.log(` ${green("cleaned")} ${cyan(dir)} ${dim("(orphaned)")}`);
405
403
  }
406
404
  }
407
- await fs.writeFile(ACTIVE_SESSIONS_FILE, "{}");
408
- console.log(green("\nAll sessions stopped."));
405
+ const remaining = await readActiveSessions();
406
+ for (const id of ids) delete remaining[id];
407
+ await fs.writeFile(ACTIVE_SESSIONS_FILE, JSON.stringify(remaining, null, 2));
408
+ console.log(green(`
409
+ All ${REPO_NAME} sessions stopped.`));
409
410
  }
410
411
  async function destroyAll() {
411
412
  await stopAll();
412
413
  try {
413
- const dirs = await fs.readdir(SESSIONS_DIR);
414
+ const dirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession);
414
415
  for (const dir of dirs) {
415
416
  await fs.rm(path.join(SESSIONS_DIR, dir), { recursive: true, force: true });
416
417
  }
417
418
  } catch {
418
419
  }
419
420
  try {
420
- await fs.unlink(HISTORY_FILE);
421
- } catch {
422
- }
423
- try {
424
- await fs.unlink(ACTIVE_SESSIONS_FILE);
425
- } catch {
426
- }
427
- try {
428
- await fs.unlink(path.join(GROUNDCREW_DIR, "tool-history.csv"));
421
+ await fs.unlink(path.join(GROUNDCREW_HOME, "tool-history.csv"));
429
422
  } catch {
430
423
  }
431
- console.log(green("All session data and history deleted."));
424
+ console.log(green(`All ${REPO_NAME} session data deleted.`));
432
425
  }
433
426
  async function listSessionChoices() {
434
427
  const active = await readActiveSessions();
435
428
  const choices = [];
436
429
  for (const [id, entry] of Object.entries(active)) {
430
+ if (!isRepoSession(id)) continue;
437
431
  const dir = path.join(SESSIONS_DIR, id);
438
432
  let status2 = "active";
439
433
  let minutes = 0;
@@ -458,7 +452,7 @@ async function listSessionChoices() {
458
452
  async function pickSession(rl) {
459
453
  const choices = await listSessionChoices();
460
454
  if (choices.length === 0) {
461
- console.log(red("No active sessions. Start Copilot with groundcrew first."));
455
+ console.log(red(`No active sessions for repo "${REPO_NAME}". Start Copilot with groundcrew first.`));
462
456
  return null;
463
457
  }
464
458
  if (choices.length === 1) {
@@ -1116,7 +1110,6 @@ ${bold("groundcrew")} \u2014 CLI companion for the Groundcrew Copilot plugin
1116
1110
  ${bold("Usage:")}
1117
1111
  groundcrew chat Interactive chat mode (recommended)
1118
1112
  groundcrew chat --session <id> Chat with a specific session
1119
- groundcrew init Initialize .groundcrew/ in current dir
1120
1113
  groundcrew add <task> Add a task to the queue
1121
1114
  groundcrew add --priority <task> Add an urgent task (processed first)
1122
1115
  groundcrew add --session <id> <task> Add to a specific session
@@ -1127,9 +1120,9 @@ ${bold("Usage:")}
1127
1120
  groundcrew sessions List all sessions
1128
1121
  groundcrew history Show completed tasks
1129
1122
  groundcrew clear Clear all pending tasks
1130
- groundcrew stop Stop all active sessions
1123
+ groundcrew stop Stop all sessions for current repo
1131
1124
  groundcrew stop --session <id> Stop a specific session
1132
- groundcrew destroy Delete all sessions, history, and data
1125
+ groundcrew destroy Delete all sessions for current repo
1133
1126
  groundcrew destroy --session <id> Delete a specific session
1134
1127
 
1135
1128
  ${bold("Session targeting:")}
@@ -1161,9 +1154,6 @@ async function main() {
1161
1154
  const { value: explicitSession, remaining: args } = extractFlag(rawArgs, "--session");
1162
1155
  const command = args[0];
1163
1156
  switch (command) {
1164
- case "init":
1165
- await init();
1166
- return;
1167
1157
  case "chat":
1168
1158
  await chat(explicitSession);
1169
1159
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groundcrew-cli",
3
- "version": "0.16.6",
3
+ "version": "0.17.0",
4
4
  "description": "CLI companion for Groundcrew — queue tasks, send feedback, monitor your Copilot agent from another terminal.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "fs/promises";
2
2
  import { existsSync } from "fs";
3
3
  import path from "path";
4
+ import os from "os";
4
5
  import readline from "readline";
5
6
  import { execFile, execFileSync } from "child_process";
6
7
  import { promisify } from "util";
@@ -68,17 +69,16 @@ close access fp`], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
68
69
  } catch { return null; }
69
70
  }
70
71
 
71
- // Resolved at startup by resolveRoot() — git-aware project root discovery
72
- let GROUNDCREW_DIR = ".groundcrew";
73
- let SESSIONS_DIR = path.join(GROUNDCREW_DIR, "sessions");
74
- let ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_DIR, "active-sessions.json");
75
- let HISTORY_FILE = path.join(GROUNDCREW_DIR, "history.json");
72
+ // ── Centralized storage at ~/.groundcrew ─────────────────────────────────────
73
+ const GROUNDCREW_HOME = path.join(os.homedir(), ".groundcrew");
74
+ const SESSIONS_DIR = path.join(GROUNDCREW_HOME, "sessions");
75
+ const ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_HOME, "active-sessions.json");
76
+ const HISTORY_FILE = path.join(GROUNDCREW_HOME, "history.json");
77
+
78
+ let REPO_NAME = "";
76
79
 
77
80
  /**
78
- * Resolve the project root that contains .groundcrew/.
79
- * Uses git rev-parse --show-toplevel for worktree support, then walks up
80
- * from CWD as fallback. This ensures the CLI works from subdirectories
81
- * and git worktrees.
81
+ * Derive repo name from CWD (git-aware for worktree/subdirectory support).
82
82
  */
83
83
  async function resolveRoot(): Promise<void> {
84
84
  let root: string | null = null;
@@ -86,31 +86,16 @@ async function resolveRoot(): Promise<void> {
86
86
  // 1. Try git rev-parse --show-toplevel (worktree-aware)
87
87
  try {
88
88
  const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"]);
89
- const gitRoot = stdout.trim();
90
- if (gitRoot && existsSync(path.join(gitRoot, ".groundcrew"))) {
91
- root = gitRoot;
92
- }
89
+ root = stdout.trim() || null;
93
90
  } catch { /* not a git repo or git not installed */ }
94
91
 
95
- // 2. Walk up from CWD looking for .groundcrew/
96
- if (!root) {
97
- let dir = process.cwd();
98
- while (dir !== path.dirname(dir)) {
99
- if (existsSync(path.join(dir, ".groundcrew"))) {
100
- root = dir;
101
- break;
102
- }
103
- dir = path.dirname(dir);
104
- }
105
- }
106
-
107
- // 3. Fallback to CWD (for `groundcrew init`)
92
+ // 2. Fallback to CWD
108
93
  if (!root) root = process.cwd();
109
94
 
110
- GROUNDCREW_DIR = path.join(root, ".groundcrew");
111
- SESSIONS_DIR = path.join(GROUNDCREW_DIR, "sessions");
112
- ACTIVE_SESSIONS_FILE = path.join(GROUNDCREW_DIR, "active-sessions.json");
113
- HISTORY_FILE = path.join(GROUNDCREW_DIR, "history.json");
95
+ REPO_NAME = path.basename(root).replace(/[^a-zA-Z0-9_-]/g, "_") || "unknown";
96
+
97
+ // Ensure centralized dirs exist
98
+ await fs.mkdir(SESSIONS_DIR, { recursive: true });
114
99
  }
115
100
 
116
101
  interface Task {
@@ -147,9 +132,17 @@ async function readActiveSessions(): Promise<Record<string, ActiveSessionEntry>>
147
132
  }
148
133
  }
149
134
 
135
+ /**
136
+ * Filter session IDs that belong to the current repo.
137
+ * Session IDs are prefixed with repo name: "mekari_credit-a1b2c3d4"
138
+ */
139
+ function isRepoSession(sessionId: string): boolean {
140
+ return sessionId.startsWith(REPO_NAME + "-");
141
+ }
142
+
150
143
  /**
151
144
  * Resolve which session to target.
152
- * Priority: --session flag > single active session > error if ambiguous.
145
+ * Priority: --session flag > single active repo session > error if ambiguous.
153
146
  */
154
147
  async function resolveSessionDir(explicitSession?: string): Promise<string> {
155
148
  if (explicitSession) {
@@ -161,12 +154,13 @@ async function resolveSessionDir(explicitSession?: string): Promise<string> {
161
154
  }
162
155
 
163
156
  const sessions = await readActiveSessions();
164
- const ids = Object.keys(sessions);
157
+ // Filter to sessions for THIS repo
158
+ const ids = Object.keys(sessions).filter(isRepoSession);
165
159
 
166
160
  if (ids.length === 0) {
167
- // Fallback: check if any session dirs exist (server may have exited without cleanup)
161
+ // Fallback: check session dirs on disk for this repo
168
162
  try {
169
- const dirs = await fs.readdir(SESSIONS_DIR);
163
+ const dirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession);
170
164
  if (dirs.length === 1) return path.join(SESSIONS_DIR, dirs[0]);
171
165
  if (dirs.length > 1) {
172
166
  // Pick most recently modified
@@ -182,7 +176,7 @@ async function resolveSessionDir(explicitSession?: string): Promise<string> {
182
176
  return path.join(SESSIONS_DIR, latest.dir);
183
177
  }
184
178
  } catch { /* no sessions dir */ }
185
- throw new Error("No active sessions. Start Copilot with groundcrew first.");
179
+ throw new Error(`No active sessions for repo "${REPO_NAME}". Start Copilot with groundcrew first.`);
186
180
  }
187
181
 
188
182
  if (ids.length === 1) {
@@ -244,11 +238,6 @@ const red = (t: string) => color(31, t);
244
238
 
245
239
  // ── Commands ──────────────────────────────────────────────────────────────────
246
240
 
247
- async function init(): Promise<void> {
248
- await fs.mkdir(SESSIONS_DIR, { recursive: true });
249
- console.log(green("Groundcrew initialized.") + ` ${dim(GROUNDCREW_DIR + "/ created")}`);
250
- }
251
-
252
241
  async function add(taskText: string, priority: number, sessionDir: string): Promise<void> {
253
242
  const queue = await readQueue(sessionDir);
254
243
  const task: Task = {
@@ -397,27 +386,43 @@ async function sessions(): Promise<void> {
397
386
  return;
398
387
  }
399
388
 
389
+ // Group by repo prefix
390
+ const byRepo = new Map<string, string[]>();
391
+ for (const dir of allDirs) {
392
+ const dashIdx = dir.lastIndexOf("-");
393
+ const repo = dashIdx > 0 ? dir.substring(0, dashIdx) : "unknown";
394
+ if (!byRepo.has(repo)) byRepo.set(repo, []);
395
+ byRepo.get(repo)!.push(dir);
396
+ }
397
+
400
398
  console.log(bold("Sessions:\n"));
401
399
 
402
- for (const dir of allDirs) {
403
- const isActive = ids.includes(dir);
404
- const sessionDir = path.join(SESSIONS_DIR, dir);
400
+ for (const [repo, dirs] of byRepo) {
401
+ const isCurrent = repo === REPO_NAME;
402
+ console.log(` ${isCurrent ? green(repo) : dim(repo)}`);
405
403
 
406
- let info = "";
407
- try {
408
- const session = JSON.parse(await fs.readFile(path.join(sessionDir, "session.json"), "utf-8"));
409
- const startTime = new Date(session.started).getTime();
410
- const minutes = Math.round((Date.now() - startTime) / 60000);
411
- const statusColor = session.status === "active" ? green : session.status === "parked" ? yellow : dim;
412
- info = `${statusColor(session.status)} | ${minutes}min | ${session.tasksCompleted || 0} tasks done`;
413
- } catch {
414
- info = dim("no session data");
415
- }
404
+ for (const dir of dirs) {
405
+ const isActive = ids.includes(dir);
406
+ const sessionDir = path.join(SESSIONS_DIR, dir);
416
407
 
417
- const queue = await readQueue(sessionDir);
418
- const marker = isActive ? green("*") : " ";
408
+ let info = "";
409
+ try {
410
+ const session = JSON.parse(await fs.readFile(path.join(sessionDir, "session.json"), "utf-8"));
411
+ const startTime = new Date(session.started).getTime();
412
+ const minutes = Math.round((Date.now() - startTime) / 60000);
413
+ const statusColor = session.status === "active" ? green : session.status === "parked" ? yellow : dim;
414
+ info = `${statusColor(session.status)} | ${minutes}min | ${session.tasksCompleted || 0} tasks done`;
415
+ } catch {
416
+ info = dim("no session data");
417
+ }
418
+
419
+ const queue = await readQueue(sessionDir);
420
+ const marker = isActive ? green("*") : " ";
421
+ // Show only the hex suffix for cleaner display
422
+ const shortId = dir.substring(repo.length + 1);
419
423
 
420
- console.log(` ${marker} ${cyan(dir)} ${info} | ${queue.tasks.length} queued`);
424
+ console.log(` ${marker} ${cyan(shortId)} ${info} | ${queue.tasks.length} queued`);
425
+ }
421
426
  }
422
427
 
423
428
  if (ids.length > 0) {
@@ -484,13 +489,14 @@ async function destroyOne(sessionId: string): Promise<void> {
484
489
 
485
490
  async function stopAll(): Promise<void> {
486
491
  const active = await readActiveSessions();
487
- const ids = Object.keys(active);
492
+ // Scope to current repo
493
+ const ids = Object.keys(active).filter(isRepoSession);
488
494
 
489
495
  let allDirs: string[] = [];
490
- try { allDirs = await fs.readdir(SESSIONS_DIR); } catch {}
496
+ try { allDirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession); } catch {}
491
497
 
492
498
  if (ids.length === 0 && allDirs.length === 0) {
493
- console.log(dim("No sessions to stop."));
499
+ console.log(dim(`No sessions to stop for repo "${REPO_NAME}".`));
494
500
  return;
495
501
  }
496
502
 
@@ -511,33 +517,29 @@ async function stopAll(): Promise<void> {
511
517
  }
512
518
  }
513
519
 
514
- // Clear active sessions file
515
- await fs.writeFile(ACTIVE_SESSIONS_FILE, "{}");
516
- console.log(green("\nAll sessions stopped."));
520
+ // Remove stopped sessions from active-sessions.json (keep other repos)
521
+ const remaining = await readActiveSessions();
522
+ for (const id of ids) delete remaining[id];
523
+ await fs.writeFile(ACTIVE_SESSIONS_FILE, JSON.stringify(remaining, null, 2));
524
+ console.log(green(`\nAll ${REPO_NAME} sessions stopped.`));
517
525
  }
518
526
 
519
527
  async function destroyAll(): Promise<void> {
520
- // Stop everything first
528
+ // Stop this repo's sessions first
521
529
  await stopAll();
522
530
 
523
- // Delete all session directories
531
+ // Delete this repo's session directories only
524
532
  try {
525
- const dirs = await fs.readdir(SESSIONS_DIR);
533
+ const dirs = (await fs.readdir(SESSIONS_DIR)).filter(isRepoSession);
526
534
  for (const dir of dirs) {
527
535
  await fs.rm(path.join(SESSIONS_DIR, dir), { recursive: true, force: true });
528
536
  }
529
537
  } catch { /* no sessions dir */ }
530
538
 
531
- // Delete history
532
- try { await fs.unlink(HISTORY_FILE); } catch {}
533
-
534
- // Delete active sessions file
535
- try { await fs.unlink(ACTIVE_SESSIONS_FILE); } catch {}
536
-
537
539
  // Delete tool history
538
- try { await fs.unlink(path.join(GROUNDCREW_DIR, "tool-history.csv")); } catch {}
540
+ try { await fs.unlink(path.join(GROUNDCREW_HOME, "tool-history.csv")); } catch {}
539
541
 
540
- console.log(green("All session data and history deleted."));
542
+ console.log(green(`All ${REPO_NAME} session data deleted.`));
541
543
  }
542
544
 
543
545
  // ── Chat Mode ────────────────────────────────────────────────────────────────
@@ -557,6 +559,9 @@ async function listSessionChoices(): Promise<SessionChoice[]> {
557
559
  const choices: SessionChoice[] = [];
558
560
 
559
561
  for (const [id, entry] of Object.entries(active)) {
562
+ // Only show sessions for the current repo
563
+ if (!isRepoSession(id)) continue;
564
+
560
565
  const dir = path.join(SESSIONS_DIR, id);
561
566
  let status = "active";
562
567
  let minutes = 0;
@@ -585,7 +590,7 @@ async function pickSession(rl: readline.Interface): Promise<SessionChoice | null
585
590
  const choices = await listSessionChoices();
586
591
 
587
592
  if (choices.length === 0) {
588
- console.log(red("No active sessions. Start Copilot with groundcrew first."));
593
+ console.log(red(`No active sessions for repo "${REPO_NAME}". Start Copilot with groundcrew first.`));
589
594
  return null;
590
595
  }
591
596
 
@@ -1241,7 +1246,6 @@ ${bold("groundcrew")} — CLI companion for the Groundcrew Copilot plugin
1241
1246
  ${bold("Usage:")}
1242
1247
  groundcrew chat Interactive chat mode (recommended)
1243
1248
  groundcrew chat --session <id> Chat with a specific session
1244
- groundcrew init Initialize .groundcrew/ in current dir
1245
1249
  groundcrew add <task> Add a task to the queue
1246
1250
  groundcrew add --priority <task> Add an urgent task (processed first)
1247
1251
  groundcrew add --session <id> <task> Add to a specific session
@@ -1252,9 +1256,9 @@ ${bold("Usage:")}
1252
1256
  groundcrew sessions List all sessions
1253
1257
  groundcrew history Show completed tasks
1254
1258
  groundcrew clear Clear all pending tasks
1255
- groundcrew stop Stop all active sessions
1259
+ groundcrew stop Stop all sessions for current repo
1256
1260
  groundcrew stop --session <id> Stop a specific session
1257
- groundcrew destroy Delete all sessions, history, and data
1261
+ groundcrew destroy Delete all sessions for current repo
1258
1262
  groundcrew destroy --session <id> Delete a specific session
1259
1263
 
1260
1264
  ${bold("Session targeting:")}
@@ -1295,9 +1299,6 @@ async function main(): Promise<void> {
1295
1299
 
1296
1300
  // Commands that don't need a session (handle their own resolution)
1297
1301
  switch (command) {
1298
- case "init":
1299
- await init();
1300
- return;
1301
1302
  case "chat":
1302
1303
  await chat(explicitSession);
1303
1304
  return;