@teammates/cli 0.2.4 → 0.2.6

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.
@@ -111,11 +111,12 @@ export class CliProxyAdapter {
111
111
  }
112
112
  async startSession(teammate) {
113
113
  const id = `${this.name}-${teammate.name}-${nextId++}`;
114
- // Create session file inside .teammates/.tmp so sandboxed agents can access it
114
+ // Always ensure sessions directory exists before writing startupMaintenance
115
+ // runs concurrently and may delete empty dirs between calls.
116
+ const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
117
+ const dir = join(tmpBase, "sessions");
118
+ await mkdir(dir, { recursive: true });
115
119
  if (!this.sessionsDir) {
116
- const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
117
- const dir = join(tmpBase, "sessions");
118
- await mkdir(dir, { recursive: true });
119
120
  this.sessionsDir = dir;
120
121
  // Ensure .tmp is gitignored
121
122
  const gitignorePath = join(tmpBase, "..", ".gitignore");
@@ -36,11 +36,12 @@ export class CopilotAdapter {
36
36
  const id = `copilot-${teammate.name}-${nextId++}`;
37
37
  // Ensure the client is running
38
38
  await this.ensureClient(teammate.cwd);
39
- // Create session file inside .teammates/.tmp so the agent can access it
39
+ // Always ensure sessions directory exists before writing startupMaintenance
40
+ // runs concurrently and may delete empty dirs between calls.
41
+ const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
42
+ const dir = join(tmpBase, "sessions");
43
+ await mkdir(dir, { recursive: true });
40
44
  if (!this.sessionsDir) {
41
- const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
42
- const dir = join(tmpBase, "sessions");
43
- await mkdir(dir, { recursive: true });
44
45
  this.sessionsDir = dir;
45
46
  const gitignorePath = join(tmpBase, "..", ".gitignore");
46
47
  const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
package/dist/cli.js CHANGED
@@ -1361,6 +1361,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1361
1361
  weeklyLogs: [],
1362
1362
  ownership: { primary: [], secondary: [] },
1363
1363
  routingKeywords: [],
1364
+ cwd: projectDir,
1364
1365
  };
1365
1366
  const sessionId = await adapter.startSession(tempConfig);
1366
1367
  const spinner = ora({
@@ -1431,16 +1432,22 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1431
1432
  const teammatesDir = join(projectDir, ".teammates");
1432
1433
  console.log();
1433
1434
  try {
1434
- const { teammates, files } = await importTeammates(sourceDir, teammatesDir);
1435
- if (teammates.length === 0) {
1435
+ const { teammates, skipped, files } = await importTeammates(sourceDir, teammatesDir);
1436
+ const allTeammates = [...teammates, ...skipped];
1437
+ if (allTeammates.length === 0) {
1436
1438
  console.log(chalk.yellow(" No teammates found at ") + chalk.white(sourceDir));
1437
1439
  console.log(chalk.gray(" The directory should contain teammate folders (each with a SOUL.md)."));
1438
1440
  return;
1439
1441
  }
1440
- console.log(chalk.green(" ✔") +
1441
- chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
1442
- chalk.cyan(teammates.join(", ")));
1443
- console.log(chalk.gray(` (${files.length} files copied)`));
1442
+ if (teammates.length > 0) {
1443
+ console.log(chalk.green("") +
1444
+ chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
1445
+ chalk.cyan(teammates.join(", ")));
1446
+ console.log(chalk.gray(` (${files.length} files copied)`));
1447
+ }
1448
+ if (skipped.length > 0) {
1449
+ console.log(chalk.gray(` ${skipped.length} already present: ${skipped.join(", ")} (will re-adapt)`));
1450
+ }
1444
1451
  console.log();
1445
1452
  // Copy framework files so the agent has TEMPLATE.md etc. available
1446
1453
  await copyTemplateFiles(teammatesDir);
@@ -1452,7 +1459,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1452
1459
  console.log();
1453
1460
  const adapt = await this.askChoice("Adapt now? (y/n): ", ["y", "n"]);
1454
1461
  if (adapt === "y") {
1455
- await this.runAdaptationAgent(this.adapter, projectDir, teammates, sourceDir);
1462
+ await this.runAdaptationAgent(this.adapter, projectDir, allTeammates, sourceDir);
1456
1463
  }
1457
1464
  else {
1458
1465
  console.log(chalk.gray(" Skipped adaptation. Run /init to adapt later."));
@@ -1485,6 +1492,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1485
1492
  weeklyLogs: [],
1486
1493
  ownership: { primary: [], secondary: [] },
1487
1494
  routingKeywords: [],
1495
+ cwd: projectDir,
1488
1496
  };
1489
1497
  const sessionId = await adapter.startSession(tempConfig);
1490
1498
  const spinner = ora({
@@ -2268,12 +2276,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2268
2276
  }
2269
2277
  });
2270
2278
  this.chatView.on("link", (url) => {
2279
+ const quoted = JSON.stringify(url);
2271
2280
  const cmd = process.platform === "darwin"
2272
- ? "open"
2281
+ ? `open ${quoted}`
2273
2282
  : process.platform === "win32"
2274
- ? "start"
2275
- : "xdg-open";
2276
- execCb(`${cmd} ${JSON.stringify(url)}`);
2283
+ ? `start "" ${quoted}`
2284
+ : `xdg-open ${quoted}`;
2285
+ execCb(cmd, () => { });
2277
2286
  });
2278
2287
  this.app = new App({
2279
2288
  root: this.chatView,
@@ -3011,18 +3020,25 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3011
3020
  sourceDir = resolved;
3012
3021
  }
3013
3022
  try {
3014
- const { teammates, files } = await importTeammates(sourceDir, teammatesDir);
3015
- if (teammates.length === 0) {
3023
+ const { teammates, skipped, files } = await importTeammates(sourceDir, teammatesDir);
3024
+ // Combine newly imported + already existing for adaptation
3025
+ const allTeammates = [...teammates, ...skipped];
3026
+ if (allTeammates.length === 0) {
3016
3027
  this.feedLine(tp.warning(` No teammates found at ${sourceDir}`));
3017
3028
  this.refreshView();
3018
3029
  return;
3019
3030
  }
3020
- this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
3031
+ if (teammates.length > 0) {
3032
+ this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
3033
+ }
3034
+ if (skipped.length > 0) {
3035
+ this.feedLine(tp.muted(` ${skipped.length} already present: ${skipped.join(", ")} (will re-adapt)`));
3036
+ }
3021
3037
  // Copy framework files so the agent has TEMPLATE.md etc. available
3022
3038
  await copyTemplateFiles(teammatesDir);
3023
3039
  // Queue a single adaptation task that handles all teammates
3024
3040
  this.feedLine(tp.muted(` Queuing ${this.adapterName} to scan this project and adapt the team...`));
3025
- const prompt = await buildImportAdaptationPrompt(teammatesDir, teammates, sourceDir);
3041
+ const prompt = await buildImportAdaptationPrompt(teammatesDir, allTeammates, sourceDir);
3026
3042
  this.taskQueue.push({
3027
3043
  type: "agent",
3028
3044
  teammate: this.adapterName,
@@ -3446,10 +3462,13 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3446
3462
  const fullPath = join(dir, entry.name);
3447
3463
  if (entry.isDirectory()) {
3448
3464
  await this.cleanOldTempFiles(fullPath, maxAgeMs);
3449
- // Remove dir if now empty
3450
- const remaining = await readdir(fullPath).catch(() => [""]);
3451
- if (remaining.length === 0)
3452
- await rm(fullPath, { recursive: true }).catch(() => { });
3465
+ // Remove dir if now empty — but skip "sessions" which is structural
3466
+ // and may be recreated concurrently by startSession().
3467
+ if (entry.name !== "sessions") {
3468
+ const remaining = await readdir(fullPath).catch(() => [""]);
3469
+ if (remaining.length === 0)
3470
+ await rm(fullPath, { recursive: true }).catch(() => { });
3471
+ }
3453
3472
  }
3454
3473
  else {
3455
3474
  const info = await stat(fullPath).catch(() => null);
package/dist/onboard.d.ts CHANGED
@@ -25,6 +25,7 @@ export declare function copyTemplateFiles(teammatesDir: string): Promise<string[
25
25
  */
26
26
  export declare function importTeammates(sourceDir: string, targetDir: string): Promise<{
27
27
  teammates: string[];
28
+ skipped: string[];
28
29
  files: string[];
29
30
  }>;
30
31
  /**
package/dist/onboard.js CHANGED
@@ -129,6 +129,7 @@ export async function importTeammates(sourceDir, targetDir) {
129
129
  }
130
130
  await mkdir(targetDir, { recursive: true });
131
131
  const teammates = [];
132
+ const skipped = [];
132
133
  const files = [];
133
134
  const entries = await readdir(sourceDir, { withFileTypes: true });
134
135
  for (const entry of entries) {
@@ -140,6 +141,7 @@ export async function importTeammates(sourceDir, targetDir) {
140
141
  // Skip if teammate already exists in target
141
142
  try {
142
143
  await stat(destPath);
144
+ skipped.push(entry.name);
143
145
  continue;
144
146
  }
145
147
  catch {
@@ -185,7 +187,7 @@ export async function importTeammates(sourceDir, targetDir) {
185
187
  await writeFile(gitignoreDest, "USER.md\n.*/\n", "utf-8");
186
188
  files.push(".gitignore");
187
189
  }
188
- return { teammates, files };
190
+ return { teammates, skipped, files };
189
191
  }
190
192
  /**
191
193
  * Build an import-adaptation prompt that runs as a single non-interactive agent session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Agent-agnostic CLI for teammates. Routes tasks, manages handoffs, and plugs into any coding agent backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",