@songsid/agend 2.1.2-beta.3 → 2.1.2-beta.30
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/access-path.js +15 -6
- package/dist/access-path.js.map +1 -1
- package/dist/agent-cli-instructions.md +1 -0
- package/dist/agent-cli.js +14 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +25 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +20 -0
- package/dist/backend/antigravity.js +37 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +59 -0
- package/dist/backend/claude-code.js +67 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +30 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.d.ts +25 -0
- package/dist/backend/grok.js +51 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +22 -0
- package/dist/backend/kiro.js +85 -3
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +58 -0
- package/dist/backend/types.js +13 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +15 -0
- package/dist/channel/adapters/discord.js +100 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +21 -0
- package/dist/channel/adapters/telegram.js +86 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +9 -1
- package/dist/channel/ipc-bridge.js +12 -3
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/ipc-timeouts.d.ts +46 -0
- package/dist/channel/ipc-timeouts.js +65 -0
- package/dist/channel/ipc-timeouts.js.map +1 -0
- package/dist/channel/mcp-server.js +30 -14
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +30 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/reconnect-backoff.d.ts +17 -0
- package/dist/channel/reconnect-backoff.js +21 -0
- package/dist/channel/reconnect-backoff.js.map +1 -0
- package/dist/channel/types.d.ts +29 -0
- package/dist/classic-channel-manager.js +1 -5
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +155 -63
- package/dist/cli.js.map +1 -1
- package/dist/completion.d.ts +27 -0
- package/dist/completion.js +121 -0
- package/dist/completion.js.map +1 -0
- package/dist/config-validator.js +21 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/cost-guard.d.ts +3 -1
- package/dist/cost-guard.js +3 -1
- package/dist/cost-guard.js.map +1 -1
- package/dist/daemon.d.ts +240 -14
- package/dist/daemon.js +956 -308
- package/dist/daemon.js.map +1 -1
- package/dist/event-log.d.ts +31 -0
- package/dist/event-log.js +96 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-context.d.ts +9 -0
- package/dist/fleet-manager.d.ts +311 -2
- package/dist/fleet-manager.js +1369 -108
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +22 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +14 -23
- package/dist/general-knowledge/skills/session-management/SKILL.md +15 -34
- package/dist/hang-detector.d.ts +19 -13
- package/dist/hang-detector.js +19 -49
- package/dist/hang-detector.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +43 -1
- package/dist/instance-lifecycle.js +155 -43
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +5 -0
- package/dist/instructions.js +9 -11
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +5 -1
- package/dist/locale.js.map +1 -1
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -1
- package/dist/mcp-liveness.d.ts +21 -0
- package/dist/mcp-liveness.js +27 -0
- package/dist/mcp-liveness.js.map +1 -0
- package/dist/outbound-handlers.d.ts +16 -0
- package/dist/outbound-handlers.js +151 -27
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +11 -3
- package/dist/outbound-schemas.js +16 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-write-lock.d.ts +48 -0
- package/dist/pane-write-lock.js +73 -0
- package/dist/pane-write-lock.js.map +1 -0
- package/dist/process-memory.d.ts +31 -0
- package/dist/process-memory.js +79 -0
- package/dist/process-memory.js.map +1 -0
- package/dist/quickstart.js +17 -16
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-dedup.d.ts +41 -0
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -0
- package/dist/scheduler/db.js +3 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/sd-notify.d.ts +27 -0
- package/dist/sd-notify.js +33 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/secret-file.d.ts +33 -0
- package/dist/secret-file.js +36 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/setup-wizard.js +9 -7
- package/dist/setup-wizard.js.map +1 -1
- package/dist/tmux-control.d.ts +58 -5
- package/dist/tmux-control.js +102 -14
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +31 -1
- package/dist/tmux-manager.js +40 -11
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +50 -10
- package/dist/topic-commands.js +269 -106
- package/dist/topic-commands.js.map +1 -1
- package/dist/tui-glyphs.d.ts +25 -0
- package/dist/tui-glyphs.js +26 -0
- package/dist/tui-glyphs.js.map +1 -0
- package/dist/types.d.ts +12 -2
- package/dist/ui/view.html +123 -2
- package/dist/update-marker.d.ts +15 -0
- package/dist/update-marker.js +69 -0
- package/dist/update-marker.js.map +1 -0
- package/dist/usage/format-rich.d.ts +11 -0
- package/dist/usage/format-rich.js +121 -0
- package/dist/usage/format-rich.js.map +1 -0
- package/dist/usage/providers.d.ts +130 -0
- package/dist/usage/providers.js +1030 -0
- package/dist/usage/providers.js.map +1 -0
- package/dist/usage/statusline-usage.d.ts +20 -0
- package/dist/usage/statusline-usage.js +120 -0
- package/dist/usage/statusline-usage.js.map +1 -0
- package/dist/usage/usage-api.d.ts +50 -0
- package/dist/usage/usage-api.js +167 -0
- package/dist/usage/usage-api.js.map +1 -0
- package/dist/view-api.d.ts +3 -0
- package/dist/view-api.js +11 -2
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.js +5 -2
- package/dist/web-api.js.map +1 -1
- package/package.json +4 -1
- package/dist/channel/tool-tracker.d.ts +0 -13
- package/dist/channel/tool-tracker.js +0 -58
- package/dist/channel/tool-tracker.js.map +0 -1
- package/dist/daemon-entry.d.ts +0 -1
- package/dist/daemon-entry.js +0 -30
- package/dist/daemon-entry.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,10 @@ import { spawn, execSync, execFileSync } from "node:child_process";
|
|
|
13
13
|
import { getAgendHome, getTmuxSocketName } from "./paths.js";
|
|
14
14
|
import { readClassicLastActivityAt } from "./classic-channel-manager.js";
|
|
15
15
|
import { hasPausedMarker } from "./pause-marker.js";
|
|
16
|
+
import { PIE_PERCENT_RE } from "./tui-glyphs.js";
|
|
17
|
+
import { COMPLETION_SHELLS, completionScript } from "./completion.js";
|
|
16
18
|
import { getUpdateSelector, lookupTargetVersion, reportUpdateRestart, shouldSkipUpdate, } from "./update-check.js";
|
|
19
|
+
import { clearUpdateMarker, markUpdateInProgress } from "./update-marker.js";
|
|
17
20
|
/** Prefix tmux args with -L when socket isolation is active. */
|
|
18
21
|
function tmuxArgs(args) {
|
|
19
22
|
const socket = getTmuxSocketName();
|
|
@@ -67,8 +70,14 @@ fleet
|
|
|
67
70
|
const fleet = loadFleetConfig(FLEET_CONFIG_PATH);
|
|
68
71
|
const port = fleet.health_port ?? 19280;
|
|
69
72
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
// Any HTTP answer means a fleet daemon is listening — including a 503,
|
|
74
|
+
// which /health now returns when the fleet is degraded (no adapter
|
|
75
|
+
// connected, an instance crashed). Checking `.ok` here would treat a
|
|
76
|
+
// degraded fleet as "not running" and start a SECOND fleet on the same
|
|
77
|
+
// data dir, whose startup cleanup kills the first one's tmux windows.
|
|
78
|
+
// Only a connection error means nothing is there (the catch below).
|
|
79
|
+
await fetch(`http://127.0.0.1:${port}/health`);
|
|
80
|
+
{
|
|
72
81
|
try {
|
|
73
82
|
const resp = await fetch(`http://127.0.0.1:${port}/api/instance/${encodeURIComponent(instance)}/start`, { method: "POST" });
|
|
74
83
|
const body = await resp.json();
|
|
@@ -91,6 +100,59 @@ fleet
|
|
|
91
100
|
}
|
|
92
101
|
const { FleetManager } = await import("./fleet-manager.js");
|
|
93
102
|
const fm = new FleetManager(DATA_DIR);
|
|
103
|
+
// Register crash/signal handlers BEFORE startAll(). Startup routinely takes
|
|
104
|
+
// minutes (sequential general spawn, staggered instances, warmup waits), and
|
|
105
|
+
// without these a Ctrl-C or `systemctl stop` in that window got Node's default
|
|
106
|
+
// behaviour: instant death with fleet.pid already written, databases unclosed,
|
|
107
|
+
// and every tmux window orphaned.
|
|
108
|
+
let stopping = false;
|
|
109
|
+
const shutdown = async () => {
|
|
110
|
+
if (stopping)
|
|
111
|
+
return; // SIGINT and SIGTERM share this, and crash paths call stopAll too
|
|
112
|
+
stopping = true;
|
|
113
|
+
console.log("\nStopping fleet...");
|
|
114
|
+
await fm.stopAll().catch(err => console.error("Shutdown error:", err));
|
|
115
|
+
process.exit(0);
|
|
116
|
+
};
|
|
117
|
+
process.on("SIGINT", shutdown);
|
|
118
|
+
process.on("SIGTERM", shutdown);
|
|
119
|
+
process.on("uncaughtException", (err) => {
|
|
120
|
+
// An uncaught exception leaves unknown state — still the one case worth
|
|
121
|
+
// tearing down for.
|
|
122
|
+
console.error("Uncaught exception:", err);
|
|
123
|
+
if (stopping)
|
|
124
|
+
return;
|
|
125
|
+
stopping = true;
|
|
126
|
+
fm.stopAll().catch(() => { }).finally(() => process.exit(1));
|
|
127
|
+
});
|
|
128
|
+
process.on("unhandledRejection", (err) => {
|
|
129
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
130
|
+
// 409 = another bot poller exists — the adapter retries, don't crash.
|
|
131
|
+
if (msg.includes("409") && msg.includes("getUpdates")) {
|
|
132
|
+
console.error("Bot polling conflict (409) — retrying...");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Do NOT take the fleet down. A rejected promise in one instance's tmux
|
|
136
|
+
// call, health tick, or model switch used to stop every other working
|
|
137
|
+
// instance and exit(1) — the widest blast radius in the process. The
|
|
138
|
+
// per-instance health check and crash recovery handle a genuinely broken
|
|
139
|
+
// instance on their own.
|
|
140
|
+
//
|
|
141
|
+
// Surviving quietly would be its own failure mode, so this reports to the
|
|
142
|
+
// General topic as well as the log: the operator must not have to read
|
|
143
|
+
// daemon.log to learn the fleet swallowed a fault. notifyFleetError()
|
|
144
|
+
// throttles repeats, since these usually arrive from a loop.
|
|
145
|
+
console.error("Unhandled rejection (fleet continues):", err);
|
|
146
|
+
if (err instanceof Error && err.stack)
|
|
147
|
+
console.error(err.stack);
|
|
148
|
+
try {
|
|
149
|
+
fm.notifyFleetError(`⚠️ Fleet caught an unhandled error and kept running:\n\`${msg}\`\nSee daemon.log for the stack trace.`);
|
|
150
|
+
}
|
|
151
|
+
catch (notifyErr) {
|
|
152
|
+
// Never let the reporting path itself become an unhandled rejection.
|
|
153
|
+
console.error("Failed to report unhandled rejection:", notifyErr);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
94
156
|
if (instance) {
|
|
95
157
|
const config = fm.loadConfig(FLEET_CONFIG_PATH);
|
|
96
158
|
const inst = config.instances[instance];
|
|
@@ -108,30 +170,6 @@ fleet
|
|
|
108
170
|
await fm.startAll(FLEET_CONFIG_PATH);
|
|
109
171
|
}
|
|
110
172
|
console.log("Fleet started");
|
|
111
|
-
// Keep process alive + clean shutdown on Ctrl+C
|
|
112
|
-
const shutdown = async () => {
|
|
113
|
-
console.log("\nStopping fleet...");
|
|
114
|
-
await fm.stopAll();
|
|
115
|
-
process.exit(0);
|
|
116
|
-
};
|
|
117
|
-
process.on("SIGINT", shutdown);
|
|
118
|
-
process.on("SIGTERM", shutdown);
|
|
119
|
-
process.on("uncaughtException", async (err) => {
|
|
120
|
-
console.error("Uncaught exception:", err);
|
|
121
|
-
await fm.stopAll().catch(() => { });
|
|
122
|
-
process.exit(1);
|
|
123
|
-
});
|
|
124
|
-
process.on("unhandledRejection", async (err) => {
|
|
125
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
-
// 409 = another bot poller exists — adapter handles retry, don't crash
|
|
127
|
-
if (msg.includes("409") && msg.includes("getUpdates")) {
|
|
128
|
-
console.error("Bot polling conflict (409) — retrying...");
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
console.error("Unhandled rejection:", err);
|
|
132
|
-
await fm.stopAll().catch(() => { });
|
|
133
|
-
process.exit(1);
|
|
134
|
-
});
|
|
135
173
|
});
|
|
136
174
|
fleet
|
|
137
175
|
.command("stop")
|
|
@@ -1105,6 +1143,18 @@ program
|
|
|
1105
1143
|
else {
|
|
1106
1144
|
console.log(`\n Updating AgEnD to ${tag}...\n`);
|
|
1107
1145
|
}
|
|
1146
|
+
// Tell the running fleet that what happens next is planned. `npm install -g`
|
|
1147
|
+
// replaces the package directory underneath the live daemon — MCP servers
|
|
1148
|
+
// die, then the restart stops every instance — and without this marker the
|
|
1149
|
+
// old daemon reports each one as a crash. The new fleet deletes the marker
|
|
1150
|
+
// once it finishes starting; it also expires on its own, so an update that
|
|
1151
|
+
// dies here does not silence real alerts.
|
|
1152
|
+
markUpdateInProgress(DATA_DIR);
|
|
1153
|
+
const failUpdate = (message) => {
|
|
1154
|
+
clearUpdateMarker(DATA_DIR);
|
|
1155
|
+
console.error(message);
|
|
1156
|
+
process.exit(1);
|
|
1157
|
+
};
|
|
1108
1158
|
// Detect and remove stale npm link (local build that shadows global install)
|
|
1109
1159
|
try {
|
|
1110
1160
|
const agendPath = execSync("which agend", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
@@ -1146,8 +1196,7 @@ program
|
|
|
1146
1196
|
execSync("curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash", { stdio: "inherit" });
|
|
1147
1197
|
}
|
|
1148
1198
|
catch {
|
|
1149
|
-
|
|
1150
|
-
process.exit(1);
|
|
1199
|
+
failUpdate(" Failed to install nvm. Install manually: https://github.com/nvm-sh/nvm");
|
|
1151
1200
|
}
|
|
1152
1201
|
}
|
|
1153
1202
|
console.log(" Using nvm to install Node 22...");
|
|
@@ -1156,8 +1205,7 @@ program
|
|
|
1156
1205
|
execSync(`bash -c '${nvmPrefix} && npm install -g ${pkg}'`, { stdio: "inherit" });
|
|
1157
1206
|
}
|
|
1158
1207
|
catch {
|
|
1159
|
-
|
|
1160
|
-
process.exit(1);
|
|
1208
|
+
failUpdate(" Failed to install via nvm.");
|
|
1161
1209
|
}
|
|
1162
1210
|
// Try to remove old system binary
|
|
1163
1211
|
console.log(" Note: removing old system install (may require sudo)...");
|
|
@@ -1169,8 +1217,7 @@ program
|
|
|
1169
1217
|
execSync(`npm install -g ${pkg}`, { stdio: "inherit" });
|
|
1170
1218
|
}
|
|
1171
1219
|
catch {
|
|
1172
|
-
|
|
1173
|
-
process.exit(1);
|
|
1220
|
+
failUpdate(` Failed to update. Try: npm install -g ${pkg}`);
|
|
1174
1221
|
}
|
|
1175
1222
|
}
|
|
1176
1223
|
// ── Verify installation ──
|
|
@@ -1180,15 +1227,13 @@ program
|
|
|
1180
1227
|
? spawnSync("bash", ["-c", `source ${nvmSh2} && nvm use 22 > /dev/null 2>&1 && which agend`], { encoding: "utf-8" }).stdout?.trim()
|
|
1181
1228
|
: spawnSync("which", ["agend"], { encoding: "utf-8" }).stdout?.trim();
|
|
1182
1229
|
if (!agendPath) {
|
|
1183
|
-
console.error(" ✗ Verification failed: agend not found in PATH after install.");
|
|
1184
1230
|
if (needsSudo)
|
|
1185
1231
|
console.error(" You may need to add nvm to your shell profile and restart.");
|
|
1186
|
-
|
|
1232
|
+
failUpdate(" ✗ Verification failed: agend not found in PATH after install.");
|
|
1187
1233
|
}
|
|
1188
1234
|
const verifyResult = spawnSync(agendPath, ["--version"], { encoding: "utf-8", timeout: 5000 });
|
|
1189
1235
|
if (verifyResult.status !== 0) {
|
|
1190
|
-
|
|
1191
|
-
process.exit(1);
|
|
1236
|
+
failUpdate(" ✗ Verification failed: agend --version returned error.");
|
|
1192
1237
|
}
|
|
1193
1238
|
const newVersion = (verifyResult.stdout ?? "").trim();
|
|
1194
1239
|
console.log(` ✓ Installed: ${newVersion}`);
|
|
@@ -1216,8 +1261,12 @@ program
|
|
|
1216
1261
|
// systemd → user systemd → launchd → detached pid).
|
|
1217
1262
|
console.log(" Restarting fleet...");
|
|
1218
1263
|
const restartResult = spawnSync(agendPath, ["restart"], { encoding: "utf-8", timeout: 30000, stdio: "inherit" });
|
|
1219
|
-
if (!reportUpdateRestart(restartResult.status))
|
|
1264
|
+
if (!reportUpdateRestart(restartResult.status)) {
|
|
1265
|
+
// No new fleet is coming up to clear the marker — do it here, or the next
|
|
1266
|
+
// 15 minutes of genuine crashes would go unreported.
|
|
1267
|
+
clearUpdateMarker(DATA_DIR);
|
|
1220
1268
|
process.exitCode = 1;
|
|
1269
|
+
}
|
|
1221
1270
|
});
|
|
1222
1271
|
program
|
|
1223
1272
|
.command("reload")
|
|
@@ -1834,28 +1883,7 @@ async function fuzzyMatch(query, names) {
|
|
|
1834
1883
|
return null;
|
|
1835
1884
|
}
|
|
1836
1885
|
async function resolveInstance(query, config) {
|
|
1837
|
-
const names =
|
|
1838
|
-
// Include classic instances from classicBot.yaml
|
|
1839
|
-
try {
|
|
1840
|
-
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
1841
|
-
if (existsSync(classicPath)) {
|
|
1842
|
-
const yamlMod = (await import("js-yaml")).default;
|
|
1843
|
-
const classic = yamlMod.load(readFileSync(classicPath, "utf-8"));
|
|
1844
|
-
if (classic?.channels) {
|
|
1845
|
-
for (const [key, val] of Object.entries(classic.channels)) {
|
|
1846
|
-
// New format persists instanceName; old format keyed by channelId.
|
|
1847
|
-
if (val.instanceName) {
|
|
1848
|
-
names.push(val.instanceName);
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
const channelId = val.channelId ?? key;
|
|
1852
|
-
const chName = (val.name ?? channelId).toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "project";
|
|
1853
|
-
names.push(`classic-${chName}-${channelId.slice(-4)}`);
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
catch { /* ignore */ }
|
|
1886
|
+
const names = await listInstanceNames(config);
|
|
1859
1887
|
const match = await fuzzyMatch(query, names);
|
|
1860
1888
|
if (!match) {
|
|
1861
1889
|
console.error(`No instance matching "${query}". Available: ${names.join(", ")}`);
|
|
@@ -1950,7 +1978,7 @@ function formatTimeSince(isoStr) {
|
|
|
1950
1978
|
* Universal context-% parser for `agend ls` (all backends). Kept in sync with
|
|
1951
1979
|
* parseContextPercent() in topic-commands.ts — same formats, bottom-up so the
|
|
1952
1980
|
* most recent prompt wins:
|
|
1953
|
-
* kiro classic "8% !>" / TUI "
|
|
1981
|
+
* kiro classic "8% !>" / TUI "◑ 1%" (any pie glyph) / bracket "[8%]" / prompt "8% ❯"
|
|
1954
1982
|
* codex "Context N% left" (remaining → 100-N) or "Context N% used"
|
|
1955
1983
|
* opencode "1.2K (6%)"
|
|
1956
1984
|
* grok "12K / 500K" (used tokens / context window)
|
|
@@ -1977,7 +2005,7 @@ const defaultParser = (output) => {
|
|
|
1977
2005
|
return used / total * 100;
|
|
1978
2006
|
}
|
|
1979
2007
|
const m = line.match(/(\d+)%.*[!❯>]/)
|
|
1980
|
-
|| line.match(
|
|
2008
|
+
|| line.match(PIE_PERCENT_RE)
|
|
1981
2009
|
|| line.match(/\[(\d+)%\]/)
|
|
1982
2010
|
|| line.match(/Context\s+(\d+)%\s+used/i)
|
|
1983
2011
|
|| line.match(/\d+(?:\.\d+)?[KM]?\s*\((\d+)%\)/);
|
|
@@ -1988,10 +2016,48 @@ const defaultParser = (output) => {
|
|
|
1988
2016
|
};
|
|
1989
2017
|
/** Optional backend-specific overrides; none needed — defaultParser covers all. */
|
|
1990
2018
|
const contextParsers = {};
|
|
2019
|
+
/**
|
|
2020
|
+
* Every instance name the CLI accepts: fleet.yaml instances plus the
|
|
2021
|
+
* dynamically-created ClassicBot ones. Shared by `resolveInstance` (fuzzy
|
|
2022
|
+
* matching for `attach`) and `ls --names-only` (shell completion), so the
|
|
2023
|
+
* candidates offered by tab-completion are exactly the ones `attach` resolves.
|
|
2024
|
+
*/
|
|
2025
|
+
async function listInstanceNames(config) {
|
|
2026
|
+
const names = Object.keys(config.instances);
|
|
2027
|
+
try {
|
|
2028
|
+
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
2029
|
+
if (existsSync(classicPath)) {
|
|
2030
|
+
const yamlMod = (await import("js-yaml")).default;
|
|
2031
|
+
const classic = yamlMod.load(readFileSync(classicPath, "utf-8"));
|
|
2032
|
+
if (classic?.channels) {
|
|
2033
|
+
for (const [key, val] of Object.entries(classic.channels)) {
|
|
2034
|
+
// New format persists instanceName; old format keyed by channelId.
|
|
2035
|
+
if (val.instanceName) {
|
|
2036
|
+
names.push(val.instanceName);
|
|
2037
|
+
continue;
|
|
2038
|
+
}
|
|
2039
|
+
const channelId = val.channelId ?? key;
|
|
2040
|
+
const chName = (val.name ?? channelId).toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "project";
|
|
2041
|
+
names.push(`classic-${chName}-${channelId.slice(-4)}`);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
catch { /* ignore */ }
|
|
2047
|
+
return names;
|
|
2048
|
+
}
|
|
1991
2049
|
async function lsAction(opts) {
|
|
1992
2050
|
const yaml = (await import("js-yaml")).default;
|
|
1993
2051
|
const config = yaml.load(readFileSync(FLEET_CONFIG_PATH, "utf-8"));
|
|
1994
2052
|
const names = Object.keys(config.instances);
|
|
2053
|
+
// --names-only feeds shell completion, so it must return before any tmux or
|
|
2054
|
+
// status work (a tab press should not wait on the fleet) and must print
|
|
2055
|
+
// nothing but names — a "no instances" notice would become a candidate.
|
|
2056
|
+
if (opts.namesOnly) {
|
|
2057
|
+
for (const n of await listInstanceNames(config))
|
|
2058
|
+
console.log(n);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
1995
2061
|
// Load classic channels from classicBot.yaml (keyed by channelId)
|
|
1996
2062
|
const classicPath = join(DATA_DIR, "classicBot.yaml");
|
|
1997
2063
|
let classicConfig = null;
|
|
@@ -2277,12 +2343,38 @@ program
|
|
|
2277
2343
|
.command("ls")
|
|
2278
2344
|
.description("List all instances with status, backend, team, and last activity")
|
|
2279
2345
|
.option("--json", "Output as JSON")
|
|
2346
|
+
.option("--names-only", "Print instance names one per line (for shell completion)")
|
|
2280
2347
|
.action(async (opts) => {
|
|
2281
2348
|
await lsAction(opts);
|
|
2282
2349
|
});
|
|
2283
2350
|
program
|
|
2284
|
-
.command("
|
|
2285
|
-
.description("
|
|
2351
|
+
.command("completion")
|
|
2352
|
+
.description("Print a shell completion script (bash or zsh)")
|
|
2353
|
+
.argument("<shell>", `Shell to generate for (${COMPLETION_SHELLS.join(", ")})`)
|
|
2354
|
+
.addHelpText("after", `
|
|
2355
|
+
Install:
|
|
2356
|
+
bash echo 'eval "$(agend completion bash)"' >> ~/.bashrc
|
|
2357
|
+
zsh echo 'eval "$(agend completion zsh)"' >> ~/.zshrc
|
|
2358
|
+
|
|
2359
|
+
Then reload the shell. Tab-completes instance names for \`agend attach\` and
|
|
2360
|
+
\`agend fleet start|stop|restart\`, and subcommands elsewhere.`)
|
|
2361
|
+
.action((shell) => {
|
|
2362
|
+
if (!COMPLETION_SHELLS.includes(shell)) {
|
|
2363
|
+
console.error(`Unsupported shell: ${shell}. Supported: ${COMPLETION_SHELLS.join(", ")}`);
|
|
2364
|
+
process.exit(2);
|
|
2365
|
+
}
|
|
2366
|
+
// Subcommand names come from commander itself, so the script can't drift
|
|
2367
|
+
// out of sync with the CLI the way a hardcoded list would.
|
|
2368
|
+
const fleetCmd = program.commands.find(c => c.name() === "fleet");
|
|
2369
|
+
console.log(completionScript(shell, {
|
|
2370
|
+
topLevel: program.commands.map(c => c.name()),
|
|
2371
|
+
fleetSub: fleetCmd?.commands.map(c => c.name()) ?? [],
|
|
2372
|
+
instanceCommands: ["attach"],
|
|
2373
|
+
fleetInstanceCommands: ["start", "stop", "restart"],
|
|
2374
|
+
}));
|
|
2375
|
+
});
|
|
2376
|
+
program
|
|
2377
|
+
.command("health").description("Fleet health check — shows problems and diagnostics")
|
|
2286
2378
|
.option("--json", "Output as JSON")
|
|
2287
2379
|
.option("-q, --quiet", "One-line summary only")
|
|
2288
2380
|
.action(async (opts) => {
|