@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/adapter.d.ts +2 -0
- package/dist/adapter.js +4 -0
- package/dist/adapter.test.js +1 -0
- package/dist/adapters/cli-proxy.js +12 -0
- package/dist/adapters/copilot.js +11 -0
- package/dist/adapters/echo.test.js +1 -0
- package/dist/banner.d.ts +6 -1
- package/dist/banner.js +18 -3
- package/dist/cli-args.js +0 -1
- package/dist/cli.js +589 -254
- package/dist/console/startup.d.ts +2 -1
- package/dist/console/startup.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/orchestrator.d.ts +2 -0
- package/dist/orchestrator.js +15 -12
- package/dist/orchestrator.test.js +2 -1
- package/dist/registry.js +7 -0
- package/dist/registry.test.js +1 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
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,
|
|
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,
|
|
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
|
|
106
|
+
// Queue the summarization task through the user's agent
|
|
107
107
|
this.taskQueue.push({
|
|
108
108
|
type: "summarize",
|
|
109
|
-
teammate: this.
|
|
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
|
-
|
|
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) -
|
|
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} ${
|
|
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(
|
|
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
|
|
303
|
-
const label =
|
|
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(` ✖
|
|
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
|
-
//
|
|
869
|
-
|
|
870
|
-
let
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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.
|
|
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.
|
|
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
|
|
928
|
-
*
|
|
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
|
|
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(
|
|
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
|
|
982
|
+
return false;
|
|
967
983
|
}
|
|
968
984
|
if (choice === "3") {
|
|
969
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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.
|
|
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(" ✔
|
|
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(
|
|
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.
|
|
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(" ✔
|
|
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
|
-
*
|
|
1226
|
-
*
|
|
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
|
-
|
|
1229
|
-
|
|
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
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
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
|
-
//
|
|
1888
|
+
// Detect whether this is a brand-new project (no .teammates/ at all)
|
|
1889
|
+
const isNewProject = !teammatesDir;
|
|
1617
1890
|
if (!teammatesDir) {
|
|
1618
|
-
teammatesDir =
|
|
1619
|
-
|
|
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
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
2020
|
+
displayName: `@${this.selfName}`,
|
|
1714
2021
|
teammateCount: names.length,
|
|
1715
2022
|
cwd: process.cwd(),
|
|
1716
|
-
teammates:
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
2259
|
-
let installLabel;
|
|
2583
|
+
this.feedLine(tp.text(" Run this in another terminal:"));
|
|
2260
2584
|
if (plat === "win32") {
|
|
2261
|
-
|
|
2262
|
-
installLabel = "winget install --id GitHub.cli";
|
|
2585
|
+
this.feedCommand("winget install --id GitHub.cli");
|
|
2263
2586
|
}
|
|
2264
2587
|
else if (plat === "darwin") {
|
|
2265
|
-
|
|
2266
|
-
installLabel = "brew install gh";
|
|
2588
|
+
this.feedCommand("brew install gh");
|
|
2267
2589
|
}
|
|
2268
2590
|
else {
|
|
2269
|
-
|
|
2270
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(`
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
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(` ✔
|
|
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(` ✔
|
|
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(` ✖
|
|
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("✔
|
|
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("✖
|
|
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
|
|
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.
|
|
3445
|
-
this.feedLine(concat(tp.muted(" Queued USER.md update → "), tp.accent(`@${this.
|
|
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.
|
|
3794
|
+
teammate: this.selfName,
|
|
3460
3795
|
task: question,
|
|
3461
3796
|
});
|
|
3462
|
-
this.feedLine(concat(tp.muted(" Side question → "), tp.accent(`@${this.
|
|
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, "✔
|
|
3492
|
-
row("warning", t.warning, "⚠
|
|
3493
|
-
row("error", t.error, "✖
|
|
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 | ✔
|
|
3562
|
-
"| Python | ✔
|
|
3563
|
-
"| C# | ✔
|
|
3896
|
+
"| JavaScript | ✔ Ready |",
|
|
3897
|
+
"| Python | ✔ Ready |",
|
|
3898
|
+
"| C# | ✔ Ready |",
|
|
3564
3899
|
"",
|
|
3565
3900
|
"---",
|
|
3566
3901
|
].join("\n");
|