@withtruing/companion 0.13.1 → 0.13.2
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/README.md +4 -0
- package/dist/cli.js +106 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,10 @@ workspace-task settings; one companion process polls all of them independently.
|
|
|
133
133
|
local development Console therefore does not replace or move projects already connected to the
|
|
134
134
|
hosted Console.
|
|
135
135
|
|
|
136
|
+
If a saved backend is unavailable, `run` prints the exact setup fix and skips only the affected
|
|
137
|
+
Console, project, or workspace-task poll. Other runnable pairings keep working; `run` exits with
|
|
138
|
+
an error only when no configured work remains runnable.
|
|
139
|
+
|
|
136
140
|
To remove one Console and all of its projects from this computer:
|
|
137
141
|
|
|
138
142
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -404,6 +404,23 @@ export function backendRequirements(backends) {
|
|
|
404
404
|
needsCli: backends.some((backend) => backend === 'cli'),
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
+
export function partitionPairingsByAvailability(pairings, availability) {
|
|
408
|
+
const partitions = pairings.map((pairing) => {
|
|
409
|
+
const unavailableConnections = new Set(pairing.connections
|
|
410
|
+
.filter((connection) => !availability[connection.backend])
|
|
411
|
+
.map((connection) => connection.connectionId));
|
|
412
|
+
const workspaceTasksUnavailable = Boolean(pairing.pollWorkspaceTasks && !availability[pairing.workspaceBackend ?? 'api-key']);
|
|
413
|
+
return { pairing, unavailableConnections, workspaceTasksUnavailable };
|
|
414
|
+
});
|
|
415
|
+
const isRunnable = ({ pairing }) => pairing.connections.some((connection) => connection.backend === 'codex' || availability[connection.backend])
|
|
416
|
+
|| Boolean(pairing.pollWorkspaceTasks
|
|
417
|
+
&& (pairing.workspaceBackend === 'codex' || availability[pairing.workspaceBackend ?? 'api-key']));
|
|
418
|
+
return {
|
|
419
|
+
pairings: partitions,
|
|
420
|
+
runnablePairings: partitions.filter(isRunnable),
|
|
421
|
+
unavailablePairings: partitions.filter((partition) => !isRunnable(partition)),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
407
424
|
export function createBackendRuntime(config, anthropic, createCodexTextModel = codexTextModel) {
|
|
408
425
|
let usage = null;
|
|
409
426
|
const codexOptions = config.backend === 'codex'
|
|
@@ -886,7 +903,7 @@ async function runPairing(pairing, options) {
|
|
|
886
903
|
connection.connectionId,
|
|
887
904
|
createBackendRuntime(connection, options.anthropic),
|
|
888
905
|
]));
|
|
889
|
-
const workspaceRuntime = pairing.pollWorkspaceTasks &&
|
|
906
|
+
const workspaceRuntime = pairing.pollWorkspaceTasks && !options.workspaceTasksUnavailable
|
|
890
907
|
? createBackendRuntime({ backend: workspaceBackend, codexModel: pairing.codexModel }, options.anthropic)
|
|
891
908
|
: null;
|
|
892
909
|
const repositoryReadiness = new Map();
|
|
@@ -897,7 +914,7 @@ async function runPairing(pairing, options) {
|
|
|
897
914
|
});
|
|
898
915
|
}
|
|
899
916
|
if (activeConnections.length !== pairing.connections.length && activeConnections.length > 0) {
|
|
900
|
-
error('Skipping investigation polling while a configured
|
|
917
|
+
error('Skipping investigation polling while a configured connection backend is unavailable, so the companion cannot claim an investigation it cannot run.');
|
|
901
918
|
}
|
|
902
919
|
const projectCount = activeConnections.length;
|
|
903
920
|
const watching = workspaceRuntime
|
|
@@ -1116,53 +1133,106 @@ async function cmdRun(options = {}) {
|
|
|
1116
1133
|
process.exitCode = 1;
|
|
1117
1134
|
return;
|
|
1118
1135
|
}
|
|
1119
|
-
const
|
|
1120
|
-
if (!config ||
|
|
1136
|
+
const configuredPairings = config?.pairings.filter((pairing) => pairing.connections.length > 0 || pairing.pollWorkspaceTasks) ?? [];
|
|
1137
|
+
if (!config || configuredPairings.length === 0) {
|
|
1121
1138
|
console.error(options.safeStartupDiagnostics
|
|
1122
1139
|
? 'Nothing is set up yet. Run connect first.'
|
|
1123
1140
|
: `Nothing set up yet — ${configPath} is missing or empty. Run "connect" first.`);
|
|
1124
1141
|
process.exitCode = 1;
|
|
1125
1142
|
return;
|
|
1126
1143
|
}
|
|
1127
|
-
const allBackends =
|
|
1144
|
+
const allBackends = configuredPairings.flatMap((pairing) => [
|
|
1128
1145
|
...pairing.connections.map((connection) => connection.backend),
|
|
1129
1146
|
...(pairing.pollWorkspaceTasks ? [pairing.workspaceBackend ?? 'api-key'] : []),
|
|
1130
1147
|
]);
|
|
1131
1148
|
const { needsApiKey, needsCli } = backendRequirements(allBackends);
|
|
1132
1149
|
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (needsCli && !await checkClaudeCliAvailable()) {
|
|
1141
|
-
console.error('This needs Claude Code installed and you logged into it first: npm install -g @anthropic-ai/claude-code, then run `claude` and follow the login prompt.');
|
|
1142
|
-
process.exitCode = 1;
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
const unavailableConnections = new Set();
|
|
1150
|
+
const apiKeyMissingMessage = 'No Anthropic API key found. Get one free at console.anthropic.com, then either run '
|
|
1151
|
+
+ '"connect" again adding --api-key <your key>, or set it yourself: '
|
|
1152
|
+
+ '"set ANTHROPIC_API_KEY=<your key>" (Windows) / "export ANTHROPIC_API_KEY=<your key>" (Mac/Linux), then run "run" again.';
|
|
1153
|
+
const cliMissingMessage = 'This needs Claude Code installed and you logged into it first: npm install -g @anthropic-ai/claude-code, then run `claude` and follow the login prompt.';
|
|
1154
|
+
const cliAvailable = !needsCli || Boolean(await checkClaudeCliAvailable());
|
|
1146
1155
|
let codexAvailable = true;
|
|
1156
|
+
let codexStatus = null;
|
|
1147
1157
|
if (allBackends.includes('codex')) {
|
|
1148
1158
|
const status = await checkCodexLogin(defaultCodexHome());
|
|
1149
1159
|
codexAvailable = status === 'logged-in';
|
|
1150
|
-
if (!codexAvailable)
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1160
|
+
if (!codexAvailable)
|
|
1161
|
+
codexStatus = status;
|
|
1162
|
+
}
|
|
1163
|
+
const partitioned = partitionPairingsByAvailability(configuredPairings, {
|
|
1164
|
+
'api-key': !needsApiKey || Boolean(apiKey),
|
|
1165
|
+
cli: cliAvailable,
|
|
1166
|
+
codex: codexAvailable,
|
|
1167
|
+
});
|
|
1168
|
+
const distinctBackends = new Set(allBackends);
|
|
1169
|
+
if (partitioned.runnablePairings.length === 0 && configuredPairings.length === 1 && distinctBackends.size === 1) {
|
|
1170
|
+
const [backend] = distinctBackends;
|
|
1171
|
+
if (backend === 'api-key')
|
|
1172
|
+
console.error(apiKeyMissingMessage);
|
|
1173
|
+
if (backend === 'cli')
|
|
1174
|
+
console.error(cliMissingMessage);
|
|
1175
|
+
if (backend === 'codex' && codexStatus) {
|
|
1176
|
+
logCodexReadiness(codexStatus);
|
|
1177
|
+
for (const connection of configuredPairings[0].connections) {
|
|
1178
|
+
console.error(`Skipping ${connection.connectionId}: Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed for this connection.`);
|
|
1162
1179
|
}
|
|
1180
|
+
if (configuredPairings[0].pollWorkspaceTasks) {
|
|
1181
|
+
console.error(`Skipping workspace-level tasks for ${configuredPairings[0].url}: Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed.`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
process.exitCode = 1;
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (codexStatus)
|
|
1188
|
+
logCodexReadiness(codexStatus);
|
|
1189
|
+
const backendUnavailableMessage = (backend) => {
|
|
1190
|
+
if (backend === 'api-key')
|
|
1191
|
+
return apiKeyMissingMessage;
|
|
1192
|
+
if (backend === 'cli')
|
|
1193
|
+
return cliMissingMessage;
|
|
1194
|
+
return `Codex is ${codexStatus === 'not-installed' ? 'not installed' : 'not logged in'}; no work will be claimed.`;
|
|
1195
|
+
};
|
|
1196
|
+
const fullyUnavailablePairings = new Set(partitioned.unavailablePairings);
|
|
1197
|
+
for (const partition of partitioned.pairings) {
|
|
1198
|
+
const { pairing, unavailableConnections, workspaceTasksUnavailable } = partition;
|
|
1199
|
+
const pairingLabel = `${pairing.url}${pairing.workspaceId ? ` (workspace ${pairing.workspaceId})` : ''}`;
|
|
1200
|
+
if (fullyUnavailablePairings.has(partition)) {
|
|
1201
|
+
const unavailableBackends = new Set(pairing.connections.map((connection) => connection.backend));
|
|
1202
|
+
if (pairing.pollWorkspaceTasks)
|
|
1203
|
+
unavailableBackends.add(pairing.workspaceBackend ?? 'api-key');
|
|
1204
|
+
for (const backend of unavailableBackends) {
|
|
1205
|
+
console.error(`Skipping ${pairingLabel}: ${backendUnavailableMessage(backend)}`);
|
|
1206
|
+
}
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
for (const connection of pairing.connections) {
|
|
1210
|
+
if (!unavailableConnections.has(connection.connectionId))
|
|
1211
|
+
continue;
|
|
1212
|
+
console.error(connection.backend === 'codex'
|
|
1213
|
+
? `Skipping ${connection.connectionId}: ${backendUnavailableMessage(connection.backend)}`
|
|
1214
|
+
: `Skipping ${connection.connectionId} for ${pairingLabel}: ${backendUnavailableMessage(connection.backend)}`);
|
|
1215
|
+
}
|
|
1216
|
+
if (workspaceTasksUnavailable) {
|
|
1217
|
+
const workspaceBackend = pairing.workspaceBackend ?? 'api-key';
|
|
1218
|
+
console.error(workspaceBackend === 'codex'
|
|
1219
|
+
? `Skipping workspace-level tasks for ${pairing.url}: ${backendUnavailableMessage(workspaceBackend)}`
|
|
1220
|
+
: `Skipping workspace-level tasks for ${pairingLabel}: ${backendUnavailableMessage(workspaceBackend)}`);
|
|
1163
1221
|
}
|
|
1164
1222
|
}
|
|
1165
|
-
|
|
1223
|
+
if (partitioned.runnablePairings.length === 0) {
|
|
1224
|
+
process.exitCode = 1;
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const remainingBackends = partitioned.runnablePairings.flatMap(({ pairing, unavailableConnections, workspaceTasksUnavailable }) => [
|
|
1228
|
+
...pairing.connections
|
|
1229
|
+
.filter((connection) => !unavailableConnections.has(connection.connectionId))
|
|
1230
|
+
.map((connection) => connection.backend),
|
|
1231
|
+
...(pairing.pollWorkspaceTasks && !workspaceTasksUnavailable
|
|
1232
|
+
? [pairing.workspaceBackend ?? 'api-key']
|
|
1233
|
+
: []),
|
|
1234
|
+
]);
|
|
1235
|
+
const anthropic = backendRequirements(remainingBackends).needsApiKey ? new Anthropic({ apiKey }) : null;
|
|
1166
1236
|
const usageLimitPause = new UsageLimitPause();
|
|
1167
1237
|
let stopping = false;
|
|
1168
1238
|
process.on('SIGINT', () => {
|
|
@@ -1174,21 +1244,21 @@ async function cmdRun(options = {}) {
|
|
|
1174
1244
|
expectedExitReason = 'stopped by Ctrl+C';
|
|
1175
1245
|
stopping = true;
|
|
1176
1246
|
});
|
|
1177
|
-
let activePairings = runnablePairings.length;
|
|
1178
|
-
await Promise.all(runnablePairings.map(async (pairing) => {
|
|
1247
|
+
let activePairings = partitioned.runnablePairings.length;
|
|
1248
|
+
await Promise.all(partitioned.runnablePairings.map(async ({ pairing, unavailableConnections, workspaceTasksUnavailable }) => {
|
|
1179
1249
|
try {
|
|
1180
1250
|
return await runPairing(pairing, {
|
|
1181
1251
|
...options,
|
|
1182
|
-
multiple: runnablePairings.length > 1,
|
|
1252
|
+
multiple: partitioned.runnablePairings.length > 1,
|
|
1183
1253
|
anthropic,
|
|
1184
1254
|
unavailableConnections,
|
|
1185
|
-
|
|
1255
|
+
workspaceTasksUnavailable,
|
|
1186
1256
|
usageLimitPause,
|
|
1187
1257
|
shouldStop: () => stopping,
|
|
1188
1258
|
});
|
|
1189
1259
|
}
|
|
1190
1260
|
catch (reason) {
|
|
1191
|
-
if (runnablePairings.length === 1)
|
|
1261
|
+
if (partitioned.runnablePairings.length === 1)
|
|
1192
1262
|
throw reason;
|
|
1193
1263
|
console.error(`${pairingPrefix(pairing)} The Console at ${pairing.url} stopped because of an unexpected error: ${reason instanceof Error ? reason.message : reason}`);
|
|
1194
1264
|
return 'stopped';
|