@teammates/cli 0.4.0 → 0.4.1

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
@@ -7,12 +7,12 @@
7
7
  * teammates --adapter codex Use a specific agent adapter
8
8
  * teammates --dir <path> Override .teammates/ location
9
9
  */
10
- import { exec as execCb, execSync, spawn } from "node:child_process";
10
+ import { exec as execCb, execSync, spawnSync } from "node:child_process";
11
11
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
13
13
  import { dirname, join, resolve } from "node:path";
14
14
  import { createInterface } from "node:readline";
15
- import { App, ChatView, concat, esc, Interview, pen, renderMarkdown, stripAnsi, } from "@teammates/consolonia";
15
+ import { App, ChatView, concat, esc, pen, renderMarkdown, stripAnsi, } from "@teammates/consolonia";
16
16
  import chalk from "chalk";
17
17
  import ora from "ora";
18
18
  import { syncRecallIndex } from "./adapter.js";
@@ -103,10 +103,10 @@ class TeammatesREPL {
103
103
  : `You are maintaining a running summary of an ongoing conversation between a user and their AI teammates. Summarize the conversation entries below.\n\n## Entries to Summarize\n\n${entriesText}\n\n## Instructions\n\nReturn ONLY the summary — no preamble, no explanation. The summary should:\n- Be a concise bulleted list of key topics discussed, decisions made, and work completed\n- Preserve important context that future messages might reference\n- Drop trivial or redundant details\n- Stay under 2000 characters\n- Do NOT include any output protocol (no TO:, no # Subject, no handoff blocks)`;
104
104
  // Remove the summarized entries — they'll be captured in the summary
105
105
  this.conversationHistory.splice(0, splitIdx);
106
- // Queue the summarization task to the base coding agent
106
+ // Queue the summarization task through the user's agent
107
107
  this.taskQueue.push({
108
108
  type: "summarize",
109
- teammate: this.adapterName,
109
+ teammate: this.selfName,
110
110
  task: prompt,
111
111
  });
112
112
  this.kickDrain();
@@ -142,9 +142,16 @@ class TeammatesREPL {
142
142
  _replyContexts = new Map();
143
143
  /** Quoted reply text to expand on next submit. */
144
144
  _pendingQuotedReply = null;
145
- defaultFooter = null; // cached default footer content
145
+ /** Resolver for inline ask when set, next submit resolves this instead of normal handling. */
146
+ _pendingAsk = null;
147
+ defaultFooter = null; // cached left footer content
148
+ defaultFooterRight = null; // cached right footer content
146
149
  /** Cached service statuses for banner + /configure. */
147
150
  serviceStatuses = [];
151
+ /** Reference to the animated banner widget for live updates. */
152
+ banner = null;
153
+ /** The local user's alias (avatar name). Set after USER.md is read or interview completes. */
154
+ userAlias = null;
148
155
  // ── Animated status tracker ─────────────────────────────────────
149
156
  activeTasks = new Map();
150
157
  statusTimer = null;
@@ -166,6 +173,13 @@ class TeammatesREPL {
166
173
  constructor(adapterName) {
167
174
  this.adapterName = adapterName;
168
175
  }
176
+ /**
177
+ * The name used for the local user in the roster.
178
+ * Returns the user's alias if set, otherwise the adapter name.
179
+ */
180
+ get selfName() {
181
+ return this.userAlias ?? this.adapterName;
182
+ }
169
183
  /** Show the prompt with the fenced border. */
170
184
  showPrompt() {
171
185
  if (this.chatView) {
@@ -221,27 +235,28 @@ class TeammatesREPL {
221
235
  const entries = Array.from(this.activeTasks.values());
222
236
  const idx = this.statusRotateIndex % entries.length;
223
237
  const { teammate, task } = entries[idx];
238
+ const displayName = teammate === this.adapterName ? this.selfName : teammate;
224
239
  const spinChar = TeammatesREPL.SPINNER[this.statusFrame % TeammatesREPL.SPINNER.length];
225
240
  const taskPreview = task.length > 50 ? `${task.slice(0, 47)}...` : task;
226
241
  const queueInfo = this.activeTasks.size > 1 ? ` (${idx + 1}/${this.activeTasks.size})` : "";
227
242
  if (this.chatView) {
228
243
  // Strip newlines and truncate task text for single-line display
229
244
  const cleanTask = task.replace(/[\r\n]+/g, " ").trim();
230
- const maxLen = Math.max(20, (process.stdout.columns || 80) - teammate.length - 10);
245
+ const maxLen = Math.max(20, (process.stdout.columns || 80) - displayName.length - 10);
231
246
  const taskText = cleanTask.length > maxLen
232
247
  ? `${cleanTask.slice(0, maxLen - 1)}…`
233
248
  : cleanTask;
234
249
  const queueTag = this.activeTasks.size > 1
235
250
  ? ` (${idx + 1}/${this.activeTasks.size})`
236
251
  : "";
237
- this.chatView.setProgress(concat(tp.accent(`${spinChar} ${teammate}… `), tp.muted(taskText + queueTag)));
252
+ this.chatView.setProgress(concat(tp.accent(`${spinChar} ${displayName}… `), tp.muted(taskText + queueTag)));
238
253
  this.app.refresh();
239
254
  }
240
255
  else {
241
256
  // Mostly bright blue, periodically flicker to dark blue
242
257
  const spinColor = this.statusFrame % 8 === 0 ? chalk.blue : chalk.blueBright;
243
258
  const line = ` ${spinColor(spinChar)} ` +
244
- chalk.bold(teammate) +
259
+ chalk.bold(displayName) +
245
260
  chalk.gray(`… ${taskPreview}`) +
246
261
  (queueInfo ? chalk.gray(queueInfo) : "");
247
262
  this.input.setStatus(line);
@@ -299,8 +314,8 @@ class TeammatesREPL {
299
314
  rendered.push({ type: "text", content: line });
300
315
  }
301
316
  }
302
- // Render first line with "User: " label
303
- const label = "user: ";
317
+ // Render first line with alias label
318
+ const label = `${this.selfName}: `;
304
319
  const first = rendered.shift();
305
320
  if (first) {
306
321
  if (first.type === "text") {
@@ -483,7 +498,7 @@ class TeammatesREPL {
483
498
  style: { fg: chrome },
484
499
  }));
485
500
  if (!isValid) {
486
- this.feedLine(tp.error(` ✖ Unknown teammate: @${h.to}`));
501
+ this.feedLine(tp.error(` ✖ Unknown teammate: @${h.to}`));
487
502
  }
488
503
  else if (this.autoApproveHandoffs) {
489
504
  this.taskQueue.push({ type: "agent", teammate: h.to, task: h.task });
@@ -847,13 +862,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
847
862
  if (this.app)
848
863
  this.app.refresh();
849
864
  }
850
- queueTask(input) {
865
+ queueTask(input, preMentions) {
851
866
  const allNames = this.orchestrator.listTeammates();
852
867
  // Check for @everyone — queue to all teammates except the coding agent
853
868
  const everyoneMatch = input.match(/^@everyone\s+([\s\S]+)$/i);
854
869
  if (everyoneMatch) {
855
870
  const task = everyoneMatch[1];
856
- const names = allNames.filter((n) => n !== this.adapterName);
871
+ const names = allNames.filter((n) => n !== this.selfName && n !== this.adapterName);
857
872
  for (const teammate of names) {
858
873
  this.taskQueue.push({ type: "agent", teammate, task });
859
874
  }
@@ -865,14 +880,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
865
880
  this.kickDrain();
866
881
  return;
867
882
  }
868
- // Collect all @mentioned teammates anywhere in the input
869
- const mentionRegex = /@(\S+)/g;
870
- let m;
871
- const mentioned = [];
872
- while ((m = mentionRegex.exec(input)) !== null) {
873
- const name = m[1];
874
- if (allNames.includes(name) && !mentioned.includes(name)) {
875
- mentioned.push(name);
883
+ // Use pre-resolved mentions if provided (avoids picking up @mentions from expanded paste text),
884
+ // otherwise scan the input directly.
885
+ let mentioned;
886
+ if (preMentions) {
887
+ mentioned = preMentions;
888
+ }
889
+ else {
890
+ const mentionRegex = /@(\S+)/g;
891
+ let m;
892
+ mentioned = [];
893
+ while ((m = mentionRegex.exec(input)) !== null) {
894
+ const name = m[1];
895
+ if (allNames.includes(name) && !mentioned.includes(name)) {
896
+ mentioned.push(name);
897
+ }
876
898
  }
877
899
  }
878
900
  if (mentioned.length > 0) {
@@ -894,12 +916,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
894
916
  match = this.lastResult.teammate;
895
917
  }
896
918
  if (!match) {
897
- match = this.orchestrator.route(input) ?? this.adapterName;
919
+ match = this.orchestrator.route(input) ?? this.selfName;
898
920
  }
899
921
  {
900
922
  const bg = this._userBg;
901
923
  const t = theme();
902
- this.feedUserLine(concat(pen.fg(t.textMuted).bg(bg)(" → "), pen.fg(t.accent).bg(bg)(`@${match}`)));
924
+ const displayName = match === this.adapterName ? this.selfName : match;
925
+ this.feedUserLine(concat(pen.fg(t.textMuted).bg(bg)(" → "), pen.fg(t.accent).bg(bg)(`@${displayName}`)));
903
926
  }
904
927
  this.feedLine();
905
928
  this.refreshView();
@@ -924,20 +947,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
924
947
  }
925
948
  // ─── Onboarding ───────────────────────────────────────────────────
926
949
  /**
927
- * Interactive prompt when no .teammates/ directory is found.
928
- * Returns the new .teammates/ path, or null if user chose to exit.
950
+ * Interactive prompt for team onboarding after user profile is set up.
951
+ * .teammates/ already exists at this point. Returns false if user chose to exit.
929
952
  */
930
- async promptOnboarding(adapter) {
953
+ async promptTeamOnboarding(adapter, teammatesDir) {
931
954
  const cwd = process.cwd();
932
- const teammatesDir = join(cwd, ".teammates");
933
955
  const termWidth = process.stdout.columns || 100;
934
956
  console.log();
935
- this.printLogo([
936
- chalk.bold("Teammates") + chalk.gray(` v${PKG_VERSION}`),
937
- chalk.yellow("No .teammates/ directory found"),
938
- chalk.gray(cwd),
939
- ]);
940
- console.log();
941
957
  console.log(chalk.gray("─".repeat(termWidth)));
942
958
  console.log();
943
959
  console.log(chalk.white(" Set up teammates for this project?\n"));
@@ -952,7 +968,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
952
968
  console.log(chalk.cyan(" 3") +
953
969
  chalk.gray(") ") +
954
970
  chalk.white("Solo mode") +
955
- chalk.gray(` — use ${this.adapterName} without teammates`));
971
+ chalk.gray(" — use your agent without teammates"));
956
972
  console.log(chalk.cyan(" 4") + chalk.gray(") ") + chalk.white("Exit"));
957
973
  console.log();
958
974
  const choice = await this.askChoice("Pick an option (1/2/3/4): ", [
@@ -963,27 +979,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
963
979
  ]);
964
980
  if (choice === "4") {
965
981
  console.log(chalk.gray(" Goodbye."));
966
- return null;
982
+ return false;
967
983
  }
968
984
  if (choice === "3") {
969
- await mkdir(teammatesDir, { recursive: true });
970
- console.log();
971
- console.log(chalk.green(" ✔") + chalk.gray(` Created ${teammatesDir}`));
972
- console.log(chalk.gray(` Running in solo mode — all tasks go to ${this.adapterName}.`));
985
+ console.log(chalk.gray(" Running in solo mode — all tasks go to your agent."));
973
986
  console.log(chalk.gray(" Run /init later to set up teammates."));
974
987
  console.log();
975
- return teammatesDir;
988
+ return true;
976
989
  }
977
990
  if (choice === "2") {
978
- // Import from another project
979
- await mkdir(teammatesDir, { recursive: true });
980
991
  await this.runImport(cwd);
981
- return teammatesDir;
992
+ return true;
982
993
  }
983
994
  // choice === "1": Run onboarding via the agent
984
- await mkdir(teammatesDir, { recursive: true });
985
995
  await this.runOnboardingAgent(adapter, cwd);
986
- return teammatesDir;
996
+ return true;
987
997
  }
988
998
  /**
989
999
  * Run the onboarding agent to analyze the codebase and create teammates.
@@ -992,19 +1002,20 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
992
1002
  async runOnboardingAgent(adapter, projectDir) {
993
1003
  console.log();
994
1004
  console.log(chalk.blue(" Starting onboarding...") +
995
- chalk.gray(` ${this.adapterName} will analyze your codebase and create .teammates/`));
1005
+ chalk.gray(" Your agent will analyze your codebase and create .teammates/"));
996
1006
  console.log();
997
1007
  // Copy framework files from bundled template
998
1008
  const teammatesDir = join(projectDir, ".teammates");
999
1009
  const copied = await copyTemplateFiles(teammatesDir);
1000
1010
  if (copied.length > 0) {
1001
- console.log(chalk.green(" ✔") +
1011
+ console.log(chalk.green(" ✔ ") +
1002
1012
  chalk.gray(` Copied template files: ${copied.join(", ")}`));
1003
1013
  console.log();
1004
1014
  }
1005
1015
  const onboardingPrompt = await getOnboardingPrompt(projectDir);
1006
1016
  const tempConfig = {
1007
1017
  name: this.adapterName,
1018
+ type: "ai",
1008
1019
  role: "Onboarding agent",
1009
1020
  soul: "",
1010
1021
  wisdom: "",
@@ -1016,8 +1027,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1016
1027
  };
1017
1028
  const sessionId = await adapter.startSession(tempConfig);
1018
1029
  const spinner = ora({
1019
- text: chalk.blue(this.adapterName) +
1020
- chalk.gray(" is analyzing your codebase..."),
1030
+ text: chalk.gray("Analyzing your codebase..."),
1021
1031
  spinner: "dots",
1022
1032
  }).start();
1023
1033
  try {
@@ -1025,7 +1035,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1025
1035
  spinner.stop();
1026
1036
  this.printAgentOutput(result.rawOutput);
1027
1037
  if (result.success) {
1028
- console.log(chalk.green(" ✔ Onboarding complete!"));
1038
+ console.log(chalk.green(" ✔ Onboarding complete!"));
1029
1039
  }
1030
1040
  else {
1031
1041
  console.log(chalk.yellow(` ⚠ Onboarding finished with issues: ${result.summary}`));
@@ -1091,7 +1101,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1091
1101
  return;
1092
1102
  }
1093
1103
  if (teammates.length > 0) {
1094
- console.log(chalk.green(" ✔") +
1104
+ console.log(chalk.green(" ✔ ") +
1095
1105
  chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
1096
1106
  chalk.cyan(teammates.join(", ")));
1097
1107
  console.log(chalk.gray(` (${files.length} files copied)`));
@@ -1131,11 +1141,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1131
1141
  const teammatesDir = join(projectDir, ".teammates");
1132
1142
  console.log();
1133
1143
  console.log(chalk.blue(" Starting adaptation...") +
1134
- chalk.gray(` ${this.adapterName} will scan this project and adapt the team`));
1144
+ chalk.gray(" Your agent will scan this project and adapt the team"));
1135
1145
  console.log();
1136
1146
  const prompt = await buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath);
1137
1147
  const tempConfig = {
1138
1148
  name: this.adapterName,
1149
+ type: "ai",
1139
1150
  role: "Adaptation agent",
1140
1151
  soul: "",
1141
1152
  wisdom: "",
@@ -1147,8 +1158,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1147
1158
  };
1148
1159
  const sessionId = await adapter.startSession(tempConfig);
1149
1160
  const spinner = ora({
1150
- text: chalk.blue(this.adapterName) +
1151
- chalk.gray(" is scanning the project and adapting teammates..."),
1161
+ text: chalk.gray("Scanning the project and adapting teammates..."),
1152
1162
  spinner: "dots",
1153
1163
  }).start();
1154
1164
  try {
@@ -1156,7 +1166,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1156
1166
  spinner.stop();
1157
1167
  this.printAgentOutput(result.rawOutput);
1158
1168
  if (result.success) {
1159
- console.log(chalk.green(" ✔ Team adaptation complete!"));
1169
+ console.log(chalk.green(" ✔ Team adaptation complete!"));
1160
1170
  }
1161
1171
  else {
1162
1172
  console.log(chalk.yellow(` ⚠ Adaptation finished with issues: ${result.summary}`));
@@ -1206,6 +1216,30 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1206
1216
  });
1207
1217
  });
1208
1218
  }
1219
+ /**
1220
+ * Ask for input using the ChatView's own prompt (no raw readline).
1221
+ * Temporarily replaces the footer with the prompt text and intercepts the next submit.
1222
+ */
1223
+ askInline(prompt) {
1224
+ return new Promise((resolve) => {
1225
+ if (!this.chatView) {
1226
+ // Fallback if no ChatView (shouldn't happen during /configure)
1227
+ return this.askInput(prompt).then(resolve);
1228
+ }
1229
+ // Show the prompt in the feed so it's visible
1230
+ this.feedLine(tp.accent(` ${prompt}`));
1231
+ this.chatView.setFooter(tp.accent(` ${prompt}`));
1232
+ this._pendingAsk = (answer) => {
1233
+ // Restore footer
1234
+ if (this.chatView && this.defaultFooter) {
1235
+ this.chatView.setFooter(this.defaultFooter);
1236
+ }
1237
+ this.refreshView();
1238
+ resolve(answer.trim());
1239
+ };
1240
+ this.refreshView();
1241
+ });
1242
+ }
1209
1243
  /**
1210
1244
  * Check whether USER.md needs to be created or is still template placeholders.
1211
1245
  */
@@ -1222,71 +1256,309 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1222
1256
  }
1223
1257
  }
1224
1258
  /**
1225
- * Run the user interview inside the ChatView using the Interview widget.
1226
- * Hides the normal input prompt until the interview completes.
1259
+ * Pre-TUI user profile setup. Runs in the console before the ChatView is created.
1260
+ * Offers GitHub-based or manual profile creation.
1227
1261
  */
1228
- startUserInterview(teammatesDir, bannerWidget) {
1229
- if (!this.chatView)
1262
+ async runUserSetup(teammatesDir) {
1263
+ const termWidth = process.stdout.columns || 100;
1264
+ console.log();
1265
+ console.log(chalk.gray("─".repeat(termWidth)));
1266
+ console.log();
1267
+ console.log(chalk.white(" Set up your profile\n"));
1268
+ console.log(chalk.cyan(" 1") +
1269
+ chalk.gray(") ") +
1270
+ chalk.white("Use GitHub account") +
1271
+ chalk.gray(" — import your name and username from GitHub"));
1272
+ console.log(chalk.cyan(" 2") +
1273
+ chalk.gray(") ") +
1274
+ chalk.white("Manual setup") +
1275
+ chalk.gray(" — enter your details manually"));
1276
+ console.log(chalk.cyan(" 3") +
1277
+ chalk.gray(") ") +
1278
+ chalk.white("Skip") +
1279
+ chalk.gray(" — set up later with /user"));
1280
+ console.log();
1281
+ const choice = await this.askChoice("Pick an option (1/2/3): ", [
1282
+ "1",
1283
+ "2",
1284
+ "3",
1285
+ ]);
1286
+ if (choice === "3") {
1287
+ console.log(chalk.gray(" Skipped — run /user to set up your profile later."));
1288
+ console.log();
1230
1289
  return;
1231
- const t = theme();
1232
- const interview = new Interview({
1233
- title: "Quick intro — helps teammates tailor their work to you.",
1234
- subtitle: "(press Enter to skip any question)",
1235
- questions: [
1236
- { key: "name", prompt: "Your name" },
1237
- {
1238
- key: "role",
1239
- prompt: "Your role",
1240
- placeholder: "e.g., senior backend engineer",
1241
- },
1242
- {
1243
- key: "experience",
1244
- prompt: "Relevant experience",
1245
- placeholder: "e.g., 10 years Go, new to React",
1246
- },
1247
- {
1248
- key: "preferences",
1249
- prompt: "How you like to work",
1250
- placeholder: "e.g., terse responses, explain reasoning",
1251
- },
1252
- {
1253
- key: "context",
1254
- prompt: "Anything else",
1255
- placeholder: "e.g., solo dev, working on a rewrite",
1256
- },
1257
- ],
1258
- titleStyle: { fg: t.text },
1259
- subtitleStyle: { fg: t.textDim, italic: true },
1260
- promptStyle: { fg: t.accent },
1261
- answeredStyle: { fg: t.textMuted, italic: true },
1262
- inputStyle: { fg: t.text },
1263
- cursorStyle: { fg: t.cursorFg, bg: t.cursorBg },
1264
- placeholderStyle: { fg: t.textDim, italic: true },
1265
- });
1266
- this.chatView.setInputOverride(interview);
1267
- if (this.app)
1268
- this.app.refresh();
1269
- interview.on("complete", (answers) => {
1270
- // Write USER.md
1271
- const userMdPath = join(teammatesDir, "USER.md");
1272
- const lines = ["# User\n"];
1273
- lines.push(`- **Name:** ${answers.name || "_not provided_"}`);
1274
- lines.push(`- **Role:** ${answers.role || "_not provided_"}`);
1275
- lines.push(`- **Experience:** ${answers.experience || "_not provided_"}`);
1276
- lines.push(`- **Preferences:** ${answers.preferences || "_not provided_"}`);
1277
- lines.push(`- **Context:** ${answers.context || "_not provided_"}`);
1278
- writeFileSync(userMdPath, `${lines.join("\n")}\n`, "utf-8");
1279
- // Remove override and restore normal input
1280
- if (this.chatView) {
1281
- this.chatView.setInputOverride(null);
1282
- this.chatView.appendStyledToFeed(concat(tp.success(" "), tp.dim("Saved USER.md — update anytime with /user")));
1283
- }
1284
- // Release the banner hold so commands animate in
1285
- if (bannerWidget)
1286
- bannerWidget.releaseHold();
1287
- if (this.app)
1288
- this.app.refresh();
1290
+ }
1291
+ if (choice === "1") {
1292
+ await this.setupGitHubProfile(teammatesDir);
1293
+ }
1294
+ else {
1295
+ await this.setupManualProfile(teammatesDir);
1296
+ }
1297
+ }
1298
+ /**
1299
+ * GitHub-based profile setup. Ensures gh is installed and authenticated,
1300
+ * then fetches user info from the GitHub API to create the profile.
1301
+ */
1302
+ async setupGitHubProfile(teammatesDir) {
1303
+ console.log();
1304
+ // Step 1: Check if gh is installed
1305
+ let ghInstalled = false;
1306
+ try {
1307
+ execSync("gh --version", { stdio: "pipe" });
1308
+ ghInstalled = true;
1309
+ }
1310
+ catch {
1311
+ // not installed
1312
+ }
1313
+ if (!ghInstalled) {
1314
+ console.log(chalk.yellow(" GitHub CLI is not installed.\n"));
1315
+ const plat = process.platform;
1316
+ console.log(chalk.white(" Run this in another terminal:"));
1317
+ if (plat === "win32") {
1318
+ console.log(chalk.cyan(" winget install --id GitHub.cli"));
1319
+ }
1320
+ else if (plat === "darwin") {
1321
+ console.log(chalk.cyan(" brew install gh"));
1322
+ }
1323
+ else {
1324
+ console.log(chalk.cyan(" sudo apt install gh"));
1325
+ console.log(chalk.gray(" (or see https://cli.github.com)"));
1326
+ }
1327
+ console.log();
1328
+ const answer = await this.askChoice("Press Enter when done, or s to skip: ", ["", "s", "S"]);
1329
+ if (answer.toLowerCase() === "s") {
1330
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1331
+ return this.setupManualProfile(teammatesDir);
1332
+ }
1333
+ // Re-check
1334
+ try {
1335
+ execSync("gh --version", { stdio: "pipe" });
1336
+ ghInstalled = true;
1337
+ console.log(chalk.green(" ✔ GitHub CLI installed"));
1338
+ }
1339
+ catch {
1340
+ console.log(chalk.yellow(" GitHub CLI still not found. You may need to restart your terminal."));
1341
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1342
+ return this.setupManualProfile(teammatesDir);
1343
+ }
1344
+ }
1345
+ else {
1346
+ console.log(chalk.green(" ✔ GitHub CLI installed"));
1347
+ }
1348
+ // Step 2: Check auth
1349
+ let authed = false;
1350
+ try {
1351
+ execSync("gh auth status", { stdio: "pipe" });
1352
+ authed = true;
1353
+ }
1354
+ catch {
1355
+ // not authenticated
1356
+ }
1357
+ if (!authed) {
1358
+ console.log();
1359
+ console.log(chalk.gray(" Authenticating with GitHub...\n"));
1360
+ const result = spawnSync("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
1361
+ stdio: "inherit",
1362
+ shell: true,
1363
+ });
1364
+ if (result.status !== 0) {
1365
+ console.log(chalk.yellow(" Authentication failed or was cancelled."));
1366
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1367
+ return this.setupManualProfile(teammatesDir);
1368
+ }
1369
+ // Verify
1370
+ try {
1371
+ execSync("gh auth status", { stdio: "pipe" });
1372
+ authed = true;
1373
+ }
1374
+ catch {
1375
+ console.log(chalk.yellow(" Authentication could not be verified."));
1376
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1377
+ return this.setupManualProfile(teammatesDir);
1378
+ }
1379
+ }
1380
+ console.log(chalk.green(" ✔ GitHub authenticated"));
1381
+ // Step 3: Fetch user info from GitHub API
1382
+ let login = "";
1383
+ let name = "";
1384
+ try {
1385
+ const json = execSync("gh api user", {
1386
+ stdio: "pipe",
1387
+ encoding: "utf-8",
1388
+ });
1389
+ const user = JSON.parse(json);
1390
+ login = (user.login || "").toLowerCase().replace(/[^a-z0-9_-]/g, "");
1391
+ name = user.name || user.login || "";
1392
+ }
1393
+ catch {
1394
+ console.log(chalk.yellow(" Could not fetch GitHub user info."));
1395
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1396
+ return this.setupManualProfile(teammatesDir);
1397
+ }
1398
+ if (!login) {
1399
+ console.log(chalk.yellow(" No GitHub username found."));
1400
+ console.log(chalk.gray(" Falling back to manual setup.\n"));
1401
+ return this.setupManualProfile(teammatesDir);
1402
+ }
1403
+ console.log(chalk.green(` ✔ Authenticated as `) +
1404
+ chalk.cyan(`@${login}`) +
1405
+ (name && name !== login ? chalk.gray(` (${name})`) : ""));
1406
+ console.log();
1407
+ // Ask for role (optional) since GitHub doesn't provide this
1408
+ const role = await this.askInput("Your role (optional, press Enter to skip): ");
1409
+ const answers = {
1410
+ alias: login,
1411
+ name: name || login,
1412
+ role: role || "",
1413
+ experience: "",
1414
+ preferences: "",
1415
+ context: "",
1416
+ };
1417
+ this.writeUserProfile(teammatesDir, login, answers);
1418
+ this.createUserAvatar(teammatesDir, login, answers);
1419
+ console.log(chalk.green(" ✔ ") +
1420
+ chalk.gray(`Profile created — avatar @${login}`));
1421
+ console.log();
1422
+ }
1423
+ /**
1424
+ * Manual (console-based) profile setup. Collects fields via askInput().
1425
+ */
1426
+ async setupManualProfile(teammatesDir) {
1427
+ console.log();
1428
+ console.log(chalk.gray(" (alias is required, press Enter to skip others)\n"));
1429
+ const aliasRaw = await this.askInput("Your alias (e.g., alex): ");
1430
+ const alias = aliasRaw.toLowerCase().replace(/[^a-z0-9_-]/g, "").trim();
1431
+ if (!alias) {
1432
+ console.log(chalk.yellow(" Alias is required. Run /user to try again.\n"));
1433
+ return;
1434
+ }
1435
+ const name = await this.askInput("Your name: ");
1436
+ const role = await this.askInput("Your role (e.g., senior backend engineer): ");
1437
+ const experience = await this.askInput("Relevant experience (e.g., 10 years Go, new to React): ");
1438
+ const preferences = await this.askInput("How you like to work (e.g., terse responses): ");
1439
+ const context = await this.askInput("Anything else: ");
1440
+ const answers = {
1441
+ alias,
1442
+ name,
1443
+ role,
1444
+ experience,
1445
+ preferences,
1446
+ context,
1447
+ };
1448
+ this.writeUserProfile(teammatesDir, alias, answers);
1449
+ this.createUserAvatar(teammatesDir, alias, answers);
1450
+ console.log();
1451
+ console.log(chalk.green(" ✔ ") +
1452
+ chalk.gray(`Profile created — avatar @${alias}`));
1453
+ console.log(chalk.gray(" Update anytime with /user"));
1454
+ console.log();
1455
+ }
1456
+ /**
1457
+ * Write USER.md from collected answers.
1458
+ */
1459
+ writeUserProfile(teammatesDir, alias, answers) {
1460
+ const userMdPath = join(teammatesDir, "USER.md");
1461
+ const lines = ["# User\n"];
1462
+ lines.push(`- **Alias:** ${alias}`);
1463
+ lines.push(`- **Name:** ${answers.name || "_not provided_"}`);
1464
+ lines.push(`- **Role:** ${answers.role || "_not provided_"}`);
1465
+ lines.push(`- **Experience:** ${answers.experience || "_not provided_"}`);
1466
+ lines.push(`- **Preferences:** ${answers.preferences || "_not provided_"}`);
1467
+ lines.push(`- **Context:** ${answers.context || "_not provided_"}`);
1468
+ writeFileSync(userMdPath, `${lines.join("\n")}\n`, "utf-8");
1469
+ }
1470
+ /**
1471
+ * Create the user's avatar folder with SOUL.md and WISDOM.md.
1472
+ * The avatar is a teammate folder with type: human.
1473
+ */
1474
+ createUserAvatar(teammatesDir, alias, answers) {
1475
+ const avatarDir = join(teammatesDir, alias);
1476
+ const memoryDir = join(avatarDir, "memory");
1477
+ mkdirSync(avatarDir, { recursive: true });
1478
+ mkdirSync(memoryDir, { recursive: true });
1479
+ const name = answers.name || alias;
1480
+ const role = answers.role || "Team member";
1481
+ const experience = answers.experience || "";
1482
+ const preferences = answers.preferences || "";
1483
+ const context = answers.context || "";
1484
+ // Write SOUL.md
1485
+ const soulLines = [
1486
+ `# ${name}`,
1487
+ "",
1488
+ "## Identity",
1489
+ "",
1490
+ `**Type:** human`,
1491
+ `**Alias:** ${alias}`,
1492
+ `**Role:** ${role}`,
1493
+ ];
1494
+ if (experience)
1495
+ soulLines.push(`**Experience:** ${experience}`);
1496
+ if (preferences)
1497
+ soulLines.push(`**Preferences:** ${preferences}`);
1498
+ if (context) {
1499
+ soulLines.push("", "## Context", "", context);
1500
+ }
1501
+ soulLines.push("");
1502
+ const soulPath = join(avatarDir, "SOUL.md");
1503
+ writeFileSync(soulPath, soulLines.join("\n"), "utf-8");
1504
+ // Write empty WISDOM.md
1505
+ const wisdomPath = join(avatarDir, "WISDOM.md");
1506
+ writeFileSync(wisdomPath, `# ${name} — Wisdom\n\nDistilled from work history. Updated during compaction.\n`, "utf-8");
1507
+ // Avatar registration happens later in start() after the orchestrator is initialized.
1508
+ // During pre-TUI setup, the orchestrator doesn't exist yet.
1509
+ }
1510
+ /**
1511
+ * Read USER.md and extract the alias field.
1512
+ * Returns null if USER.md doesn't exist or has no alias.
1513
+ */
1514
+ readUserAlias(teammatesDir) {
1515
+ try {
1516
+ const content = readFileSync(join(teammatesDir, "USER.md"), "utf-8");
1517
+ const match = content.match(/\*\*Alias:\*\*\s*(\S+)/);
1518
+ return match ? match[1].toLowerCase().replace(/[^a-z0-9_-]/g, "") : null;
1519
+ }
1520
+ catch {
1521
+ return null;
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Register the user's avatar as a teammate in the orchestrator.
1526
+ * Sets presence to "online" since the local user is always online.
1527
+ * Replaces the old coding agent entry.
1528
+ */
1529
+ registerUserAvatar(teammatesDir, alias) {
1530
+ const registry = this.orchestrator.getRegistry();
1531
+ const avatarDir = join(teammatesDir, alias);
1532
+ // Read the avatar's SOUL.md if it exists
1533
+ let soul = "";
1534
+ let role = "Team member";
1535
+ try {
1536
+ soul = readFileSync(join(avatarDir, "SOUL.md"), "utf-8");
1537
+ const roleMatch = soul.match(/\*\*Role:\*\*\s*(.+)/);
1538
+ if (roleMatch)
1539
+ role = roleMatch[1].trim();
1540
+ }
1541
+ catch { /* avatar folder may not exist yet */ }
1542
+ let wisdom = "";
1543
+ try {
1544
+ wisdom = readFileSync(join(avatarDir, "WISDOM.md"), "utf-8");
1545
+ }
1546
+ catch { /* ok */ }
1547
+ registry.register({
1548
+ name: alias,
1549
+ type: "human",
1550
+ role,
1551
+ soul,
1552
+ wisdom,
1553
+ dailyLogs: [],
1554
+ weeklyLogs: [],
1555
+ ownership: { primary: [], secondary: [] },
1556
+ routingKeywords: [],
1289
1557
  });
1558
+ // Set presence to online (local user is always online)
1559
+ this.orchestrator.getAllStatuses().set(alias, { state: "idle", presence: "online" });
1560
+ // Update the adapter name so tasks route to the avatar
1561
+ this.userAlias = alias;
1290
1562
  }
1291
1563
  // ─── Display helpers ──────────────────────────────────────────────
1292
1564
  /**
@@ -1613,15 +1885,29 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1613
1885
  agentPassthrough: cliArgs.agentPassthrough,
1614
1886
  });
1615
1887
  this.adapter = adapter;
1616
- // No .teammates/ found offer onboarding or solo mode
1888
+ // Detect whether this is a brand-new project (no .teammates/ at all)
1889
+ const isNewProject = !teammatesDir;
1617
1890
  if (!teammatesDir) {
1618
- teammatesDir = await this.promptOnboarding(adapter);
1619
- if (!teammatesDir)
1891
+ teammatesDir = join(process.cwd(), ".teammates");
1892
+ await mkdir(teammatesDir, { recursive: true });
1893
+ // Show welcome logo for new projects
1894
+ console.log();
1895
+ this.printLogo([
1896
+ chalk.bold("Teammates") + chalk.gray(` v${PKG_VERSION}`),
1897
+ chalk.yellow("New project setup"),
1898
+ chalk.gray(process.cwd()),
1899
+ ]);
1900
+ }
1901
+ // Always onboard the user first if USER.md is missing
1902
+ if (this.needsUserSetup(teammatesDir)) {
1903
+ await this.runUserSetup(teammatesDir);
1904
+ }
1905
+ // Team onboarding if .teammates/ was missing
1906
+ if (isNewProject) {
1907
+ const cont = await this.promptTeamOnboarding(adapter, teammatesDir);
1908
+ if (!cont)
1620
1909
  return; // user chose to exit
1621
1910
  }
1622
- // Check if USER.md needs setup — we'll run the interview inside the
1623
- // ChatView after the UI loads (not before).
1624
- const pendingUserInterview = this.needsUserSetup(teammatesDir);
1625
1911
  // Init orchestrator
1626
1912
  this.teammatesDir = teammatesDir;
1627
1913
  this.orchestrator = new Orchestrator({
@@ -1630,26 +1916,38 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1630
1916
  onEvent: (e) => this.handleEvent(e),
1631
1917
  });
1632
1918
  await this.orchestrator.init();
1633
- // Register the agent itself as a mentionable teammate
1634
- const registry = this.orchestrator.getRegistry();
1635
- registry.register({
1636
- name: this.adapterName,
1637
- role: `General-purpose coding agent (${this.adapterName})`,
1638
- soul: "",
1639
- wisdom: "",
1640
- dailyLogs: [],
1641
- weeklyLogs: [],
1642
- ownership: { primary: [], secondary: [] },
1643
- routingKeywords: [],
1644
- cwd: dirname(this.teammatesDir),
1645
- });
1646
- // Add status entry (init() already ran, so we add it manually)
1647
- this.orchestrator.getAllStatuses().set(this.adapterName, { state: "idle" });
1919
+ // Register the local user's avatar if alias is configured.
1920
+ // The user's avatar is the entry point for all generic/fallback tasks —
1921
+ // the coding agent is an internal execution engine, not an addressable teammate.
1922
+ const alias = this.readUserAlias(teammatesDir);
1923
+ if (alias) {
1924
+ this.registerUserAvatar(teammatesDir, alias);
1925
+ }
1926
+ else {
1927
+ // No alias yet (solo mode or pre-interview). Register a minimal avatar
1928
+ // under the adapter name so internal tasks (btw, summarize, debug) can execute.
1929
+ const registry = this.orchestrator.getRegistry();
1930
+ registry.register({
1931
+ name: this.adapterName,
1932
+ type: "ai",
1933
+ role: "General-purpose coding agent",
1934
+ soul: "",
1935
+ wisdom: "",
1936
+ dailyLogs: [],
1937
+ weeklyLogs: [],
1938
+ ownership: { primary: [], secondary: [] },
1939
+ routingKeywords: [],
1940
+ cwd: dirname(this.teammatesDir),
1941
+ });
1942
+ this.orchestrator.getAllStatuses().set(this.adapterName, { state: "idle", presence: "online" });
1943
+ }
1648
1944
  // Populate roster on the adapter so prompts include team info
1945
+ // Exclude the user avatar and adapter fallback — neither is an addressable teammate
1649
1946
  if ("roster" in this.adapter) {
1650
1947
  const registry = this.orchestrator.getRegistry();
1651
1948
  this.adapter.roster = this.orchestrator
1652
1949
  .listTeammates()
1950
+ .filter((n) => n !== this.adapterName && n !== this.userAlias)
1653
1951
  .map((name) => {
1654
1952
  const t = registry.get(name);
1655
1953
  return { name: t.name, role: t.role, ownership: t.ownership };
@@ -1667,8 +1965,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1667
1965
  borderStyle: (s) => chalk.gray(s),
1668
1966
  colorize: (value) => {
1669
1967
  const validNames = new Set([
1670
- ...this.orchestrator.listTeammates(),
1671
- this.adapterName,
1968
+ ...this.orchestrator.listTeammates().filter((n) => n !== this.adapterName),
1969
+ this.selfName,
1672
1970
  "everyone",
1673
1971
  ]);
1674
1972
  return value
@@ -1707,18 +2005,25 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1707
2005
  // ── Detect service statuses ────────────────────────────────────────
1708
2006
  this.serviceStatuses = this.detectServices();
1709
2007
  // ── Build animated banner for ChatView ─────────────────────────────
1710
- const names = this.orchestrator.listTeammates();
2008
+ const names = this.orchestrator
2009
+ .listTeammates()
2010
+ .filter((n) => n !== this.adapterName && n !== this.userAlias);
1711
2011
  const reg = this.orchestrator.getRegistry();
2012
+ const statuses = this.orchestrator.getAllStatuses();
2013
+ const bannerTeammates = [];
2014
+ for (const name of names) {
2015
+ const t = reg.get(name);
2016
+ const p = statuses.get(name)?.presence ?? "online";
2017
+ bannerTeammates.push({ name, role: t?.role ?? "", presence: p });
2018
+ }
1712
2019
  const bannerWidget = new AnimatedBanner({
1713
- adapterName: this.adapterName,
2020
+ displayName: `@${this.selfName}`,
1714
2021
  teammateCount: names.length,
1715
2022
  cwd: process.cwd(),
1716
- teammates: names.map((name) => {
1717
- const t = reg.get(name);
1718
- return { name, role: t?.role ?? "" };
1719
- }),
2023
+ teammates: bannerTeammates,
1720
2024
  services: this.serviceStatuses,
1721
2025
  });
2026
+ this.banner = bannerWidget;
1722
2027
  // ── Create ChatView and Consolonia App ────────────────────────────
1723
2028
  const t = theme();
1724
2029
  this.chatView = new ChatView({
@@ -1741,10 +2046,10 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1741
2046
  styles[i] = accentStyle;
1742
2047
  }
1743
2048
  }
1744
- // Colorize @mentions only if they reference a valid teammate or the coding agent
2049
+ // Colorize @mentions only if they reference a valid teammate or the user
1745
2050
  const validNames = new Set([
1746
- ...this.orchestrator.listTeammates(),
1747
- this.adapterName,
2051
+ ...this.orchestrator.listTeammates().filter((n) => n !== this.adapterName),
2052
+ this.selfName,
1748
2053
  "everyone",
1749
2054
  ]);
1750
2055
  const mentionPattern = /@(\w+)/g;
@@ -1787,10 +2092,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1787
2092
  progressStyle: { fg: t.progress, italic: true },
1788
2093
  dropdownHighlightStyle: { fg: t.accent },
1789
2094
  dropdownStyle: { fg: t.textMuted },
1790
- footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`)),
2095
+ footer: concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName)),
2096
+ footerRight: tp.muted("? /help "),
1791
2097
  footerStyle: { fg: t.textDim },
1792
2098
  });
1793
- this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`));
2099
+ this.defaultFooter = concat(tp.accent(" Teammates"), tp.dim(` v${PKG_VERSION}`), tp.muted(" "), tp.text(this.adapterName));
2100
+ this.defaultFooterRight = tp.muted("? /help ");
1794
2101
  // Wire ChatView events for input handling
1795
2102
  this.chatView.on("submit", (rawLine) => {
1796
2103
  this.handleSubmit(rawLine).catch((err) => {
@@ -1816,6 +2123,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1816
2123
  this.escTimer = null;
1817
2124
  }
1818
2125
  this.chatView.setFooter(this.defaultFooter);
2126
+ this.chatView.setFooterRight(this.defaultFooterRight);
1819
2127
  this.refreshView();
1820
2128
  }
1821
2129
  if (this.ctrlcPending) {
@@ -1825,6 +2133,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1825
2133
  this.ctrlcTimer = null;
1826
2134
  }
1827
2135
  this.chatView.setFooter(this.defaultFooter);
2136
+ this.chatView.setFooterRight(this.defaultFooterRight);
1828
2137
  this.refreshView();
1829
2138
  }
1830
2139
  });
@@ -1848,22 +2157,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1848
2157
  }
1849
2158
  this.chatView.inputValue = "";
1850
2159
  this.chatView.setFooter(this.defaultFooter);
2160
+ this.chatView.setFooterRight(this.defaultFooterRight);
1851
2161
  this.pastedTexts.clear();
1852
2162
  this.refreshView();
1853
2163
  }
1854
2164
  else if (this.chatView.inputValue.length > 0) {
1855
- // First ESC with text — show hint in footer, auto-expire after 2s
2165
+ // First ESC with text — show hint in footer right, auto-expire after 2s
1856
2166
  this.escPending = true;
1857
- const termW = process.stdout.columns || 80;
1858
- const hint = "ESC again to clear";
1859
- const pad = Math.max(0, termW - hint.length - 1);
1860
- this.chatView.setFooter(concat(tp.dim(" ".repeat(pad)), tp.muted(hint)));
2167
+ this.chatView.setFooterRight(tp.muted("ESC again to clear "));
1861
2168
  this.refreshView();
1862
2169
  this.escTimer = setTimeout(() => {
1863
2170
  this.escTimer = null;
1864
2171
  if (this.escPending) {
1865
2172
  this.escPending = false;
1866
2173
  this.chatView.setFooter(this.defaultFooter);
2174
+ this.chatView.setFooterRight(this.defaultFooterRight);
1867
2175
  this.refreshView();
1868
2176
  }
1869
2177
  }, 2000);
@@ -1881,29 +2189,31 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1881
2189
  this.ctrlcTimer = null;
1882
2190
  }
1883
2191
  this.chatView.setFooter(this.defaultFooter);
2192
+ this.chatView.setFooterRight(this.defaultFooterRight);
1884
2193
  if (this.app)
1885
2194
  this.app.stop();
1886
2195
  this.orchestrator.shutdown().then(() => process.exit(0));
1887
2196
  return;
1888
2197
  }
1889
- // First Ctrl+C — show hint in footer, auto-expire after 2s
2198
+ // First Ctrl+C — show hint in footer right, auto-expire after 2s
1890
2199
  this.ctrlcPending = true;
1891
- const termW = process.stdout.columns || 80;
1892
- const hint = "Ctrl+C again to exit";
1893
- const pad = Math.max(0, termW - hint.length - 1);
1894
- this.chatView.setFooter(concat(tp.dim(" ".repeat(pad)), tp.muted(hint)));
2200
+ this.chatView.setFooterRight(tp.muted("Ctrl+C again to exit "));
1895
2201
  this.refreshView();
1896
2202
  this.ctrlcTimer = setTimeout(() => {
1897
2203
  this.ctrlcTimer = null;
1898
2204
  if (this.ctrlcPending) {
1899
2205
  this.ctrlcPending = false;
1900
2206
  this.chatView.setFooter(this.defaultFooter);
2207
+ this.chatView.setFooterRight(this.defaultFooterRight);
1901
2208
  this.refreshView();
1902
2209
  }
1903
2210
  }, 2000);
1904
2211
  });
1905
2212
  this.chatView.on("action", (id) => {
1906
- if (id === "copy") {
2213
+ if (id.startsWith("copy-cmd:")) {
2214
+ this.doCopy(id.slice("copy-cmd:".length));
2215
+ }
2216
+ else if (id === "copy") {
1907
2217
  this.doCopy(this.lastCleanedOutput || undefined);
1908
2218
  }
1909
2219
  else if (id.startsWith("retro-approve-") ||
@@ -1952,15 +2262,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1952
2262
  // Start the banner animation after the first frame renders.
1953
2263
  bannerWidget.onDirty = () => this.app?.refresh();
1954
2264
  const runPromise = this.app.run();
1955
- // Hold the banner animation before commands if we need to run the interview
1956
- if (pendingUserInterview) {
1957
- bannerWidget.hold();
1958
- }
1959
2265
  bannerWidget.start();
1960
- // Run user interview inside the ChatView if USER.md needs setup
1961
- if (pendingUserInterview) {
1962
- this.startUserInterview(teammatesDir, bannerWidget);
1963
- }
1964
2266
  await runPromise;
1965
2267
  }
1966
2268
  /**
@@ -1983,7 +2285,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1983
2285
  const fileName = trimmed.split(/[/\\]/).pop() || trimmed;
1984
2286
  const n = ++this.pasteCounter;
1985
2287
  this.pastedTexts.set(n, `[Image: source: ${trimmed}]`);
1986
- const placeholder = `[Image ${fileName}]`;
2288
+ const placeholder = `[Image ${fileName}] `;
1987
2289
  const newVal = current.slice(0, idx) +
1988
2290
  placeholder +
1989
2291
  current.slice(idx + clean.length);
@@ -2020,9 +2322,28 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2020
2322
  }
2021
2323
  /** Handle line submission from ChatView. */
2022
2324
  async handleSubmit(rawLine) {
2325
+ // If an inline ask is pending, resolve it instead of normal processing
2326
+ if (this._pendingAsk) {
2327
+ const resolve = this._pendingAsk;
2328
+ this._pendingAsk = null;
2329
+ resolve(rawLine);
2330
+ return;
2331
+ }
2023
2332
  this.clearWordwheel();
2024
2333
  this.wordwheelItems = [];
2025
2334
  this.wordwheelIndex = -1;
2335
+ // Resolve @mentions from the raw input BEFORE paste expansion.
2336
+ // This prevents @mentions inside pasted/expanded text from being picked up.
2337
+ const allNames = this.orchestrator.listTeammates();
2338
+ const preMentionRegex = /@(\S+)/g;
2339
+ let pm;
2340
+ const preMentions = [];
2341
+ while ((pm = preMentionRegex.exec(rawLine)) !== null) {
2342
+ const name = pm[1];
2343
+ if (allNames.includes(name) && !preMentions.includes(name)) {
2344
+ preMentions.push(name);
2345
+ }
2346
+ }
2026
2347
  // Expand paste placeholders with actual content
2027
2348
  let input = rawLine.replace(/\[Pasted text #(\d+) \+\d+ lines, [\d.]+KB\]\s*/g, (_match, num) => {
2028
2349
  const n = parseInt(num, 10);
@@ -2103,10 +2424,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2103
2424
  this.refreshView();
2104
2425
  return;
2105
2426
  }
2106
- // Everything else gets queued
2107
- this.conversationHistory.push({ role: "user", text: input });
2427
+ // Everything else gets queued.
2428
+ // Pass pre-resolved mentions so @mentions inside expanded paste text are ignored.
2429
+ this.conversationHistory.push({ role: this.selfName, text: input });
2108
2430
  this.printUserMessage(input);
2109
- this.queueTask(input);
2431
+ this.queueTask(input, preMentions);
2110
2432
  this.refreshView();
2111
2433
  }
2112
2434
  printBanner(teammates) {
@@ -2114,7 +2436,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2114
2436
  const termWidth = process.stdout.columns || 100;
2115
2437
  this.feedLine();
2116
2438
  this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
2117
- this.feedLine(concat(tp.text(` ${this.adapterName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
2439
+ this.feedLine(concat(tp.text(` @${this.selfName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
2118
2440
  this.feedLine(` ${process.cwd()}`);
2119
2441
  // Service status rows
2120
2442
  for (const svc of this.serviceStatuses) {
@@ -2130,12 +2452,15 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2130
2452
  : `missing — /configure ${svc.name.toLowerCase()}`;
2131
2453
  this.feedLine(concat(tp.text(" "), color(icon), color(svc.name), tp.muted(` ${label}`)));
2132
2454
  }
2133
- // Roster
2455
+ // Roster (with presence indicators)
2134
2456
  this.feedLine();
2457
+ const statuses = this.orchestrator.getAllStatuses();
2135
2458
  for (const name of teammates) {
2136
2459
  const t = registry.get(name);
2137
2460
  if (t) {
2138
- this.feedLine(concat(tp.muted(" "), tp.accent(`● @${name.padEnd(14)}`), tp.muted(t.role)));
2461
+ const p = statuses.get(name)?.presence ?? "online";
2462
+ const dot = p === "online" ? tp.success("●") : p === "reachable" ? tp.warning("●") : tp.error("●");
2463
+ this.feedLine(concat(tp.text(" "), dot, tp.accent(` @${name.padEnd(14)}`), tp.muted(t.role)));
2139
2464
  }
2140
2465
  }
2141
2466
  this.feedLine();
@@ -2255,43 +2580,21 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2255
2580
  this.feedLine(tp.warning(" GitHub CLI is not installed."));
2256
2581
  this.feedLine();
2257
2582
  const plat = process.platform;
2258
- let installCmd;
2259
- let installLabel;
2583
+ this.feedLine(tp.text(" Run this in another terminal:"));
2260
2584
  if (plat === "win32") {
2261
- installCmd = "winget install --id GitHub.cli";
2262
- installLabel = "winget install --id GitHub.cli";
2585
+ this.feedCommand("winget install --id GitHub.cli");
2263
2586
  }
2264
2587
  else if (plat === "darwin") {
2265
- installCmd = "brew install gh";
2266
- installLabel = "brew install gh";
2588
+ this.feedCommand("brew install gh");
2267
2589
  }
2268
2590
  else {
2269
- installCmd = "sudo apt install gh";
2270
- installLabel = "sudo apt install gh (or see https://cli.github.com)";
2591
+ this.feedCommand("sudo apt install gh");
2592
+ this.feedLine(tp.muted(" (or see https://cli.github.com)"));
2271
2593
  }
2272
- this.feedLine(tp.text(` Install: ${installLabel}`));
2273
2594
  this.feedLine();
2274
- // Ask user
2275
- const answer = await this.askInput("Run install command? [Y/n] ");
2595
+ const answer = await this.askInline("Press Enter when done (or n to skip)");
2276
2596
  if (answer.toLowerCase() === "n") {
2277
- this.feedLine(tp.muted(" Skipped. Install manually and re-run /configure github"));
2278
- this.refreshView();
2279
- return;
2280
- }
2281
- // Spawn install in a visible subprocess
2282
- this.feedLine(tp.muted(` Running: ${installCmd}`));
2283
- this.refreshView();
2284
- const installSuccess = await new Promise((res) => {
2285
- const parts = installCmd.split(" ");
2286
- const child = spawn(parts[0], parts.slice(1), {
2287
- stdio: "inherit",
2288
- shell: true,
2289
- });
2290
- child.on("error", () => res(false));
2291
- child.on("exit", (code) => res(code === 0));
2292
- });
2293
- if (!installSuccess) {
2294
- this.feedLine(tp.error(" Install failed. Please install manually from https://cli.github.com"));
2597
+ this.feedLine(tp.muted(" Skipped. Run /configure github when ready."));
2295
2598
  this.refreshView();
2296
2599
  return;
2297
2600
  }
@@ -2302,7 +2605,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2302
2605
  this.feedLine(tp.success(" ✓ GitHub CLI installed"));
2303
2606
  }
2304
2607
  catch {
2305
- this.feedLine(tp.error(" GitHub CLI still not found after install. You may need to restart your terminal."));
2608
+ this.feedLine(tp.error(" GitHub CLI still not found. You may need to restart your terminal."));
2306
2609
  this.refreshView();
2307
2610
  return;
2308
2611
  }
@@ -2321,31 +2624,19 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2321
2624
  // not authenticated
2322
2625
  }
2323
2626
  if (!authed) {
2324
- this.feedLine(tp.muted(" Authentication needed — this will open your browser for GitHub OAuth."));
2325
2627
  this.feedLine();
2326
- const answer = await this.askInput("Start authentication? [Y/n] ");
2628
+ this.feedLine(tp.text(" Run this in another terminal to authenticate:"));
2629
+ this.feedCommand("gh auth login --web --git-protocol https");
2630
+ this.feedLine();
2631
+ this.feedLine(tp.muted(" This will open your browser for GitHub OAuth."));
2632
+ this.feedLine();
2633
+ const answer = await this.askInline("Press Enter when done (or n to skip)");
2327
2634
  if (answer.toLowerCase() === "n") {
2328
2635
  this.feedLine(tp.muted(" Skipped. Run /configure github when ready."));
2329
2636
  this.refreshView();
2330
2637
  this.updateServiceStatus("GitHub", "not-configured");
2331
2638
  return;
2332
2639
  }
2333
- this.feedLine(tp.muted(" Starting auth flow..."));
2334
- this.refreshView();
2335
- const authSuccess = await new Promise((res) => {
2336
- const child = spawn("gh", ["auth", "login", "--web", "--git-protocol", "https"], {
2337
- stdio: "inherit",
2338
- shell: true,
2339
- });
2340
- child.on("error", () => res(false));
2341
- child.on("exit", (code) => res(code === 0));
2342
- });
2343
- if (!authSuccess) {
2344
- this.feedLine(tp.error(" Authentication failed. Try again with /configure github"));
2345
- this.refreshView();
2346
- this.updateServiceStatus("GitHub", "not-configured");
2347
- return;
2348
- }
2349
2640
  // Verify
2350
2641
  try {
2351
2642
  execSync("gh auth status", { stdio: "pipe" });
@@ -2373,8 +2664,13 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2373
2664
  }
2374
2665
  updateServiceStatus(name, status) {
2375
2666
  const svc = this.serviceStatuses.find((s) => s.name === name);
2376
- if (svc)
2667
+ if (svc) {
2377
2668
  svc.status = status;
2669
+ if (this.banner) {
2670
+ this.banner.updateServices(this.serviceStatuses);
2671
+ this.refreshView();
2672
+ }
2673
+ }
2378
2674
  }
2379
2675
  registerCommands() {
2380
2676
  const cmds = [
@@ -2541,7 +2837,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2541
2837
  const sizeKB = cleaned ? Buffer.byteLength(cleaned, "utf-8") / 1024 : 0;
2542
2838
  // Header: "teammate: subject"
2543
2839
  const subject = event.result.summary || "Task completed";
2544
- this.feedLine(concat(tp.accent(`${event.result.teammate}: `), tp.text(subject)));
2840
+ const displayTeammate = event.result.teammate === this.adapterName ? this.selfName : event.result.teammate;
2841
+ this.feedLine(concat(tp.accent(`${displayTeammate}: `), tp.text(subject)));
2545
2842
  this.lastCleanedOutput = cleaned;
2546
2843
  if (cleaned) {
2547
2844
  this.feedMarkdown(cleaned);
@@ -2611,7 +2908,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2611
2908
  this.stopStatusAnimation();
2612
2909
  if (!this.chatView)
2613
2910
  this.input.deactivateAndErase();
2614
- this.feedLine(tp.error(` ✖ ${event.teammate}: ${event.error}`));
2911
+ const displayErr = event.teammate === this.adapterName ? this.selfName : event.teammate;
2912
+ this.feedLine(tp.error(` ✖ ${displayErr}: ${event.error}`));
2615
2913
  this.showPrompt();
2616
2914
  break;
2617
2915
  }
@@ -2622,16 +2920,36 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2622
2920
  this.feedLine();
2623
2921
  this.feedLine(tp.bold(" Status"));
2624
2922
  this.feedLine(tp.muted(` ${"─".repeat(50)}`));
2923
+ // Show user avatar first if present
2924
+ if (this.userAlias) {
2925
+ const userStatus = statuses.get(this.userAlias);
2926
+ if (userStatus) {
2927
+ this.feedLine(concat(tp.success("●"), tp.accent(` @${this.userAlias}`), tp.muted(" (you)")));
2928
+ const t = registry.get(this.userAlias);
2929
+ if (t)
2930
+ this.feedLine(tp.muted(` ${t.role}`));
2931
+ this.feedLine();
2932
+ }
2933
+ }
2625
2934
  for (const [name, status] of statuses) {
2935
+ // Skip the user avatar (shown above) and adapter fallback (not addressable)
2936
+ if (name === this.adapterName || name === this.userAlias)
2937
+ continue;
2626
2938
  const t = registry.get(name);
2627
2939
  const active = this.agentActive.get(name);
2628
2940
  const queued = this.taskQueue.filter((e) => e.teammate === name);
2941
+ // Presence indicator: ● green=online, ● red=offline, ● yellow=reachable
2942
+ const presenceIcon = status.presence === "online"
2943
+ ? tp.success("●")
2944
+ : status.presence === "reachable"
2945
+ ? tp.warning("●")
2946
+ : tp.error("●");
2629
2947
  // Teammate name + state
2630
2948
  const stateLabel = active ? "working" : status.state;
2631
2949
  const stateColor = stateLabel === "working"
2632
2950
  ? tp.info(` (${stateLabel})`)
2633
2951
  : tp.muted(` (${stateLabel})`);
2634
- this.feedLine(concat(tp.accent(` @${name}`), stateColor));
2952
+ this.feedLine(concat(presenceIcon, tp.accent(` @${name}`), stateColor));
2635
2953
  // Role
2636
2954
  if (t) {
2637
2955
  this.feedLine(tp.muted(` ${t.role}`));
@@ -2669,7 +2987,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2669
2987
  // Pick all teammates with debug files, queue one analysis per teammate
2670
2988
  const names = [];
2671
2989
  for (const [name] of this.lastDebugFiles) {
2672
- if (name !== this.adapterName)
2990
+ if (name !== this.selfName)
2673
2991
  names.push(name);
2674
2992
  }
2675
2993
  if (names.length === 0) {
@@ -2734,7 +3052,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2734
3052
  this.refreshView();
2735
3053
  this.taskQueue.push({
2736
3054
  type: "debug",
2737
- teammate: this.adapterName,
3055
+ teammate: this.selfName,
2738
3056
  task: analysisPrompt,
2739
3057
  });
2740
3058
  this.kickDrain();
@@ -2752,7 +3070,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2752
3070
  return;
2753
3071
  }
2754
3072
  const removed = this.taskQueue.splice(n - 1, 1)[0];
2755
- this.feedLine(concat(tp.muted(" Cancelled: "), tp.accent(`@${removed.teammate}`), tp.muted(" "), tp.text(removed.task.slice(0, 60))));
3073
+ const cancelDisplay = removed.teammate === this.adapterName ? this.selfName : removed.teammate;
3074
+ this.feedLine(concat(tp.muted(" Cancelled: "), tp.accent(`@${cancelDisplay}`), tp.muted(" — "), tp.text(removed.task.slice(0, 60))));
2756
3075
  this.refreshView();
2757
3076
  }
2758
3077
  /** Drain tasks for a single agent — runs in parallel with other agents. */
@@ -2815,7 +3134,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2815
3134
  if (this.activeTasks.size === 0)
2816
3135
  this.stopStatusAnimation();
2817
3136
  const msg = err?.message ?? String(err);
2818
- this.feedLine(tp.error(` ✖ @${agent}: ${msg}`));
3137
+ const displayAgent = agent === this.adapterName ? this.selfName : agent;
3138
+ this.feedLine(tp.error(` ✖ @${displayAgent}: ${msg}`));
2819
3139
  this.refreshView();
2820
3140
  }
2821
3141
  this.agentActive.delete(agent);
@@ -2950,11 +3270,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
2950
3270
  // Copy framework files so the agent has TEMPLATE.md etc. available
2951
3271
  await copyTemplateFiles(teammatesDir);
2952
3272
  // Queue a single adaptation task that handles all teammates
2953
- this.feedLine(tp.muted(` Queuing ${this.adapterName} to scan this project and adapt the team...`));
3273
+ this.feedLine(tp.muted(" Queuing agent to scan this project and adapt the team..."));
2954
3274
  const prompt = await buildImportAdaptationPrompt(teammatesDir, allTeammates, sourceDir);
2955
3275
  this.taskQueue.push({
2956
3276
  type: "agent",
2957
- teammate: this.adapterName,
3277
+ teammate: this.selfName,
2958
3278
  task: prompt,
2959
3279
  });
2960
3280
  this.kickDrain();
@@ -3014,9 +3334,11 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3014
3334
  return;
3015
3335
  const registry = this.orchestrator.getRegistry();
3016
3336
  // Update adapter roster so prompts include the new teammates
3337
+ // Exclude the user avatar and adapter fallback — neither is an addressable teammate
3017
3338
  if ("roster" in this.adapter) {
3018
3339
  this.adapter.roster = this.orchestrator
3019
3340
  .listTeammates()
3341
+ .filter((n) => n !== this.adapterName && n !== this.userAlias)
3020
3342
  .map((name) => {
3021
3343
  const t = registry.get(name);
3022
3344
  return { name: t.name, role: t.role, ownership: t.ownership };
@@ -3038,7 +3360,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3038
3360
  const arg = argsStr.trim().replace(/^@/, "");
3039
3361
  const allTeammates = this.orchestrator
3040
3362
  .listTeammates()
3041
- .filter((n) => n !== this.adapterName);
3363
+ .filter((n) => n !== this.selfName && n !== this.adapterName);
3042
3364
  const names = !arg || arg === "everyone" ? allTeammates : [arg];
3043
3365
  // Validate all names first
3044
3366
  const valid = [];
@@ -3109,7 +3431,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3109
3431
  if (spinner)
3110
3432
  spinner.succeed(`${name}: ${parts.join(", ")}`);
3111
3433
  if (this.chatView)
3112
- this.feedLine(tp.success(` ✔ ${name}: ${parts.join(", ")}`));
3434
+ this.feedLine(tp.success(` ✔ ${name}: ${parts.join(", ")}`));
3113
3435
  }
3114
3436
  if (this.chatView)
3115
3437
  this.chatView.setProgress(null);
@@ -3131,7 +3453,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3131
3453
  syncSpinner.succeed(`${name}: index synced`);
3132
3454
  if (this.chatView) {
3133
3455
  this.chatView.setProgress(null);
3134
- this.feedLine(tp.success(` ✔ ${name}: index synced`));
3456
+ this.feedLine(tp.success(` ✔ ${name}: index synced`));
3135
3457
  }
3136
3458
  }
3137
3459
  catch {
@@ -3161,7 +3483,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3161
3483
  spinner.fail(`${name}: ${msg}`);
3162
3484
  if (this.chatView) {
3163
3485
  this.chatView.setProgress(null);
3164
- this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
3486
+ this.feedLine(tp.error(` ✖ ${name}: ${msg}`));
3165
3487
  }
3166
3488
  }
3167
3489
  this.refreshView();
@@ -3171,7 +3493,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3171
3493
  // Resolve target list
3172
3494
  const allTeammates = this.orchestrator
3173
3495
  .listTeammates()
3174
- .filter((n) => n !== this.adapterName);
3496
+ .filter((n) => n !== this.selfName && n !== this.adapterName);
3175
3497
  let targets;
3176
3498
  if (arg === "everyone") {
3177
3499
  targets = allTeammates;
@@ -3284,7 +3606,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3284
3606
  }
3285
3607
  const teammates = this.orchestrator
3286
3608
  .listTeammates()
3287
- .filter((n) => n !== this.adapterName);
3609
+ .filter((n) => n !== this.selfName && n !== this.adapterName);
3288
3610
  if (teammates.length === 0)
3289
3611
  return;
3290
3612
  // 1. Check each teammate for stale daily logs (older than 7 days)
@@ -3369,7 +3691,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3369
3691
  child.stdin?.end();
3370
3692
  // Show brief "Copied" message in the progress area
3371
3693
  if (this.chatView) {
3372
- this.chatView.setProgress(concat(tp.success("✔ "), tp.muted("Copied to clipboard")));
3694
+ this.chatView.setProgress(concat(tp.success("✔ "), tp.muted("Copied to clipboard")));
3373
3695
  this.refreshView();
3374
3696
  setTimeout(() => {
3375
3697
  this.chatView.setProgress(null);
@@ -3379,7 +3701,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3379
3701
  }
3380
3702
  catch {
3381
3703
  if (this.chatView) {
3382
- this.chatView.setProgress(concat(tp.error("✖ "), tp.muted("Failed to copy")));
3704
+ this.chatView.setProgress(concat(tp.error("✖ "), tp.muted("Failed to copy")));
3383
3705
  this.refreshView();
3384
3706
  setTimeout(() => {
3385
3707
  this.chatView.setProgress(null);
@@ -3388,6 +3710,19 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3388
3710
  }
3389
3711
  }
3390
3712
  }
3713
+ /**
3714
+ * Feed a command line with a clickable [copy] button.
3715
+ * Renders as: ` command text [copy]`
3716
+ */
3717
+ feedCommand(command) {
3718
+ if (!this.chatView) {
3719
+ this.feedLine(tp.accent(` ${command}`));
3720
+ return;
3721
+ }
3722
+ const normal = concat(tp.accent(` ${command} `), tp.muted("[copy]"));
3723
+ const hover = concat(tp.accent(` ${command} `), tp.accent("[copy]"));
3724
+ this.chatView.appendAction(`copy-cmd:${command}`, normal, hover);
3725
+ }
3391
3726
  async cmdHelp() {
3392
3727
  this.feedLine();
3393
3728
  this.feedLine(tp.bold(" Commands"));
@@ -3439,10 +3774,10 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3439
3774
  this.refreshView();
3440
3775
  return;
3441
3776
  }
3442
- // Has args — queue a task to the coding agent to apply the change
3777
+ // Has args — queue a task to apply the change
3443
3778
  const task = `Update the file ${userMdPath} with the following change:\n\n${change}\n\nKeep the existing content intact unless the change explicitly replaces something. This is the user's profile — be concise and accurate.`;
3444
- this.taskQueue.push({ type: "agent", teammate: this.adapterName, task });
3445
- this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.adapterName}`)));
3779
+ this.taskQueue.push({ type: "agent", teammate: this.selfName, task });
3780
+ this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.selfName}`)));
3446
3781
  this.feedLine();
3447
3782
  this.refreshView();
3448
3783
  this.kickDrain();
@@ -3456,10 +3791,10 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3456
3791
  }
3457
3792
  this.taskQueue.push({
3458
3793
  type: "btw",
3459
- teammate: this.adapterName,
3794
+ teammate: this.selfName,
3460
3795
  task: question,
3461
3796
  });
3462
- this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.adapterName}`)));
3797
+ this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.selfName}`)));
3463
3798
  this.feedLine();
3464
3799
  this.refreshView();
3465
3800
  this.kickDrain();
@@ -3488,9 +3823,9 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3488
3823
  row("textDim", t.textDim, "─── separator ───");
3489
3824
  this.feedLine();
3490
3825
  // Status
3491
- row("success", t.success, "✔ Task completed");
3492
- row("warning", t.warning, "⚠ Pending handoff");
3493
- row("error", t.error, "✖ Something went wrong");
3826
+ row("success", t.success, "✔ Task completed");
3827
+ row("warning", t.warning, "⚠ Pending handoff");
3828
+ row("error", t.error, "✖ Something went wrong");
3494
3829
  row("info", t.info, "⠋ Working on task...");
3495
3830
  this.feedLine();
3496
3831
  // Interactive
@@ -3558,9 +3893,9 @@ Issues that can't be resolved unilaterally — they need input from other teamma
3558
3893
  "",
3559
3894
  "| Language | Status |",
3560
3895
  "|------------|---------|",
3561
- "| JavaScript | ✔ Ready |",
3562
- "| Python | ✔ Ready |",
3563
- "| C# | ✔ Ready |",
3896
+ "| JavaScript | ✔ Ready |",
3897
+ "| Python | ✔ Ready |",
3898
+ "| C# | ✔ Ready |",
3564
3899
  "",
3565
3900
  "---",
3566
3901
  ].join("\n");