@hua-labs/tap 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -194
- package/dist/bridges/codex-app-server-bridge.d.mts +10 -1
- package/dist/bridges/codex-app-server-bridge.mjs +93 -37
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +2 -0
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +598 -109
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +275 -66
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +218 -199
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +65 -65
- /package/bin/{tap-comms.mjs → tap.mjs} +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -775,10 +775,10 @@ function parsePermissionMode(args) {
|
|
|
775
775
|
}
|
|
776
776
|
var INIT_HELP = `
|
|
777
777
|
Usage:
|
|
778
|
-
tap
|
|
778
|
+
tap init [options]
|
|
779
779
|
|
|
780
780
|
Description:
|
|
781
|
-
Initialize the tap
|
|
781
|
+
Initialize the tap directory structure, state file, and permissions.
|
|
782
782
|
Optionally clone a shared comms repository.
|
|
783
783
|
|
|
784
784
|
Options:
|
|
@@ -1077,7 +1077,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
if (!sourcePath) {
|
|
1079
1079
|
issues.push(
|
|
1080
|
-
"tap
|
|
1080
|
+
"tap MCP server entry not found. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
|
|
1081
1081
|
);
|
|
1082
1082
|
return { command: null, args: [], env, sourcePath, warnings, issues };
|
|
1083
1083
|
}
|
|
@@ -1107,7 +1107,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
if (!command) {
|
|
1109
1109
|
issues.push(
|
|
1110
|
-
"bun is required to run the repo-local tap
|
|
1110
|
+
"bun is required to run the repo-local tap MCP server (.ts source). Install bun: https://bun.sh"
|
|
1111
1111
|
);
|
|
1112
1112
|
return { command: null, args: [], env, sourcePath, warnings, issues };
|
|
1113
1113
|
}
|
|
@@ -1122,7 +1122,8 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
1124
|
// src/adapters/claude.ts
|
|
1125
|
-
var MCP_SERVER_KEY = "tap
|
|
1125
|
+
var MCP_SERVER_KEY = "tap";
|
|
1126
|
+
var OLD_MCP_SERVER_KEY = "tap-comms";
|
|
1126
1127
|
function findMcpJsonPath(ctx) {
|
|
1127
1128
|
return path8.join(ctx.repoRoot, ".mcp.json");
|
|
1128
1129
|
}
|
|
@@ -1200,6 +1201,11 @@ var claudeAdapter = {
|
|
|
1200
1201
|
`Existing "${MCP_SERVER_KEY}" entry in .mcp.json will be overwritten.`
|
|
1201
1202
|
);
|
|
1202
1203
|
}
|
|
1204
|
+
if (config.mcpServers?.[OLD_MCP_SERVER_KEY]) {
|
|
1205
|
+
conflicts.push(
|
|
1206
|
+
`Legacy "${OLD_MCP_SERVER_KEY}" entry will be migrated to "${MCP_SERVER_KEY}".`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1203
1209
|
} catch {
|
|
1204
1210
|
warnings.push(
|
|
1205
1211
|
".mcp.json exists but is not valid JSON. Will be overwritten."
|
|
@@ -1209,7 +1215,7 @@ var claudeAdapter = {
|
|
|
1209
1215
|
const serverEntry = buildMcpServerEntry(ctx);
|
|
1210
1216
|
if (!serverEntry) {
|
|
1211
1217
|
warnings.push(
|
|
1212
|
-
"tap
|
|
1218
|
+
"tap MCP server entry not found. Skipping .mcp.json patch. Reinstall @hua-labs/tap or run from a repo with packages/tap-plugin/channels/ available."
|
|
1213
1219
|
);
|
|
1214
1220
|
return {
|
|
1215
1221
|
runtime: "claude",
|
|
@@ -1262,6 +1268,10 @@ var claudeAdapter = {
|
|
|
1262
1268
|
);
|
|
1263
1269
|
}
|
|
1264
1270
|
}
|
|
1271
|
+
const servers = config.mcpServers;
|
|
1272
|
+
if (servers?.[OLD_MCP_SERVER_KEY]) {
|
|
1273
|
+
delete servers[OLD_MCP_SERVER_KEY];
|
|
1274
|
+
}
|
|
1265
1275
|
if (op.key) {
|
|
1266
1276
|
setNestedKey(config, op.key, op.value);
|
|
1267
1277
|
}
|
|
@@ -1310,7 +1320,7 @@ var claudeAdapter = {
|
|
|
1310
1320
|
checks.push({ name: "Config is valid JSON", passed: true });
|
|
1311
1321
|
const entry = config.mcpServers?.[MCP_SERVER_KEY];
|
|
1312
1322
|
checks.push({
|
|
1313
|
-
name: "tap
|
|
1323
|
+
name: "tap entry present",
|
|
1314
1324
|
passed: !!entry,
|
|
1315
1325
|
message: entry ? void 0 : `mcpServers.${MCP_SERVER_KEY} not found`
|
|
1316
1326
|
});
|
|
@@ -1403,8 +1413,10 @@ function readArtifactBackup(backupPath) {
|
|
|
1403
1413
|
}
|
|
1404
1414
|
|
|
1405
1415
|
// src/adapters/codex.ts
|
|
1406
|
-
var MCP_SELECTOR = "mcp_servers.tap
|
|
1407
|
-
var ENV_SELECTOR = "mcp_servers.tap
|
|
1416
|
+
var MCP_SELECTOR = "mcp_servers.tap";
|
|
1417
|
+
var ENV_SELECTOR = "mcp_servers.tap.env";
|
|
1418
|
+
var OLD_MCP_SELECTOR = "mcp_servers.tap-comms";
|
|
1419
|
+
var OLD_ENV_SELECTOR = "mcp_servers.tap-comms.env";
|
|
1408
1420
|
function findCodexConfigPath2() {
|
|
1409
1421
|
return path10.join(getHomeDir(), ".codex", "config.toml");
|
|
1410
1422
|
}
|
|
@@ -1458,12 +1470,12 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
1458
1470
|
message: fs10.existsSync(configPath) ? void 0 : `${configPath} not found`
|
|
1459
1471
|
});
|
|
1460
1472
|
checks.push({
|
|
1461
|
-
name: "tap
|
|
1473
|
+
name: "tap MCP table present",
|
|
1462
1474
|
passed: !!mainTable,
|
|
1463
1475
|
message: mainTable ? void 0 : `${MCP_SELECTOR} not found`
|
|
1464
1476
|
});
|
|
1465
1477
|
checks.push({
|
|
1466
|
-
name: "tap
|
|
1478
|
+
name: "tap env table present",
|
|
1467
1479
|
passed: !!envTable,
|
|
1468
1480
|
message: envTable ? void 0 : `${ENV_SELECTOR} not found`
|
|
1469
1481
|
});
|
|
@@ -1483,7 +1495,7 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
1483
1495
|
passed: mainTable.includes(
|
|
1484
1496
|
`command = "${managed.command.replace(/\\/g, "\\\\")}"`
|
|
1485
1497
|
) && mainTable.includes(`args = [${expectedArgs}]`),
|
|
1486
|
-
message: "Managed tap
|
|
1498
|
+
message: "Managed tap command/args do not match expected values"
|
|
1487
1499
|
});
|
|
1488
1500
|
}
|
|
1489
1501
|
return checks;
|
|
@@ -1533,6 +1545,11 @@ var codexAdapter = {
|
|
|
1533
1545
|
if (extractTomlTable(content, MCP_SELECTOR)) {
|
|
1534
1546
|
conflicts.push(`Existing ${MCP_SELECTOR} table will be updated.`);
|
|
1535
1547
|
}
|
|
1548
|
+
if (extractTomlTable(content, OLD_MCP_SELECTOR)) {
|
|
1549
|
+
conflicts.push(
|
|
1550
|
+
`Legacy ${OLD_MCP_SELECTOR} table will be migrated to ${MCP_SELECTOR}.`
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1536
1553
|
if (extractTomlTable(content, ENV_SELECTOR)) {
|
|
1537
1554
|
conflicts.push(`Existing ${ENV_SELECTOR} table will be updated.`);
|
|
1538
1555
|
}
|
|
@@ -1598,6 +1615,12 @@ var codexAdapter = {
|
|
|
1598
1615
|
return { ...artifact, backupPath };
|
|
1599
1616
|
});
|
|
1600
1617
|
let nextContent = existingContent;
|
|
1618
|
+
if (extractTomlTable(nextContent, OLD_ENV_SELECTOR)) {
|
|
1619
|
+
nextContent = removeTomlTable(nextContent, OLD_ENV_SELECTOR);
|
|
1620
|
+
}
|
|
1621
|
+
if (extractTomlTable(nextContent, OLD_MCP_SELECTOR)) {
|
|
1622
|
+
nextContent = removeTomlTable(nextContent, OLD_MCP_SELECTOR);
|
|
1623
|
+
}
|
|
1601
1624
|
nextContent = replaceTomlTable(
|
|
1602
1625
|
nextContent,
|
|
1603
1626
|
MCP_SELECTOR,
|
|
@@ -1711,7 +1734,8 @@ var codexAdapter = {
|
|
|
1711
1734
|
// src/adapters/gemini.ts
|
|
1712
1735
|
import * as fs11 from "fs";
|
|
1713
1736
|
import * as path11 from "path";
|
|
1714
|
-
var GEMINI_SELECTOR = "mcpServers.tap
|
|
1737
|
+
var GEMINI_SELECTOR = "mcpServers.tap";
|
|
1738
|
+
var OLD_GEMINI_SELECTOR = "mcpServers.tap-comms";
|
|
1715
1739
|
function candidateConfigPaths(ctx) {
|
|
1716
1740
|
const home = getHomeDir();
|
|
1717
1741
|
return [
|
|
@@ -1773,7 +1797,7 @@ function verifyGeminiConfig(config, configPath, ctx) {
|
|
|
1773
1797
|
message: fs11.existsSync(configPath) ? void 0 : `${configPath} not found`
|
|
1774
1798
|
});
|
|
1775
1799
|
checks.push({
|
|
1776
|
-
name: "tap
|
|
1800
|
+
name: "tap entry present",
|
|
1777
1801
|
passed: !!entry,
|
|
1778
1802
|
message: entry ? void 0 : `${GEMINI_SELECTOR} not found`
|
|
1779
1803
|
});
|
|
@@ -1839,6 +1863,11 @@ var geminiAdapter = {
|
|
|
1839
1863
|
if (readNestedKey(config, GEMINI_SELECTOR) !== void 0) {
|
|
1840
1864
|
conflicts.push(`Existing ${GEMINI_SELECTOR} entry will be updated.`);
|
|
1841
1865
|
}
|
|
1866
|
+
if (readNestedKey(config, OLD_GEMINI_SELECTOR) !== void 0) {
|
|
1867
|
+
conflicts.push(
|
|
1868
|
+
`Legacy ${OLD_GEMINI_SELECTOR} entry will be migrated to ${GEMINI_SELECTOR}.`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1842
1871
|
} catch {
|
|
1843
1872
|
warnings.push(
|
|
1844
1873
|
`${configPath} exists but is not valid JSON. It will be replaced.`
|
|
@@ -1906,6 +1935,13 @@ var geminiAdapter = {
|
|
|
1906
1935
|
existed: previousValue !== void 0,
|
|
1907
1936
|
value: previousValue
|
|
1908
1937
|
});
|
|
1938
|
+
const oldValue = readNestedKey(config, OLD_GEMINI_SELECTOR);
|
|
1939
|
+
if (oldValue !== void 0) {
|
|
1940
|
+
const servers = config.mcpServers;
|
|
1941
|
+
if (servers) {
|
|
1942
|
+
delete servers["tap-comms"];
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1909
1945
|
setNestedKey2(config, GEMINI_SELECTOR, {
|
|
1910
1946
|
command: managed.command,
|
|
1911
1947
|
args: managed.args,
|
|
@@ -1987,6 +2023,7 @@ function getAdapter(runtime) {
|
|
|
1987
2023
|
// src/engine/bridge.ts
|
|
1988
2024
|
import * as fs13 from "fs";
|
|
1989
2025
|
import * as net from "net";
|
|
2026
|
+
import * as os3 from "os";
|
|
1990
2027
|
import * as path13 from "path";
|
|
1991
2028
|
import { randomBytes } from "crypto";
|
|
1992
2029
|
import { spawn, spawnSync as spawnSync3, execSync as execSync3 } from "child_process";
|
|
@@ -2159,6 +2196,8 @@ var APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
|
|
|
2159
2196
|
var APP_SERVER_HEALTH_RETRY_MS = 250;
|
|
2160
2197
|
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
2161
2198
|
var APP_SERVER_AUTH_FILE_MODE = 384;
|
|
2199
|
+
var WINDOWS_SPAWN_WRAPPER_PREFIX = "tap-spawn-";
|
|
2200
|
+
var WINDOWS_SPAWN_WRAPPER_STALE_MS = 60 * 60 * 1e3;
|
|
2162
2201
|
function appServerLogFilePath(stateDir, instanceId) {
|
|
2163
2202
|
return path13.join(stateDir, "logs", `app-server-${instanceId}.log`);
|
|
2164
2203
|
}
|
|
@@ -2195,12 +2234,66 @@ function removeFileIfExists(filePath) {
|
|
|
2195
2234
|
} catch {
|
|
2196
2235
|
}
|
|
2197
2236
|
}
|
|
2237
|
+
function toPowerShellSingleQuotedString(value) {
|
|
2238
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2239
|
+
}
|
|
2240
|
+
function toPowerShellStringArrayLiteral(values) {
|
|
2241
|
+
return `@(${values.map(toPowerShellSingleQuotedString).join(", ")})`;
|
|
2242
|
+
}
|
|
2243
|
+
function cleanupStaleWindowsSpawnWrappers(now = Date.now()) {
|
|
2244
|
+
let entries;
|
|
2245
|
+
try {
|
|
2246
|
+
entries = fs13.readdirSync(os3.tmpdir());
|
|
2247
|
+
} catch {
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
for (const entry of entries) {
|
|
2251
|
+
if (!entry.startsWith(WINDOWS_SPAWN_WRAPPER_PREFIX) || !/\.(cmd|ps1)$/i.test(entry)) {
|
|
2252
|
+
continue;
|
|
2253
|
+
}
|
|
2254
|
+
const wrapperPath = path13.join(os3.tmpdir(), entry);
|
|
2255
|
+
try {
|
|
2256
|
+
const stats = fs13.statSync(wrapperPath);
|
|
2257
|
+
if (now - stats.mtimeMs < WINDOWS_SPAWN_WRAPPER_STALE_MS) {
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
fs13.unlinkSync(wrapperPath);
|
|
2261
|
+
} catch {
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
function buildWindowsDetachedWrapperScript(command, args, logPath, stderrLogPath, env) {
|
|
2266
|
+
const lines = ["$ErrorActionPreference = 'Stop'"];
|
|
2267
|
+
for (const [key, value] of Object.entries(env)) {
|
|
2268
|
+
if (value !== void 0 && value !== process.env[key]) {
|
|
2269
|
+
lines.push(
|
|
2270
|
+
`[Environment]::SetEnvironmentVariable(${toPowerShellSingleQuotedString(key)}, ${toPowerShellSingleQuotedString(value)}, 'Process')`
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
lines.push(
|
|
2275
|
+
`$logPath = ${toPowerShellSingleQuotedString(logPath)}`,
|
|
2276
|
+
`$stderrLogPath = ${toPowerShellSingleQuotedString(stderrLogPath)}`,
|
|
2277
|
+
`$commandPath = ${toPowerShellSingleQuotedString(command)}`,
|
|
2278
|
+
`$commandArgs = ${toPowerShellStringArrayLiteral(args)}`,
|
|
2279
|
+
"$exitCode = 1",
|
|
2280
|
+
"try {",
|
|
2281
|
+
" & $commandPath @commandArgs >> $logPath 2>> $stderrLogPath",
|
|
2282
|
+
" $exitCode = if ($null -ne $LASTEXITCODE) { $LASTEXITCODE } else { 0 }",
|
|
2283
|
+
"} finally {",
|
|
2284
|
+
" Remove-Item -LiteralPath $PSCommandPath -Force -ErrorAction SilentlyContinue",
|
|
2285
|
+
"}",
|
|
2286
|
+
"exit $exitCode"
|
|
2287
|
+
);
|
|
2288
|
+
return `${lines.join("\r\n")}\r
|
|
2289
|
+
`;
|
|
2290
|
+
}
|
|
2198
2291
|
function getWebSocketCtor() {
|
|
2199
2292
|
const candidate = globalThis.WebSocket;
|
|
2200
2293
|
return typeof candidate === "function" ? candidate : null;
|
|
2201
2294
|
}
|
|
2202
2295
|
function delay(ms) {
|
|
2203
|
-
return new Promise((
|
|
2296
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
2204
2297
|
}
|
|
2205
2298
|
function isLoopbackHost(hostname) {
|
|
2206
2299
|
return hostname === "127.0.0.1" || hostname === "localhost";
|
|
@@ -2252,7 +2345,7 @@ function getBridgeRuntimeStateDir(repoRoot, instanceId) {
|
|
|
2252
2345
|
}
|
|
2253
2346
|
async function allocateLoopbackPort(hostname) {
|
|
2254
2347
|
const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
|
|
2255
|
-
return await new Promise((
|
|
2348
|
+
return await new Promise((resolve13, reject) => {
|
|
2256
2349
|
const server = net.createServer();
|
|
2257
2350
|
server.unref();
|
|
2258
2351
|
server.once("error", reject);
|
|
@@ -2270,7 +2363,7 @@ async function allocateLoopbackPort(hostname) {
|
|
|
2270
2363
|
reject(error);
|
|
2271
2364
|
return;
|
|
2272
2365
|
}
|
|
2273
|
-
|
|
2366
|
+
resolve13(port);
|
|
2274
2367
|
});
|
|
2275
2368
|
});
|
|
2276
2369
|
});
|
|
@@ -2453,35 +2546,50 @@ function findReusableManagedAppServer(stateDir, publicUrl) {
|
|
|
2453
2546
|
return null;
|
|
2454
2547
|
}
|
|
2455
2548
|
function startWindowsDetachedProcess(command, args, repoRoot, logPath, env = process.env) {
|
|
2456
|
-
const ext = path13.extname(command).toLowerCase();
|
|
2457
2549
|
const stderrLogPath = stderrLogFilePath(logPath);
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2550
|
+
const powerShellCommand = resolvePowerShellCommand();
|
|
2551
|
+
cleanupStaleWindowsSpawnWrappers();
|
|
2552
|
+
const wrapperPath = path13.join(
|
|
2553
|
+
os3.tmpdir(),
|
|
2554
|
+
`${WINDOWS_SPAWN_WRAPPER_PREFIX}${randomBytes(4).toString("hex")}.ps1`
|
|
2555
|
+
);
|
|
2556
|
+
fs13.writeFileSync(
|
|
2557
|
+
wrapperPath,
|
|
2558
|
+
buildWindowsDetachedWrapperScript(
|
|
2559
|
+
command,
|
|
2560
|
+
args,
|
|
2561
|
+
logPath,
|
|
2562
|
+
stderrLogPath,
|
|
2563
|
+
env
|
|
2564
|
+
)
|
|
2565
|
+
);
|
|
2566
|
+
const psCommand = [
|
|
2567
|
+
"$p = Start-Process",
|
|
2568
|
+
`-FilePath ${toPowerShellSingleQuotedString(powerShellCommand)}`,
|
|
2569
|
+
`-ArgumentList ${toPowerShellStringArrayLiteral(["-NoLogo", "-NoProfile", "-File", wrapperPath])}`,
|
|
2570
|
+
`-WorkingDirectory ${toPowerShellSingleQuotedString(repoRoot)}`,
|
|
2571
|
+
"-WindowStyle Hidden",
|
|
2572
|
+
"-PassThru",
|
|
2573
|
+
"; Write-Output $p.Id"
|
|
2574
|
+
].join(" ");
|
|
2575
|
+
const result = spawnSync3(
|
|
2576
|
+
powerShellCommand,
|
|
2577
|
+
["-NoLogo", "-NoProfile", "-Command", psCommand],
|
|
2578
|
+
{
|
|
2579
|
+
encoding: "utf-8",
|
|
2580
|
+
windowsHide: true
|
|
2581
|
+
}
|
|
2582
|
+
);
|
|
2583
|
+
if (result.status !== 0) {
|
|
2584
|
+
removeFileIfExists(wrapperPath);
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
const pid = parseInt(result.stdout.trim(), 10);
|
|
2588
|
+
if (!Number.isFinite(pid)) {
|
|
2589
|
+
removeFileIfExists(wrapperPath);
|
|
2590
|
+
return null;
|
|
2484
2591
|
}
|
|
2592
|
+
return pid;
|
|
2485
2593
|
}
|
|
2486
2594
|
function startWindowsCodexAppServer(command, url, repoRoot, logPath) {
|
|
2487
2595
|
return startWindowsDetachedProcess(
|
|
@@ -2543,12 +2651,12 @@ function resolveAppServerUrl(baseUrl, port) {
|
|
|
2543
2651
|
}
|
|
2544
2652
|
async function isTcpPortAvailable(hostname, port) {
|
|
2545
2653
|
const bindHost = hostname === "localhost" ? "127.0.0.1" : hostname;
|
|
2546
|
-
return await new Promise((
|
|
2654
|
+
return await new Promise((resolve13) => {
|
|
2547
2655
|
const server = net.createServer();
|
|
2548
2656
|
server.unref();
|
|
2549
|
-
server.once("error", () =>
|
|
2657
|
+
server.once("error", () => resolve13(false));
|
|
2550
2658
|
server.listen(port, bindHost, () => {
|
|
2551
|
-
server.close((error) =>
|
|
2659
|
+
server.close((error) => resolve13(!error));
|
|
2552
2660
|
});
|
|
2553
2661
|
});
|
|
2554
2662
|
}
|
|
@@ -2583,7 +2691,7 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
2583
2691
|
if (!WebSocket) {
|
|
2584
2692
|
return false;
|
|
2585
2693
|
}
|
|
2586
|
-
return new Promise((
|
|
2694
|
+
return new Promise((resolve13) => {
|
|
2587
2695
|
let settled = false;
|
|
2588
2696
|
let socket = null;
|
|
2589
2697
|
const finish = (healthy) => {
|
|
@@ -2596,7 +2704,7 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
2596
2704
|
socket?.close();
|
|
2597
2705
|
} catch {
|
|
2598
2706
|
}
|
|
2599
|
-
|
|
2707
|
+
resolve13(healthy);
|
|
2600
2708
|
};
|
|
2601
2709
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
2602
2710
|
try {
|
|
@@ -2920,7 +3028,11 @@ function logFilePath(stateDir, instanceId) {
|
|
|
2920
3028
|
function runtimeHeartbeatFilePath(runtimeStateDir) {
|
|
2921
3029
|
return path13.join(runtimeStateDir, "heartbeat.json");
|
|
2922
3030
|
}
|
|
2923
|
-
function
|
|
3031
|
+
function runtimeThreadStateFilePath(runtimeStateDir) {
|
|
3032
|
+
return path13.join(runtimeStateDir, "thread.json");
|
|
3033
|
+
}
|
|
3034
|
+
function loadRuntimeBridgeHeartbeat(bridgeState) {
|
|
3035
|
+
const runtimeStateDir = bridgeState?.runtimeStateDir;
|
|
2924
3036
|
if (!runtimeStateDir) {
|
|
2925
3037
|
return null;
|
|
2926
3038
|
}
|
|
@@ -2929,13 +3041,35 @@ function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
|
|
|
2929
3041
|
return null;
|
|
2930
3042
|
}
|
|
2931
3043
|
try {
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3044
|
+
return JSON.parse(
|
|
3045
|
+
fs13.readFileSync(heartbeatPath, "utf-8")
|
|
3046
|
+
);
|
|
3047
|
+
} catch {
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
function loadRuntimeBridgeThreadState(bridgeState) {
|
|
3052
|
+
const runtimeStateDir = bridgeState?.runtimeStateDir;
|
|
3053
|
+
if (!runtimeStateDir) {
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
const threadPath = runtimeThreadStateFilePath(runtimeStateDir);
|
|
3057
|
+
if (!fs13.existsSync(threadPath)) {
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
try {
|
|
3061
|
+
const parsed = JSON.parse(
|
|
3062
|
+
fs13.readFileSync(threadPath, "utf-8")
|
|
3063
|
+
);
|
|
3064
|
+
return parsed.threadId ? parsed : null;
|
|
2935
3065
|
} catch {
|
|
2936
3066
|
return null;
|
|
2937
3067
|
}
|
|
2938
3068
|
}
|
|
3069
|
+
function loadRuntimeHeartbeatTimestamp(runtimeStateDir) {
|
|
3070
|
+
const heartbeat = loadRuntimeBridgeHeartbeat({ runtimeStateDir });
|
|
3071
|
+
return typeof heartbeat?.updatedAt === "string" ? heartbeat.updatedAt : null;
|
|
3072
|
+
}
|
|
2939
3073
|
function resolveHeartbeatTimestamp(state) {
|
|
2940
3074
|
return loadRuntimeHeartbeatTimestamp(state?.runtimeStateDir) ?? state?.lastHeartbeat ?? null;
|
|
2941
3075
|
}
|
|
@@ -3105,6 +3239,7 @@ async function startBridge(options) {
|
|
|
3105
3239
|
options.messageLookbackMinutes
|
|
3106
3240
|
)
|
|
3107
3241
|
} : {},
|
|
3242
|
+
...process.env.TAP_COLD_START_WARMUP === "true" ? { TAP_COLD_START_WARMUP: "true" } : {},
|
|
3108
3243
|
...options.threadId ? { TAP_THREAD_ID: options.threadId } : {},
|
|
3109
3244
|
...options.ephemeral ? { TAP_EPHEMERAL: "true" } : {},
|
|
3110
3245
|
...options.processExistingMessages ? { TAP_PROCESS_EXISTING: "true" } : {}
|
|
@@ -3238,10 +3373,10 @@ function getBridgeStatus(stateDir, instanceId) {
|
|
|
3238
3373
|
// src/commands/add.ts
|
|
3239
3374
|
var ADD_HELP = `
|
|
3240
3375
|
Usage:
|
|
3241
|
-
tap
|
|
3376
|
+
tap add <claude|codex|gemini> [options]
|
|
3242
3377
|
|
|
3243
3378
|
Description:
|
|
3244
|
-
Install a runtime instance and configure it to use tap
|
|
3379
|
+
Install a runtime instance and configure it to use tap.
|
|
3245
3380
|
|
|
3246
3381
|
Options:
|
|
3247
3382
|
--name <name> Instance name (default: runtime name)
|
|
@@ -3598,7 +3733,7 @@ async function addCommand(args) {
|
|
|
3598
3733
|
// src/commands/status.ts
|
|
3599
3734
|
var STATUS_HELP = `
|
|
3600
3735
|
Usage:
|
|
3601
|
-
tap
|
|
3736
|
+
tap status
|
|
3602
3737
|
|
|
3603
3738
|
Description:
|
|
3604
3739
|
Show all installed instances, their bridge status, and configuration info.
|
|
@@ -3874,7 +4009,7 @@ function cleanEmptyParents(obj, keyPath) {
|
|
|
3874
4009
|
// src/commands/remove.ts
|
|
3875
4010
|
var REMOVE_HELP = `
|
|
3876
4011
|
Usage:
|
|
3877
|
-
tap
|
|
4012
|
+
tap remove <instance>
|
|
3878
4013
|
|
|
3879
4014
|
Description:
|
|
3880
4015
|
Remove a registered instance, stop its bridge, and rollback config changes.
|
|
@@ -4003,7 +4138,7 @@ function formatAge(seconds) {
|
|
|
4003
4138
|
}
|
|
4004
4139
|
var BRIDGE_HELP = `
|
|
4005
4140
|
Usage:
|
|
4006
|
-
tap
|
|
4141
|
+
tap bridge <subcommand> [instance] [options]
|
|
4007
4142
|
|
|
4008
4143
|
Subcommands:
|
|
4009
4144
|
start <instance> Start bridge for an instance (e.g. codex, codex-reviewer)
|
|
@@ -4061,6 +4196,21 @@ function redactProtectedUrl(url) {
|
|
|
4061
4196
|
function loadCurrentBridgeState(stateDir, instanceId, fallback) {
|
|
4062
4197
|
return loadBridgeState(stateDir, instanceId) ?? fallback ?? null;
|
|
4063
4198
|
}
|
|
4199
|
+
function formatThreadSummary(threadId, cwd) {
|
|
4200
|
+
if (!threadId) {
|
|
4201
|
+
return "-";
|
|
4202
|
+
}
|
|
4203
|
+
return cwd ? `${threadId} (${cwd})` : threadId;
|
|
4204
|
+
}
|
|
4205
|
+
function normalizeComparablePath(value) {
|
|
4206
|
+
return path14.resolve(value).replace(/\\/g, "/").toLowerCase();
|
|
4207
|
+
}
|
|
4208
|
+
function sameOptionalPath(left, right) {
|
|
4209
|
+
if (!left || !right) {
|
|
4210
|
+
return left === right;
|
|
4211
|
+
}
|
|
4212
|
+
return normalizeComparablePath(left) === normalizeComparablePath(right);
|
|
4213
|
+
}
|
|
4064
4214
|
function getSharedAppServerUsers(state, stateDir, currentInstanceId, appServerUrl) {
|
|
4065
4215
|
const shared = [];
|
|
4066
4216
|
for (const [id, inst] of Object.entries(state.instances)) {
|
|
@@ -4673,12 +4823,18 @@ function bridgeStatusAll() {
|
|
|
4673
4823
|
pid: null,
|
|
4674
4824
|
port: inst.port,
|
|
4675
4825
|
lastHeartbeat: null,
|
|
4826
|
+
threadId: null,
|
|
4827
|
+
threadCwd: null,
|
|
4828
|
+
savedThreadId: null,
|
|
4829
|
+
savedThreadCwd: null,
|
|
4676
4830
|
appServer: null
|
|
4677
4831
|
};
|
|
4678
4832
|
continue;
|
|
4679
4833
|
}
|
|
4680
4834
|
const status = getBridgeStatus(stateDir, instanceId);
|
|
4681
4835
|
const bridgeState = loadBridgeState(stateDir, instanceId);
|
|
4836
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
4837
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
4682
4838
|
const age = getHeartbeatAge(stateDir, instanceId);
|
|
4683
4839
|
const pid = bridgeState?.pid ?? null;
|
|
4684
4840
|
const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
@@ -4700,12 +4856,26 @@ function bridgeStatusAll() {
|
|
|
4700
4856
|
);
|
|
4701
4857
|
}
|
|
4702
4858
|
}
|
|
4859
|
+
if (runtimeHeartbeat?.threadId) {
|
|
4860
|
+
log(
|
|
4861
|
+
` Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
|
|
4862
|
+
);
|
|
4863
|
+
}
|
|
4864
|
+
if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
|
|
4865
|
+
log(
|
|
4866
|
+
` Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4703
4869
|
bridges[instanceId] = {
|
|
4704
4870
|
status,
|
|
4705
4871
|
runtime: inst.runtime,
|
|
4706
4872
|
pid,
|
|
4707
4873
|
port: inst.port,
|
|
4708
4874
|
lastHeartbeat: heartbeat,
|
|
4875
|
+
threadId: runtimeHeartbeat?.threadId ?? null,
|
|
4876
|
+
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
4877
|
+
savedThreadId: savedThread?.threadId ?? null,
|
|
4878
|
+
savedThreadCwd: savedThread?.cwd ?? null,
|
|
4709
4879
|
appServer: bridgeState?.appServer ?? null
|
|
4710
4880
|
};
|
|
4711
4881
|
}
|
|
@@ -4781,6 +4951,10 @@ function bridgeStatusOne(identifier) {
|
|
|
4781
4951
|
pid: null,
|
|
4782
4952
|
port: inst.port,
|
|
4783
4953
|
lastHeartbeat: null,
|
|
4954
|
+
threadId: null,
|
|
4955
|
+
threadCwd: null,
|
|
4956
|
+
savedThreadId: null,
|
|
4957
|
+
savedThreadCwd: null,
|
|
4784
4958
|
appServer: null
|
|
4785
4959
|
}
|
|
4786
4960
|
};
|
|
@@ -4789,6 +4963,8 @@ function bridgeStatusOne(identifier) {
|
|
|
4789
4963
|
const stateDir = resolvedCfg2.stateDir;
|
|
4790
4964
|
const status = getBridgeStatus(stateDir, instanceId);
|
|
4791
4965
|
const bridgeState = loadBridgeState(stateDir, instanceId);
|
|
4966
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
4967
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
4792
4968
|
const age = getHeartbeatAge(stateDir, instanceId);
|
|
4793
4969
|
const heartbeat = getBridgeHeartbeatTimestamp(stateDir, instanceId);
|
|
4794
4970
|
log(`Status: ${status}`);
|
|
@@ -4797,6 +4973,16 @@ function bridgeStatusOne(identifier) {
|
|
|
4797
4973
|
log(
|
|
4798
4974
|
`Heartbeat: ${heartbeat ?? "-"}${age !== null ? ` (${formatAge(age)})` : ""}`
|
|
4799
4975
|
);
|
|
4976
|
+
if (runtimeHeartbeat?.threadId) {
|
|
4977
|
+
log(
|
|
4978
|
+
`Thread: ${formatThreadSummary(runtimeHeartbeat.threadId, runtimeHeartbeat.threadCwd)}`
|
|
4979
|
+
);
|
|
4980
|
+
}
|
|
4981
|
+
if (savedThread?.threadId && (savedThread.threadId !== runtimeHeartbeat?.threadId || !sameOptionalPath(savedThread.cwd, runtimeHeartbeat?.threadCwd))) {
|
|
4982
|
+
log(
|
|
4983
|
+
`Saved: ${formatThreadSummary(savedThread.threadId, savedThread.cwd)}`
|
|
4984
|
+
);
|
|
4985
|
+
}
|
|
4800
4986
|
log(
|
|
4801
4987
|
`Log: ${path14.join(stateDir, "logs", `bridge-${instanceId}.log`)}`
|
|
4802
4988
|
);
|
|
@@ -4845,6 +5031,10 @@ function bridgeStatusOne(identifier) {
|
|
|
4845
5031
|
pid: bridgeState?.pid ?? null,
|
|
4846
5032
|
port: inst.port,
|
|
4847
5033
|
lastHeartbeat: heartbeat,
|
|
5034
|
+
threadId: runtimeHeartbeat?.threadId ?? null,
|
|
5035
|
+
threadCwd: runtimeHeartbeat?.threadCwd ?? null,
|
|
5036
|
+
savedThreadId: savedThread?.threadId ?? null,
|
|
5037
|
+
savedThreadCwd: savedThread?.cwd ?? null,
|
|
4848
5038
|
appServer: bridgeState?.appServer ?? null
|
|
4849
5039
|
}
|
|
4850
5040
|
};
|
|
@@ -5214,7 +5404,7 @@ function collectDashboardSnapshot(repoRoot, commsDirOverride) {
|
|
|
5214
5404
|
// src/commands/up.ts
|
|
5215
5405
|
var UP_HELP = `
|
|
5216
5406
|
Usage:
|
|
5217
|
-
tap
|
|
5407
|
+
tap up [bridge-start options]
|
|
5218
5408
|
|
|
5219
5409
|
Description:
|
|
5220
5410
|
Start all registered app-server bridge daemons with one command.
|
|
@@ -5238,7 +5428,18 @@ async function upCommand(args) {
|
|
|
5238
5428
|
};
|
|
5239
5429
|
}
|
|
5240
5430
|
const repoRoot = findRepoRoot();
|
|
5241
|
-
const
|
|
5431
|
+
const previousColdStartWarmup = process.env.TAP_COLD_START_WARMUP;
|
|
5432
|
+
process.env.TAP_COLD_START_WARMUP = "true";
|
|
5433
|
+
let result;
|
|
5434
|
+
try {
|
|
5435
|
+
result = await bridgeCommand(["start", "--all", ...args]);
|
|
5436
|
+
} finally {
|
|
5437
|
+
if (previousColdStartWarmup === void 0) {
|
|
5438
|
+
delete process.env.TAP_COLD_START_WARMUP;
|
|
5439
|
+
} else {
|
|
5440
|
+
process.env.TAP_COLD_START_WARMUP = previousColdStartWarmup;
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5242
5443
|
const snapshot = collectDashboardSnapshot(repoRoot);
|
|
5243
5444
|
const activeBridges = snapshot.bridges.filter(
|
|
5244
5445
|
(bridge) => bridge.status === "running"
|
|
@@ -5269,7 +5470,7 @@ async function upCommand(args) {
|
|
|
5269
5470
|
// src/commands/down.ts
|
|
5270
5471
|
var DOWN_HELP = `
|
|
5271
5472
|
Usage:
|
|
5272
|
-
tap
|
|
5473
|
+
tap down
|
|
5273
5474
|
|
|
5274
5475
|
Description:
|
|
5275
5476
|
Stop all running bridge daemons and managed app-servers.
|
|
@@ -5320,10 +5521,10 @@ import * as path16 from "path";
|
|
|
5320
5521
|
import { spawn as spawn2 } from "child_process";
|
|
5321
5522
|
var SERVE_HELP = `
|
|
5322
5523
|
Usage:
|
|
5323
|
-
tap
|
|
5524
|
+
tap serve [options]
|
|
5324
5525
|
|
|
5325
5526
|
Description:
|
|
5326
|
-
Start the tap
|
|
5527
|
+
Start the tap MCP server over stdio. This command takes over the
|
|
5327
5528
|
process \u2014 it is intended to be launched by an MCP host (e.g. Claude Code).
|
|
5328
5529
|
|
|
5329
5530
|
Options:
|
|
@@ -5393,9 +5594,9 @@ async function serveCommand(args) {
|
|
|
5393
5594
|
TAP_COMMS_DIR: commsDir
|
|
5394
5595
|
}
|
|
5395
5596
|
});
|
|
5396
|
-
return new Promise((
|
|
5597
|
+
return new Promise((resolve13) => {
|
|
5397
5598
|
child.on("error", (err) => {
|
|
5398
|
-
|
|
5599
|
+
resolve13({
|
|
5399
5600
|
ok: false,
|
|
5400
5601
|
command: "serve",
|
|
5401
5602
|
code: "TAP_INTERNAL_ERROR",
|
|
@@ -5405,7 +5606,7 @@ async function serveCommand(args) {
|
|
|
5405
5606
|
});
|
|
5406
5607
|
});
|
|
5407
5608
|
child.on("exit", (code) => {
|
|
5408
|
-
|
|
5609
|
+
resolve13({
|
|
5409
5610
|
ok: code === 0,
|
|
5410
5611
|
command: "serve",
|
|
5411
5612
|
code: code === 0 ? "TAP_SERVE_OK" : "TAP_INTERNAL_ERROR",
|
|
@@ -5423,7 +5624,7 @@ import * as path17 from "path";
|
|
|
5423
5624
|
import { execSync as execSync5 } from "child_process";
|
|
5424
5625
|
var INIT_WORKTREE_HELP = `
|
|
5425
5626
|
Usage:
|
|
5426
|
-
tap
|
|
5627
|
+
tap init-worktree [options]
|
|
5427
5628
|
|
|
5428
5629
|
Options:
|
|
5429
5630
|
--path <dir> Worktree directory (required, e.g. ../hua-wt-3)
|
|
@@ -5606,7 +5807,7 @@ function step4GenerateMcpJson(opts, warnings) {
|
|
|
5606
5807
|
);
|
|
5607
5808
|
const mcpConfig = {
|
|
5608
5809
|
mcpServers: {
|
|
5609
|
-
|
|
5810
|
+
tap: {
|
|
5610
5811
|
command: bunAbs,
|
|
5611
5812
|
args: [channelEntry],
|
|
5612
5813
|
cwd: wtAbs,
|
|
@@ -5870,7 +6071,7 @@ function renderSnapshot(snapshot) {
|
|
|
5870
6071
|
}
|
|
5871
6072
|
var DASHBOARD_HELP = `
|
|
5872
6073
|
Usage:
|
|
5873
|
-
tap
|
|
6074
|
+
tap dashboard [options]
|
|
5874
6075
|
|
|
5875
6076
|
Description:
|
|
5876
6077
|
Display a unified ops dashboard: agents, bridges, PRs, and warnings.
|
|
@@ -5959,14 +6160,150 @@ import {
|
|
|
5959
6160
|
mkdirSync as mkdirSync10,
|
|
5960
6161
|
readdirSync as readdirSync4,
|
|
5961
6162
|
readFileSync as readFileSync14,
|
|
6163
|
+
renameSync as renameSync10,
|
|
5962
6164
|
statSync as statSync2,
|
|
5963
|
-
unlinkSync as unlinkSync3
|
|
6165
|
+
unlinkSync as unlinkSync3,
|
|
6166
|
+
writeFileSync as writeFileSync12
|
|
5964
6167
|
} from "fs";
|
|
5965
|
-
import {
|
|
5966
|
-
import {
|
|
6168
|
+
import { homedir as homedir3 } from "os";
|
|
6169
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
6170
|
+
import { dirname as dirname11, join as join17, resolve as resolve12 } from "path";
|
|
5967
6171
|
var PASS = "pass";
|
|
5968
6172
|
var WARN = "warn";
|
|
5969
6173
|
var FAIL = "fail";
|
|
6174
|
+
var CODEX_ENV_DRIFT_KEYS = [
|
|
6175
|
+
"TAP_COMMS_DIR",
|
|
6176
|
+
"TAP_STATE_DIR",
|
|
6177
|
+
"TAP_REPO_ROOT"
|
|
6178
|
+
];
|
|
6179
|
+
function normalizeComparablePath2(value) {
|
|
6180
|
+
return resolve12(value).replace(/\\/g, "/").toLowerCase();
|
|
6181
|
+
}
|
|
6182
|
+
function samePath(left, right) {
|
|
6183
|
+
return normalizeComparablePath2(left) === normalizeComparablePath2(right);
|
|
6184
|
+
}
|
|
6185
|
+
function looksLikePathToken(value) {
|
|
6186
|
+
return /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("/") || value.startsWith("\\") || value.startsWith(".") || value.includes("/") || value.includes("\\");
|
|
6187
|
+
}
|
|
6188
|
+
function sameCommandToken(left, right) {
|
|
6189
|
+
return looksLikePathToken(left) || looksLikePathToken(right) ? samePath(left, right) : left === right;
|
|
6190
|
+
}
|
|
6191
|
+
function sameStringArray(left, right) {
|
|
6192
|
+
return left.length === right.length && left.every((value, index) => sameCommandToken(value, right[index] ?? ""));
|
|
6193
|
+
}
|
|
6194
|
+
function appendWarningMessage(message, extra) {
|
|
6195
|
+
return message.includes(extra) ? message : `${message}; ${extra}`;
|
|
6196
|
+
}
|
|
6197
|
+
function findCodexConfigPath3() {
|
|
6198
|
+
return join17(homedir3(), ".codex", "config.toml");
|
|
6199
|
+
}
|
|
6200
|
+
function canonicalizeTrustPath3(targetPath) {
|
|
6201
|
+
let resolved = resolve12(targetPath).replace(/\//g, "\\");
|
|
6202
|
+
const driveRoot = /^[A-Za-z]:\\$/;
|
|
6203
|
+
if (!driveRoot.test(resolved)) {
|
|
6204
|
+
resolved = resolved.replace(/\\+$/g, "");
|
|
6205
|
+
}
|
|
6206
|
+
return resolved.startsWith("\\\\?\\") ? resolved : `\\\\?\\${resolved}`;
|
|
6207
|
+
}
|
|
6208
|
+
function trustSelector2(targetPath) {
|
|
6209
|
+
return `projects.'${canonicalizeTrustPath3(targetPath)}'`;
|
|
6210
|
+
}
|
|
6211
|
+
function writeTomlAtomically(filePath, content) {
|
|
6212
|
+
const dir = dirname11(filePath);
|
|
6213
|
+
mkdirSync10(dir, { recursive: true });
|
|
6214
|
+
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
6215
|
+
writeFileSync12(tmp, content, "utf-8");
|
|
6216
|
+
renameSync10(tmp, filePath);
|
|
6217
|
+
}
|
|
6218
|
+
function hasInstalledCodexInstance(state) {
|
|
6219
|
+
return !!state ? Object.values(state.instances).some(
|
|
6220
|
+
(instance2) => instance2.runtime === "codex" && instance2.installed
|
|
6221
|
+
) : false;
|
|
6222
|
+
}
|
|
6223
|
+
function getCodexTrustTargets(repoRoot) {
|
|
6224
|
+
return [...new Set([repoRoot, process.cwd()].map((value) => resolve12(value)))];
|
|
6225
|
+
}
|
|
6226
|
+
function buildCodexDoctorSpec(repoRoot, commsDir) {
|
|
6227
|
+
const state = loadState(repoRoot);
|
|
6228
|
+
if (!hasInstalledCodexInstance(state)) {
|
|
6229
|
+
return null;
|
|
6230
|
+
}
|
|
6231
|
+
const ctx = createAdapterContext(commsDir, repoRoot);
|
|
6232
|
+
const managed = buildManagedMcpServerSpec(ctx);
|
|
6233
|
+
return {
|
|
6234
|
+
configPath: findCodexConfigPath3(),
|
|
6235
|
+
trustTargets: getCodexTrustTargets(repoRoot),
|
|
6236
|
+
managed
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
function repairCodexConfig(repoRoot, commsDir) {
|
|
6240
|
+
const spec = buildCodexDoctorSpec(repoRoot, commsDir);
|
|
6241
|
+
if (!spec) {
|
|
6242
|
+
throw new Error("No installed Codex instance found in tap state.");
|
|
6243
|
+
}
|
|
6244
|
+
if (!spec.managed.command || spec.managed.issues.length > 0) {
|
|
6245
|
+
throw new Error(
|
|
6246
|
+
spec.managed.issues[0] ?? "Unable to resolve the managed tap MCP server for Codex."
|
|
6247
|
+
);
|
|
6248
|
+
}
|
|
6249
|
+
const existingContent = existsSync16(spec.configPath) ? readFileSync14(spec.configPath, "utf-8") : "";
|
|
6250
|
+
const existingTapEnvTable = extractTomlTable(existingContent, "mcp_servers.tap.env");
|
|
6251
|
+
const existingLegacyEnvTable = extractTomlTable(
|
|
6252
|
+
existingContent,
|
|
6253
|
+
"mcp_servers.tap-comms.env"
|
|
6254
|
+
);
|
|
6255
|
+
const preservedEnv = parseTomlAssignments(
|
|
6256
|
+
existingTapEnvTable ?? existingLegacyEnvTable ?? ""
|
|
6257
|
+
);
|
|
6258
|
+
const repairedEnv = {
|
|
6259
|
+
...preservedEnv,
|
|
6260
|
+
...Object.fromEntries(
|
|
6261
|
+
CODEX_ENV_DRIFT_KEYS.map((key) => [key, spec.managed.env[key]])
|
|
6262
|
+
)
|
|
6263
|
+
};
|
|
6264
|
+
let nextContent = existingContent;
|
|
6265
|
+
if (extractTomlTable(nextContent, "mcp_servers.tap-comms.env")) {
|
|
6266
|
+
nextContent = removeTomlTable(nextContent, "mcp_servers.tap-comms.env");
|
|
6267
|
+
}
|
|
6268
|
+
if (extractTomlTable(nextContent, "mcp_servers.tap-comms")) {
|
|
6269
|
+
nextContent = removeTomlTable(nextContent, "mcp_servers.tap-comms");
|
|
6270
|
+
}
|
|
6271
|
+
nextContent = replaceTomlTable(
|
|
6272
|
+
nextContent,
|
|
6273
|
+
"mcp_servers.tap",
|
|
6274
|
+
renderTomlTable(
|
|
6275
|
+
"mcp_servers.tap",
|
|
6276
|
+
{
|
|
6277
|
+
command: spec.managed.command,
|
|
6278
|
+
args: spec.managed.args
|
|
6279
|
+
},
|
|
6280
|
+
extractTomlTable(existingContent, "mcp_servers.tap")
|
|
6281
|
+
)
|
|
6282
|
+
);
|
|
6283
|
+
nextContent = replaceTomlTable(
|
|
6284
|
+
nextContent,
|
|
6285
|
+
"mcp_servers.tap.env",
|
|
6286
|
+
renderTomlTable(
|
|
6287
|
+
"mcp_servers.tap.env",
|
|
6288
|
+
repairedEnv,
|
|
6289
|
+
existingTapEnvTable ?? existingLegacyEnvTable
|
|
6290
|
+
)
|
|
6291
|
+
);
|
|
6292
|
+
for (const trustTarget of spec.trustTargets) {
|
|
6293
|
+
const selector = trustSelector2(trustTarget);
|
|
6294
|
+
nextContent = replaceTomlTable(
|
|
6295
|
+
nextContent,
|
|
6296
|
+
selector,
|
|
6297
|
+
renderTomlTable(
|
|
6298
|
+
selector,
|
|
6299
|
+
{ trust_level: "trusted" },
|
|
6300
|
+
extractTomlTable(existingContent, selector)
|
|
6301
|
+
)
|
|
6302
|
+
);
|
|
6303
|
+
}
|
|
6304
|
+
writeTomlAtomically(spec.configPath, nextContent);
|
|
6305
|
+
return `Repaired Codex config at ${spec.configPath}. Restart Codex to reload MCP settings.`;
|
|
6306
|
+
}
|
|
5970
6307
|
function countFiles(dir, ext = ".md") {
|
|
5971
6308
|
if (!existsSync16(dir)) return 0;
|
|
5972
6309
|
try {
|
|
@@ -5991,21 +6328,6 @@ function recentFileCount(dir, withinMs) {
|
|
|
5991
6328
|
}
|
|
5992
6329
|
return count;
|
|
5993
6330
|
}
|
|
5994
|
-
function loadBridgeRuntimeHeartbeat(bridgeState) {
|
|
5995
|
-
const runtimeStateDir = bridgeState?.runtimeStateDir;
|
|
5996
|
-
if (!runtimeStateDir) {
|
|
5997
|
-
return null;
|
|
5998
|
-
}
|
|
5999
|
-
const heartbeatPath = join17(runtimeStateDir, "heartbeat.json");
|
|
6000
|
-
if (!existsSync16(heartbeatPath)) {
|
|
6001
|
-
return null;
|
|
6002
|
-
}
|
|
6003
|
-
try {
|
|
6004
|
-
return JSON.parse(readFileSync14(heartbeatPath, "utf-8"));
|
|
6005
|
-
} catch {
|
|
6006
|
-
return null;
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
6331
|
function checkComms(commsDir) {
|
|
6010
6332
|
const checks = [];
|
|
6011
6333
|
checks.push({
|
|
@@ -6089,7 +6411,8 @@ function checkInstances(repoRoot, stateDir) {
|
|
|
6089
6411
|
const running = isBridgeRunning(stateDir, id);
|
|
6090
6412
|
const bridgeState = loadBridgeState(stateDir, id);
|
|
6091
6413
|
const heartbeatAge = getHeartbeatAge(stateDir, id);
|
|
6092
|
-
const runtimeHeartbeat =
|
|
6414
|
+
const runtimeHeartbeat = loadRuntimeBridgeHeartbeat(bridgeState);
|
|
6415
|
+
const savedThread = loadRuntimeBridgeThreadState(bridgeState);
|
|
6093
6416
|
let status;
|
|
6094
6417
|
let message;
|
|
6095
6418
|
let fix;
|
|
@@ -6135,9 +6458,30 @@ function checkInstances(repoRoot, stateDir) {
|
|
|
6135
6458
|
}
|
|
6136
6459
|
const lastRuntimeError = runtimeHeartbeat?.lastError?.trim();
|
|
6137
6460
|
if (lastRuntimeError) {
|
|
6138
|
-
status =
|
|
6461
|
+
status = WARN;
|
|
6139
6462
|
message = `${message}; bridge last error: ${lastRuntimeError}`;
|
|
6140
6463
|
}
|
|
6464
|
+
if (savedThread?.threadId && savedThread.cwd && !samePath(savedThread.cwd, repoRoot)) {
|
|
6465
|
+
status = WARN;
|
|
6466
|
+
message = appendWarningMessage(
|
|
6467
|
+
message,
|
|
6468
|
+
`saved thread cwd mismatch (${savedThread.cwd})`
|
|
6469
|
+
);
|
|
6470
|
+
}
|
|
6471
|
+
if (runtimeHeartbeat?.threadId && savedThread?.threadId && runtimeHeartbeat.threadId !== savedThread.threadId) {
|
|
6472
|
+
status = WARN;
|
|
6473
|
+
message = appendWarningMessage(
|
|
6474
|
+
message,
|
|
6475
|
+
`saved thread ${savedThread.threadId} differs from active thread ${runtimeHeartbeat.threadId}`
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
if (runtimeHeartbeat?.threadCwd && !samePath(runtimeHeartbeat.threadCwd, repoRoot)) {
|
|
6479
|
+
status = WARN;
|
|
6480
|
+
message = appendWarningMessage(
|
|
6481
|
+
message,
|
|
6482
|
+
`active thread cwd mismatch (${runtimeHeartbeat.threadCwd})`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6141
6485
|
checks.push({ name: `bridge: ${id}`, status, message, fix });
|
|
6142
6486
|
} else {
|
|
6143
6487
|
checks.push({
|
|
@@ -6210,15 +6554,25 @@ function checkMcpServer(repoRoot) {
|
|
|
6210
6554
|
});
|
|
6211
6555
|
return checks;
|
|
6212
6556
|
}
|
|
6213
|
-
const
|
|
6214
|
-
|
|
6557
|
+
const mcpServers = config?.mcpServers;
|
|
6558
|
+
const hasTap = mcpServers?.["tap"];
|
|
6559
|
+
const hasOldKey = mcpServers?.["tap-comms"];
|
|
6560
|
+
if (hasOldKey) {
|
|
6561
|
+
checks.push({
|
|
6562
|
+
name: "MCP config (.mcp.json)",
|
|
6563
|
+
status: WARN,
|
|
6564
|
+
message: 'Legacy "tap-comms" key found. Run "tap add claude" to migrate to the new "tap" key.'
|
|
6565
|
+
});
|
|
6566
|
+
}
|
|
6567
|
+
if (!hasTap && !hasOldKey) {
|
|
6215
6568
|
checks.push({
|
|
6216
6569
|
name: "MCP config (.mcp.json)",
|
|
6217
6570
|
status: WARN,
|
|
6218
|
-
message: "tap
|
|
6571
|
+
message: "tap not configured"
|
|
6219
6572
|
});
|
|
6220
6573
|
return checks;
|
|
6221
6574
|
}
|
|
6575
|
+
const hasTapComms = hasTap ?? hasOldKey;
|
|
6222
6576
|
checks.push({
|
|
6223
6577
|
name: "MCP config (.mcp.json)",
|
|
6224
6578
|
status: PASS,
|
|
@@ -6229,11 +6583,12 @@ function checkMcpServer(repoRoot) {
|
|
|
6229
6583
|
let cmdAvailable = existsSync16(cmd);
|
|
6230
6584
|
if (!cmdAvailable) {
|
|
6231
6585
|
try {
|
|
6232
|
-
|
|
6586
|
+
const result = spawnSync4(cmd, ["--version"], {
|
|
6233
6587
|
stdio: "pipe",
|
|
6234
|
-
timeout: 5e3
|
|
6588
|
+
timeout: 5e3,
|
|
6589
|
+
shell: process.platform === "win32"
|
|
6235
6590
|
});
|
|
6236
|
-
cmdAvailable =
|
|
6591
|
+
cmdAvailable = result.status === 0;
|
|
6237
6592
|
} catch {
|
|
6238
6593
|
}
|
|
6239
6594
|
}
|
|
@@ -6292,6 +6647,88 @@ function checkMcpServer(repoRoot) {
|
|
|
6292
6647
|
});
|
|
6293
6648
|
return checks;
|
|
6294
6649
|
}
|
|
6650
|
+
function checkCodexConfig(repoRoot, commsDir) {
|
|
6651
|
+
const spec = buildCodexDoctorSpec(repoRoot, commsDir);
|
|
6652
|
+
if (!spec) {
|
|
6653
|
+
return [];
|
|
6654
|
+
}
|
|
6655
|
+
const checks = [];
|
|
6656
|
+
const fixHint = 'Run "tap doctor --fix" or "tap add codex --force".';
|
|
6657
|
+
if (!existsSync16(spec.configPath)) {
|
|
6658
|
+
checks.push({
|
|
6659
|
+
name: "MCP config (~/.codex/config.toml)",
|
|
6660
|
+
status: WARN,
|
|
6661
|
+
message: `${spec.configPath} not found. ${fixHint}`,
|
|
6662
|
+
fix: () => repairCodexConfig(repoRoot, commsDir)
|
|
6663
|
+
});
|
|
6664
|
+
return checks;
|
|
6665
|
+
}
|
|
6666
|
+
const content = readFileSync14(spec.configPath, "utf-8");
|
|
6667
|
+
const tapTable = extractTomlTable(content, "mcp_servers.tap");
|
|
6668
|
+
const tapEnvTable = extractTomlTable(content, "mcp_servers.tap.env");
|
|
6669
|
+
const legacyTable = extractTomlTable(content, "mcp_servers.tap-comms");
|
|
6670
|
+
const legacyEnvTable = extractTomlTable(content, "mcp_servers.tap-comms.env");
|
|
6671
|
+
const selectedMain = parseTomlAssignments(tapTable ?? "");
|
|
6672
|
+
const selectedEnv = parseTomlAssignments(
|
|
6673
|
+
tapEnvTable ?? legacyEnvTable ?? ""
|
|
6674
|
+
);
|
|
6675
|
+
const issues = [];
|
|
6676
|
+
if (legacyTable || legacyEnvTable) {
|
|
6677
|
+
issues.push('legacy "tap-comms" key present');
|
|
6678
|
+
}
|
|
6679
|
+
if (!tapTable && !legacyTable) {
|
|
6680
|
+
issues.push("tap MCP table missing");
|
|
6681
|
+
}
|
|
6682
|
+
if (!tapEnvTable && !legacyEnvTable) {
|
|
6683
|
+
issues.push("tap MCP env table missing");
|
|
6684
|
+
}
|
|
6685
|
+
if (tapTable && spec.managed.command) {
|
|
6686
|
+
const actualCommand = selectedMain.command;
|
|
6687
|
+
if (typeof actualCommand !== "string") {
|
|
6688
|
+
issues.push("tap MCP command missing");
|
|
6689
|
+
} else if (!sameCommandToken(actualCommand, spec.managed.command)) {
|
|
6690
|
+
issues.push(`tap MCP command drift (${actualCommand})`);
|
|
6691
|
+
}
|
|
6692
|
+
const actualArgs = selectedMain.args;
|
|
6693
|
+
if (!Array.isArray(actualArgs)) {
|
|
6694
|
+
issues.push("tap MCP args missing");
|
|
6695
|
+
} else if (!sameStringArray(actualArgs, spec.managed.args)) {
|
|
6696
|
+
issues.push(`tap MCP args drift (${JSON.stringify(actualArgs)})`);
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
for (const key of CODEX_ENV_DRIFT_KEYS) {
|
|
6700
|
+
const expected = spec.managed.env[key];
|
|
6701
|
+
const actual = selectedEnv[key];
|
|
6702
|
+
if (typeof actual !== "string") {
|
|
6703
|
+
issues.push(`${key} missing`);
|
|
6704
|
+
continue;
|
|
6705
|
+
}
|
|
6706
|
+
if (!samePath(actual, expected)) {
|
|
6707
|
+
issues.push(`${key} drift (${actual})`);
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
for (const trustTarget of spec.trustTargets) {
|
|
6711
|
+
const trustTable = extractTomlTable(content, trustSelector2(trustTarget));
|
|
6712
|
+
if (!trustTable || !trustTable.includes('trust_level = "trusted"')) {
|
|
6713
|
+
issues.push(`missing trust for ${trustTarget}`);
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
if (issues.length === 0) {
|
|
6717
|
+
checks.push({
|
|
6718
|
+
name: "MCP config (~/.codex/config.toml)",
|
|
6719
|
+
status: PASS,
|
|
6720
|
+
message: spec.configPath
|
|
6721
|
+
});
|
|
6722
|
+
return checks;
|
|
6723
|
+
}
|
|
6724
|
+
checks.push({
|
|
6725
|
+
name: "MCP config (~/.codex/config.toml)",
|
|
6726
|
+
status: WARN,
|
|
6727
|
+
message: `${issues.join("; ")}. ${fixHint}`,
|
|
6728
|
+
fix: () => repairCodexConfig(repoRoot, commsDir)
|
|
6729
|
+
});
|
|
6730
|
+
return checks;
|
|
6731
|
+
}
|
|
6295
6732
|
function checkBridgeTurnHealth(repoRoot) {
|
|
6296
6733
|
const checks = [];
|
|
6297
6734
|
const tmpDir = join17(repoRoot, ".tmp");
|
|
@@ -6408,7 +6845,7 @@ function renderCheck(check, fixMode) {
|
|
|
6408
6845
|
}
|
|
6409
6846
|
var DOCTOR_HELP = `
|
|
6410
6847
|
Usage:
|
|
6411
|
-
tap
|
|
6848
|
+
tap doctor [options]
|
|
6412
6849
|
|
|
6413
6850
|
Description:
|
|
6414
6851
|
Diagnose tap infrastructure health: comms directory, instances, bridges,
|
|
@@ -6456,6 +6893,7 @@ async function doctorCommand(args) {
|
|
|
6456
6893
|
checks.push(...checkInstances(repoRoot, config.stateDir));
|
|
6457
6894
|
checks.push(...checkMessageLifecycle(commsDir));
|
|
6458
6895
|
checks.push(...checkMcpServer(repoRoot));
|
|
6896
|
+
checks.push(...checkCodexConfig(repoRoot, commsDir));
|
|
6459
6897
|
checks.push(...checkBridgeTurnHealth(repoRoot));
|
|
6460
6898
|
return checks;
|
|
6461
6899
|
}
|
|
@@ -6547,12 +6985,12 @@ async function doctorCommand(args) {
|
|
|
6547
6985
|
}
|
|
6548
6986
|
|
|
6549
6987
|
// src/commands/comms.ts
|
|
6550
|
-
import { execSync as
|
|
6988
|
+
import { execSync as execSync6, spawnSync as spawnSync5 } from "child_process";
|
|
6551
6989
|
import * as fs17 from "fs";
|
|
6552
6990
|
import * as path18 from "path";
|
|
6553
6991
|
var COMMS_HELP = `
|
|
6554
6992
|
Usage:
|
|
6555
|
-
tap
|
|
6993
|
+
tap comms <subcommand>
|
|
6556
6994
|
|
|
6557
6995
|
Subcommands:
|
|
6558
6996
|
pull Pull latest changes from comms remote repo
|
|
@@ -6579,7 +7017,7 @@ function commsPull(commsDir) {
|
|
|
6579
7017
|
};
|
|
6580
7018
|
}
|
|
6581
7019
|
try {
|
|
6582
|
-
const output =
|
|
7020
|
+
const output = execSync6("git pull --rebase", {
|
|
6583
7021
|
cwd: commsDir,
|
|
6584
7022
|
encoding: "utf-8",
|
|
6585
7023
|
stdio: "pipe"
|
|
@@ -6621,8 +7059,8 @@ function commsPush(commsDir) {
|
|
|
6621
7059
|
};
|
|
6622
7060
|
}
|
|
6623
7061
|
try {
|
|
6624
|
-
|
|
6625
|
-
const status =
|
|
7062
|
+
execSync6("git add -A", { cwd: commsDir, stdio: "pipe" });
|
|
7063
|
+
const status = execSync6("git status --porcelain", {
|
|
6626
7064
|
cwd: commsDir,
|
|
6627
7065
|
encoding: "utf-8",
|
|
6628
7066
|
stdio: "pipe"
|
|
@@ -6639,7 +7077,7 @@ function commsPush(commsDir) {
|
|
|
6639
7077
|
};
|
|
6640
7078
|
}
|
|
6641
7079
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6642
|
-
const commitResult =
|
|
7080
|
+
const commitResult = spawnSync5(
|
|
6643
7081
|
"git",
|
|
6644
7082
|
["commit", "-m", `chore(comms): sync ${timestamp}`],
|
|
6645
7083
|
{ cwd: commsDir, stdio: "pipe", encoding: "utf-8" }
|
|
@@ -6655,7 +7093,7 @@ function commsPush(commsDir) {
|
|
|
6655
7093
|
data: { commsDir }
|
|
6656
7094
|
};
|
|
6657
7095
|
}
|
|
6658
|
-
|
|
7096
|
+
execSync6("git push", { cwd: commsDir, stdio: "pipe" });
|
|
6659
7097
|
logSuccess("Comms push complete");
|
|
6660
7098
|
return {
|
|
6661
7099
|
ok: true,
|
|
@@ -6739,16 +7177,61 @@ function extractJsonFlag(args) {
|
|
|
6739
7177
|
return { jsonMode, cleanArgs };
|
|
6740
7178
|
}
|
|
6741
7179
|
|
|
7180
|
+
// src/cli-suggest.ts
|
|
7181
|
+
var COMMANDS = [
|
|
7182
|
+
"init",
|
|
7183
|
+
"init-worktree",
|
|
7184
|
+
"add",
|
|
7185
|
+
"remove",
|
|
7186
|
+
"status",
|
|
7187
|
+
"bridge",
|
|
7188
|
+
"up",
|
|
7189
|
+
"down",
|
|
7190
|
+
"comms",
|
|
7191
|
+
"dashboard",
|
|
7192
|
+
"doctor",
|
|
7193
|
+
"serve",
|
|
7194
|
+
"version"
|
|
7195
|
+
];
|
|
7196
|
+
function suggestCommand(input) {
|
|
7197
|
+
let best = null;
|
|
7198
|
+
let bestDist = Infinity;
|
|
7199
|
+
for (const cmd of COMMANDS) {
|
|
7200
|
+
const d = levenshtein(input.toLowerCase(), cmd);
|
|
7201
|
+
if (d < bestDist && d <= Math.max(2, Math.floor(cmd.length / 2))) {
|
|
7202
|
+
bestDist = d;
|
|
7203
|
+
best = cmd;
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
return best;
|
|
7207
|
+
}
|
|
7208
|
+
function levenshtein(a, b) {
|
|
7209
|
+
const m = a.length;
|
|
7210
|
+
const n = b.length;
|
|
7211
|
+
const dp = Array.from(
|
|
7212
|
+
{ length: m + 1 },
|
|
7213
|
+
() => Array.from({ length: n + 1 }).fill(0)
|
|
7214
|
+
);
|
|
7215
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
7216
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
7217
|
+
for (let i = 1; i <= m; i++) {
|
|
7218
|
+
for (let j = 1; j <= n; j++) {
|
|
7219
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
return dp[m][n];
|
|
7223
|
+
}
|
|
7224
|
+
|
|
6742
7225
|
// src/cli.ts
|
|
6743
7226
|
var HELP = `
|
|
6744
7227
|
@hua-labs/tap \u2014 Cross-model AI agent communication setup
|
|
6745
7228
|
|
|
6746
7229
|
Usage:
|
|
6747
|
-
tap
|
|
7230
|
+
tap <command> [options]
|
|
6748
7231
|
|
|
6749
7232
|
Commands:
|
|
6750
7233
|
init Initialize comms directory and state
|
|
6751
|
-
init-worktree Set up a new git worktree with tap
|
|
7234
|
+
init-worktree Set up a new git worktree with tap
|
|
6752
7235
|
add <runtime> Add a runtime instance (claude, codex, gemini)
|
|
6753
7236
|
remove <instance> Remove an instance and rollback config
|
|
6754
7237
|
status Show installed instances and bridge status
|
|
@@ -6758,7 +7241,7 @@ Commands:
|
|
|
6758
7241
|
comms <pull|push> Sync comms directory with remote repo
|
|
6759
7242
|
dashboard Show unified ops dashboard
|
|
6760
7243
|
doctor Diagnose tap infrastructure health
|
|
6761
|
-
serve Start tap
|
|
7244
|
+
serve Start tap MCP server (stdio)
|
|
6762
7245
|
version Show version
|
|
6763
7246
|
|
|
6764
7247
|
Options:
|
|
@@ -6859,15 +7342,21 @@ async function main() {
|
|
|
6859
7342
|
process.exit(exitCode(serveResult));
|
|
6860
7343
|
break;
|
|
6861
7344
|
}
|
|
6862
|
-
default:
|
|
7345
|
+
default: {
|
|
7346
|
+
const suggestion = suggestCommand(command);
|
|
7347
|
+
const hint = suggestion ? `
|
|
7348
|
+
|
|
7349
|
+
Did you mean: tap ${suggestion}?` : "\n\nRun tap --help for a list of commands.";
|
|
6863
7350
|
result = {
|
|
6864
7351
|
ok: false,
|
|
6865
7352
|
command: "unknown",
|
|
6866
7353
|
code: "TAP_INVALID_ARGUMENT",
|
|
6867
|
-
message: `Unknown command: ${command}`,
|
|
7354
|
+
message: `Unknown command: ${command}${hint}`,
|
|
6868
7355
|
warnings: [],
|
|
6869
|
-
data: { requestedCommand: command }
|
|
7356
|
+
data: { requestedCommand: command, suggestion }
|
|
6870
7357
|
};
|
|
7358
|
+
break;
|
|
7359
|
+
}
|
|
6871
7360
|
}
|
|
6872
7361
|
} catch (err) {
|
|
6873
7362
|
const message = err instanceof Error ? err.message : String(err);
|