@hua-labs/tap 0.2.2 → 0.2.4
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/bridges/codex-app-server-auth-gateway.mjs +11 -3
- package/dist/bridges/codex-app-server-auth-gateway.mjs.map +1 -1
- package/dist/bridges/codex-app-server-bridge.d.mts +1 -0
- package/dist/bridges/codex-app-server-bridge.mjs +16 -13
- package/dist/bridges/codex-app-server-bridge.mjs.map +1 -1
- package/dist/bridges/codex-bridge-runner.mjs +21 -17
- package/dist/bridges/codex-bridge-runner.mjs.map +1 -1
- package/dist/cli.mjs +497 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +186 -92
- package/dist/index.mjs.map +1 -1
- package/dist/mcp-server.mjs +185 -185
- package/dist/mcp-server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/commands/init.ts
|
|
2
2
|
import * as fs6 from "fs";
|
|
3
3
|
import * as path6 from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
5
|
|
|
6
6
|
// src/state.ts
|
|
7
7
|
import * as fs3 from "fs";
|
|
@@ -23,9 +23,16 @@ function detectPlatform() {
|
|
|
23
23
|
return process.platform;
|
|
24
24
|
}
|
|
25
25
|
var _noGitWarned = false;
|
|
26
|
+
var _loggedWarnings = /* @__PURE__ */ new Set();
|
|
26
27
|
function _setNoGitWarned() {
|
|
27
28
|
_noGitWarned = true;
|
|
28
29
|
}
|
|
30
|
+
function resetLoggedWarnings() {
|
|
31
|
+
_loggedWarnings.clear();
|
|
32
|
+
}
|
|
33
|
+
function wasWarningLogged(message) {
|
|
34
|
+
return _loggedWarnings.has(message);
|
|
35
|
+
}
|
|
29
36
|
function findRepoRoot(startDir = process.cwd()) {
|
|
30
37
|
let dir = path.resolve(startDir);
|
|
31
38
|
while (true) {
|
|
@@ -33,8 +40,8 @@ function findRepoRoot(startDir = process.cwd()) {
|
|
|
33
40
|
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
34
41
|
if (!_noGitWarned) {
|
|
35
42
|
_setNoGitWarned();
|
|
36
|
-
|
|
37
|
-
"No .git directory found. Resolved
|
|
43
|
+
log(
|
|
44
|
+
"No .git directory found. Resolved tap root via package.json. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
38
45
|
);
|
|
39
46
|
}
|
|
40
47
|
return dir;
|
|
@@ -45,8 +52,8 @@ function findRepoRoot(startDir = process.cwd()) {
|
|
|
45
52
|
}
|
|
46
53
|
if (!_noGitWarned) {
|
|
47
54
|
_setNoGitWarned();
|
|
48
|
-
|
|
49
|
-
"No git repository or package.json found. Using current directory as root.
|
|
55
|
+
log(
|
|
56
|
+
"No git repository or package.json found. Using the current directory as tap root. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
50
57
|
);
|
|
51
58
|
}
|
|
52
59
|
return process.cwd();
|
|
@@ -101,7 +108,9 @@ function logSuccess(message) {
|
|
|
101
108
|
if (!_jsonMode) console.log(` + ${message}`);
|
|
102
109
|
}
|
|
103
110
|
function logWarn(message) {
|
|
104
|
-
if (
|
|
111
|
+
if (_jsonMode) return;
|
|
112
|
+
_loggedWarnings.add(message);
|
|
113
|
+
console.log(` ! ${message}`);
|
|
105
114
|
}
|
|
106
115
|
function logError(message) {
|
|
107
116
|
if (!_jsonMode) console.error(` x ${message}`);
|
|
@@ -111,6 +120,16 @@ function logHeader(message) {
|
|
|
111
120
|
${message}
|
|
112
121
|
`);
|
|
113
122
|
}
|
|
123
|
+
function parseIntFlag(value, name, min, max) {
|
|
124
|
+
if (value === void 0) return void 0;
|
|
125
|
+
const parsed = Number(value);
|
|
126
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
127
|
+
throw new RangeError(
|
|
128
|
+
`Invalid ${name}: ${value}. Must be an integer between ${min} and ${max}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return parsed;
|
|
132
|
+
}
|
|
114
133
|
function resolveInstanceId(identifier, state) {
|
|
115
134
|
if (state.instances[identifier]) {
|
|
116
135
|
return { ok: true, instanceId: identifier };
|
|
@@ -160,8 +179,8 @@ function findRepoRoot2(startDir = process.cwd()) {
|
|
|
160
179
|
if (fs2.existsSync(path2.join(dir, "package.json"))) {
|
|
161
180
|
if (!_noGitWarned) {
|
|
162
181
|
_setNoGitWarned();
|
|
163
|
-
|
|
164
|
-
"
|
|
182
|
+
log(
|
|
183
|
+
"No .git directory found. Resolved tap root via package.json. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
165
184
|
);
|
|
166
185
|
}
|
|
167
186
|
return dir;
|
|
@@ -172,8 +191,8 @@ function findRepoRoot2(startDir = process.cwd()) {
|
|
|
172
191
|
}
|
|
173
192
|
if (!_noGitWarned) {
|
|
174
193
|
_setNoGitWarned();
|
|
175
|
-
|
|
176
|
-
"
|
|
194
|
+
log(
|
|
195
|
+
"No git repository or package.json found. Using the current directory as tap root. That's fine outside git; use --comms-dir to choose a different comms location."
|
|
177
196
|
);
|
|
178
197
|
}
|
|
179
198
|
return process.cwd();
|
|
@@ -187,7 +206,7 @@ function loadJsonFile(filePath) {
|
|
|
187
206
|
return null;
|
|
188
207
|
}
|
|
189
208
|
}
|
|
190
|
-
function
|
|
209
|
+
function loadSharedConfig(repoRoot) {
|
|
191
210
|
return loadJsonFile(path2.join(repoRoot, SHARED_CONFIG_FILE));
|
|
192
211
|
}
|
|
193
212
|
function loadLocalConfig(repoRoot) {
|
|
@@ -211,7 +230,7 @@ function loadLegacyShellConfig(repoRoot) {
|
|
|
211
230
|
}
|
|
212
231
|
function resolveConfig(overrides = {}, startDir) {
|
|
213
232
|
const repoRoot = findRepoRoot2(startDir);
|
|
214
|
-
const shared =
|
|
233
|
+
const shared = loadSharedConfig(repoRoot) ?? {};
|
|
215
234
|
const local = loadLocalConfig(repoRoot) ?? {};
|
|
216
235
|
const legacy = loadLegacyShellConfig(repoRoot) ?? {};
|
|
217
236
|
const sources = {
|
|
@@ -754,7 +773,39 @@ function parsePermissionMode(args) {
|
|
|
754
773
|
}
|
|
755
774
|
return "safe";
|
|
756
775
|
}
|
|
776
|
+
var INIT_HELP = `
|
|
777
|
+
Usage:
|
|
778
|
+
tap-comms init [options]
|
|
779
|
+
|
|
780
|
+
Description:
|
|
781
|
+
Initialize the tap-comms directory structure, state file, and permissions.
|
|
782
|
+
Optionally clone a shared comms repository.
|
|
783
|
+
|
|
784
|
+
Options:
|
|
785
|
+
--comms-dir <path> Override comms directory (default: tap-comms/)
|
|
786
|
+
--comms-repo <url> Clone a shared comms git repo into comms directory
|
|
787
|
+
--permissions <mode> Permission mode: safe (default) or full
|
|
788
|
+
--force Re-initialize even if already set up
|
|
789
|
+
--help, -h Show help
|
|
790
|
+
|
|
791
|
+
Examples:
|
|
792
|
+
npx @hua-labs/tap init
|
|
793
|
+
npx @hua-labs/tap init --permissions full
|
|
794
|
+
npx @hua-labs/tap init --comms-repo https://github.com/org/comms.git
|
|
795
|
+
npx @hua-labs/tap init --comms-dir /shared/comms --force
|
|
796
|
+
`.trim();
|
|
757
797
|
async function initCommand(args) {
|
|
798
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
799
|
+
log(INIT_HELP);
|
|
800
|
+
return {
|
|
801
|
+
ok: true,
|
|
802
|
+
command: "init",
|
|
803
|
+
code: "TAP_NO_OP",
|
|
804
|
+
message: INIT_HELP,
|
|
805
|
+
warnings: [],
|
|
806
|
+
data: {}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
758
809
|
const repoRoot = findRepoRoot();
|
|
759
810
|
const commsDir = resolveCommsDir(args, repoRoot);
|
|
760
811
|
const permMode = parsePermissionMode(args);
|
|
@@ -791,10 +842,19 @@ async function initCommand(args) {
|
|
|
791
842
|
} else {
|
|
792
843
|
log(`Cloning comms repo: ${commsRepoUrl}`);
|
|
793
844
|
try {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
845
|
+
const cloneResult = spawnSync(
|
|
846
|
+
"git",
|
|
847
|
+
["clone", commsRepoUrl, commsDir],
|
|
848
|
+
{
|
|
849
|
+
stdio: "pipe",
|
|
850
|
+
encoding: "utf-8"
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
if (cloneResult.status !== 0) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
cloneResult.stderr || `git clone exited with code ${cloneResult.status}`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
798
858
|
logSuccess(`Cloned comms repo to ${commsDir}`);
|
|
799
859
|
} catch (err) {
|
|
800
860
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -811,7 +871,7 @@ async function initCommand(args) {
|
|
|
811
871
|
}
|
|
812
872
|
}
|
|
813
873
|
{
|
|
814
|
-
const sharedConfig =
|
|
874
|
+
const sharedConfig = loadSharedConfig(repoRoot) ?? {};
|
|
815
875
|
let configChanged = false;
|
|
816
876
|
if (commsRepoUrl) {
|
|
817
877
|
sharedConfig.commsRepoUrl = commsRepoUrl;
|
|
@@ -903,17 +963,17 @@ ${entry}
|
|
|
903
963
|
// src/adapters/claude.ts
|
|
904
964
|
import * as fs8 from "fs";
|
|
905
965
|
import * as path8 from "path";
|
|
906
|
-
import { execSync
|
|
966
|
+
import { execSync } from "child_process";
|
|
907
967
|
|
|
908
968
|
// src/adapters/common.ts
|
|
909
969
|
import * as fs7 from "fs";
|
|
910
970
|
import * as os2 from "os";
|
|
911
971
|
import * as path7 from "path";
|
|
912
|
-
import { spawnSync } from "child_process";
|
|
972
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
913
973
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
914
974
|
function probeCommand(candidates) {
|
|
915
975
|
for (const candidate of candidates) {
|
|
916
|
-
const result =
|
|
976
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
917
977
|
encoding: "utf-8",
|
|
918
978
|
shell: process.platform === "win32"
|
|
919
979
|
});
|
|
@@ -991,7 +1051,7 @@ function findPreferredBunCommand() {
|
|
|
991
1051
|
const candidates = process.platform === "win32" ? [path7.join(home, ".bun", "bin", "bun.exe"), "bun", "bun.cmd"] : [path7.join(home, ".bun", "bin", "bun"), "bun"];
|
|
992
1052
|
for (const candidate of candidates) {
|
|
993
1053
|
if (path7.isAbsolute(candidate) && !fs7.existsSync(candidate)) continue;
|
|
994
|
-
const result =
|
|
1054
|
+
const result = spawnSync2(candidate, ["--version"], {
|
|
995
1055
|
encoding: "utf-8",
|
|
996
1056
|
shell: process.platform === "win32"
|
|
997
1057
|
});
|
|
@@ -1022,18 +1082,18 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1022
1082
|
return { command: null, args: [], env, sourcePath, warnings, issues };
|
|
1023
1083
|
}
|
|
1024
1084
|
const isBundled = sourcePath.endsWith(".mjs");
|
|
1085
|
+
const isEphemeralSource = isEphemeralPath(sourcePath);
|
|
1025
1086
|
let command = bunCommand;
|
|
1026
1087
|
let args = [toForwardSlashPath(sourcePath)];
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1088
|
+
if (isEphemeralSource && isBundled) {
|
|
1089
|
+
command = "npx";
|
|
1090
|
+
args = ["@hua-labs/tap", "serve"];
|
|
1091
|
+
warnings.push(
|
|
1092
|
+
"Detected npx cache path. Using `npx @hua-labs/tap serve` as stable MCP launcher."
|
|
1093
|
+
);
|
|
1094
|
+
} else if (!command && isBundled) {
|
|
1029
1095
|
const isEphemeralNode = isEphemeralPath(process.execPath);
|
|
1030
|
-
if (
|
|
1031
|
-
command = "npx";
|
|
1032
|
-
args = ["@hua-labs/tap", "serve"];
|
|
1033
|
-
warnings.push(
|
|
1034
|
-
"Detected npx cache path. Using `npx @hua-labs/tap serve` as stable MCP launcher."
|
|
1035
|
-
);
|
|
1036
|
-
} else if (isEphemeralNode) {
|
|
1096
|
+
if (isEphemeralNode) {
|
|
1037
1097
|
command = "node";
|
|
1038
1098
|
warnings.push(
|
|
1039
1099
|
"Detected ephemeral node path. Using `node` from PATH for MCP config stability."
|
|
@@ -1041,11 +1101,9 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1041
1101
|
} else {
|
|
1042
1102
|
command = toForwardSlashPath(process.execPath);
|
|
1043
1103
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1104
|
+
warnings.push(
|
|
1105
|
+
"bun not found; using node to run the compiled MCP server. Install bun for better performance."
|
|
1106
|
+
);
|
|
1049
1107
|
}
|
|
1050
1108
|
if (!command) {
|
|
1051
1109
|
issues.push(
|
|
@@ -1070,7 +1128,7 @@ function findMcpJsonPath(ctx) {
|
|
|
1070
1128
|
}
|
|
1071
1129
|
function findClaudeCommand() {
|
|
1072
1130
|
try {
|
|
1073
|
-
|
|
1131
|
+
execSync("claude --version", { stdio: "pipe" });
|
|
1074
1132
|
return "claude";
|
|
1075
1133
|
} catch {
|
|
1076
1134
|
return null;
|
|
@@ -1419,13 +1477,12 @@ function verifyManagedToml(content, ctx, configPath) {
|
|
|
1419
1477
|
});
|
|
1420
1478
|
}
|
|
1421
1479
|
if (mainTable && managed.command) {
|
|
1480
|
+
const expectedArgs = managed.args.map((a) => `"${a.replace(/\\/g, "\\\\")}"`).join(", ");
|
|
1422
1481
|
checks.push({
|
|
1423
1482
|
name: "Managed command configured",
|
|
1424
1483
|
passed: mainTable.includes(
|
|
1425
1484
|
`command = "${managed.command.replace(/\\/g, "\\\\")}"`
|
|
1426
|
-
) && mainTable.includes(
|
|
1427
|
-
`args = ["${managed.args[0]?.replace(/\\/g, "\\\\") ?? ""}"]`
|
|
1428
|
-
),
|
|
1485
|
+
) && mainTable.includes(`args = [${expectedArgs}]`),
|
|
1429
1486
|
message: "Managed tap-comms command/args do not match expected values"
|
|
1430
1487
|
});
|
|
1431
1488
|
}
|
|
@@ -1932,13 +1989,13 @@ import * as fs13 from "fs";
|
|
|
1932
1989
|
import * as net from "net";
|
|
1933
1990
|
import * as path13 from "path";
|
|
1934
1991
|
import { randomBytes } from "crypto";
|
|
1935
|
-
import { spawn, spawnSync as
|
|
1992
|
+
import { spawn, spawnSync as spawnSync3, execSync as execSync3 } from "child_process";
|
|
1936
1993
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1937
1994
|
|
|
1938
1995
|
// src/runtime/resolve-node.ts
|
|
1939
1996
|
import * as fs12 from "fs";
|
|
1940
1997
|
import * as path12 from "path";
|
|
1941
|
-
import { execSync as
|
|
1998
|
+
import { execSync as execSync2 } from "child_process";
|
|
1942
1999
|
function readNodeVersion(repoRoot) {
|
|
1943
2000
|
const nvFile = path12.join(repoRoot, ".node-version");
|
|
1944
2001
|
if (!fs12.existsSync(nvFile)) return null;
|
|
@@ -1981,7 +2038,7 @@ function probeFnmNode(desiredVersion) {
|
|
|
1981
2038
|
);
|
|
1982
2039
|
if (!fs12.existsSync(candidate)) continue;
|
|
1983
2040
|
try {
|
|
1984
|
-
const v =
|
|
2041
|
+
const v = execSync2(`"${candidate}" --version`, {
|
|
1985
2042
|
encoding: "utf-8",
|
|
1986
2043
|
timeout: 5e3
|
|
1987
2044
|
}).trim();
|
|
@@ -1995,7 +2052,7 @@ function probeFnmNode(desiredVersion) {
|
|
|
1995
2052
|
}
|
|
1996
2053
|
function detectNodeMajorVersion(command) {
|
|
1997
2054
|
try {
|
|
1998
|
-
const version2 =
|
|
2055
|
+
const version2 = execSync2(`"${command}" --version`, {
|
|
1999
2056
|
encoding: "utf-8",
|
|
2000
2057
|
timeout: 5e3
|
|
2001
2058
|
}).trim();
|
|
@@ -2009,7 +2066,7 @@ function checkStripTypesSupport(command) {
|
|
|
2009
2066
|
const major = detectNodeMajorVersion(command);
|
|
2010
2067
|
if (major !== null && major >= 22) return true;
|
|
2011
2068
|
try {
|
|
2012
|
-
|
|
2069
|
+
execSync2(`"${command}" --experimental-strip-types -e ""`, {
|
|
2013
2070
|
timeout: 5e3,
|
|
2014
2071
|
stdio: "pipe"
|
|
2015
2072
|
});
|
|
@@ -2100,7 +2157,7 @@ var APP_SERVER_HEALTH_TIMEOUT_MS = 1500;
|
|
|
2100
2157
|
var APP_SERVER_START_TIMEOUT_MS = 2e4;
|
|
2101
2158
|
var APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5e3;
|
|
2102
2159
|
var APP_SERVER_HEALTH_RETRY_MS = 250;
|
|
2103
|
-
var
|
|
2160
|
+
var AUTH_SUBPROTOCOL_PREFIX = "tap-auth-";
|
|
2104
2161
|
var APP_SERVER_AUTH_FILE_MODE = 384;
|
|
2105
2162
|
function appServerLogFilePath(stateDir, instanceId) {
|
|
2106
2163
|
return path13.join(stateDir, "logs", `app-server-${instanceId}.log`);
|
|
@@ -2161,8 +2218,11 @@ function resolvePowerShellCommand() {
|
|
|
2161
2218
|
function resolveAuthGatewayScript(repoRoot) {
|
|
2162
2219
|
const moduleDir = path13.dirname(fileURLToPath4(import.meta.url));
|
|
2163
2220
|
const candidates = [
|
|
2164
|
-
|
|
2165
|
-
path13.join(moduleDir, "
|
|
2221
|
+
// Bundled: dist/bridges/ sibling (npm install / built package)
|
|
2222
|
+
path13.join(moduleDir, "bridges", "codex-app-server-auth-gateway.mjs"),
|
|
2223
|
+
// Source: src/bridges/ sibling (monorepo dev with ts runner)
|
|
2224
|
+
path13.join(moduleDir, "bridges", "codex-app-server-auth-gateway.ts"),
|
|
2225
|
+
// Monorepo dist fallback
|
|
2166
2226
|
path13.join(
|
|
2167
2227
|
repoRoot,
|
|
2168
2228
|
"packages",
|
|
@@ -2215,10 +2275,8 @@ async function allocateLoopbackPort(hostname) {
|
|
|
2215
2275
|
});
|
|
2216
2276
|
});
|
|
2217
2277
|
}
|
|
2218
|
-
function buildProtectedAppServerUrl(publicUrl,
|
|
2219
|
-
|
|
2220
|
-
url.searchParams.set(APP_SERVER_AUTH_QUERY_PARAM, token);
|
|
2221
|
-
return url.toString().replace(/\/(?=\?|$)/, "");
|
|
2278
|
+
function buildProtectedAppServerUrl(publicUrl, _token) {
|
|
2279
|
+
return publicUrl;
|
|
2222
2280
|
}
|
|
2223
2281
|
function readGatewayTokenFromPath(tokenPath) {
|
|
2224
2282
|
return fs13.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -2333,7 +2391,7 @@ async function createManagedAppServerAuth(options) {
|
|
|
2333
2391
|
throw new Error("Failed to spawn app-server auth gateway");
|
|
2334
2392
|
}
|
|
2335
2393
|
return {
|
|
2336
|
-
mode: "
|
|
2394
|
+
mode: "subprotocol",
|
|
2337
2395
|
protectedUrl,
|
|
2338
2396
|
upstreamUrl: upstreamUrl.toString().replace(/\/$/, ""),
|
|
2339
2397
|
tokenPath,
|
|
@@ -2447,7 +2505,7 @@ function findListeningProcessId(url, platform) {
|
|
|
2447
2505
|
if (port == null || !Number.isFinite(port)) {
|
|
2448
2506
|
return null;
|
|
2449
2507
|
}
|
|
2450
|
-
const result =
|
|
2508
|
+
const result = spawnSync3(
|
|
2451
2509
|
resolvePowerShellCommand(),
|
|
2452
2510
|
[
|
|
2453
2511
|
"-NoLogo",
|
|
@@ -2520,7 +2578,7 @@ async function findNextAvailableAppServerPort(state, baseUrl, basePort = 4501, e
|
|
|
2520
2578
|
`Failed to find a free app-server port starting at ${basePort}`
|
|
2521
2579
|
);
|
|
2522
2580
|
}
|
|
2523
|
-
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS) {
|
|
2581
|
+
async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_MS, gatewayToken) {
|
|
2524
2582
|
const WebSocket = getWebSocketCtor();
|
|
2525
2583
|
if (!WebSocket) {
|
|
2526
2584
|
return false;
|
|
@@ -2542,7 +2600,8 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
2542
2600
|
};
|
|
2543
2601
|
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
2544
2602
|
try {
|
|
2545
|
-
|
|
2603
|
+
const protocols = gatewayToken ? [`${AUTH_SUBPROTOCOL_PREFIX}${gatewayToken}`] : void 0;
|
|
2604
|
+
socket = new WebSocket(url, protocols);
|
|
2546
2605
|
socket.addEventListener("open", () => finish(true), { once: true });
|
|
2547
2606
|
socket.addEventListener("error", () => finish(false), { once: true });
|
|
2548
2607
|
socket.addEventListener("close", () => finish(false), { once: true });
|
|
@@ -2551,10 +2610,14 @@ async function checkAppServerHealth(url, timeoutMs = APP_SERVER_HEALTH_TIMEOUT_M
|
|
|
2551
2610
|
}
|
|
2552
2611
|
});
|
|
2553
2612
|
}
|
|
2554
|
-
async function waitForAppServerHealth(url, timeoutMs) {
|
|
2613
|
+
async function waitForAppServerHealth(url, timeoutMs, gatewayToken) {
|
|
2555
2614
|
const deadline = Date.now() + timeoutMs;
|
|
2556
2615
|
while (Date.now() < deadline) {
|
|
2557
|
-
if (await checkAppServerHealth(
|
|
2616
|
+
if (await checkAppServerHealth(
|
|
2617
|
+
url,
|
|
2618
|
+
APP_SERVER_HEALTH_TIMEOUT_MS,
|
|
2619
|
+
gatewayToken
|
|
2620
|
+
)) {
|
|
2558
2621
|
return true;
|
|
2559
2622
|
}
|
|
2560
2623
|
await delay(APP_SERVER_HEALTH_RETRY_MS);
|
|
@@ -2567,7 +2630,7 @@ async function terminateProcess(pid, platform) {
|
|
|
2567
2630
|
}
|
|
2568
2631
|
try {
|
|
2569
2632
|
if (platform === "win32") {
|
|
2570
|
-
|
|
2633
|
+
execSync3(`taskkill /PID ${pid} /F /T`, { stdio: "pipe" });
|
|
2571
2634
|
} else {
|
|
2572
2635
|
process.kill(pid, "SIGTERM");
|
|
2573
2636
|
await delay(2e3);
|
|
@@ -2819,8 +2882,9 @@ Or start it manually:
|
|
|
2819
2882
|
throw new Error("Tap auth gateway token is missing after startup.");
|
|
2820
2883
|
}
|
|
2821
2884
|
const gatewayHealthy = await waitForAppServerHealth(
|
|
2822
|
-
|
|
2823
|
-
APP_SERVER_GATEWAY_START_TIMEOUT_MS
|
|
2885
|
+
effectiveUrl,
|
|
2886
|
+
APP_SERVER_GATEWAY_START_TIMEOUT_MS,
|
|
2887
|
+
gatewayToken
|
|
2824
2888
|
);
|
|
2825
2889
|
if (!gatewayHealthy) {
|
|
2826
2890
|
await terminateProcess(pid, options.platform);
|
|
@@ -3172,8 +3236,49 @@ function getBridgeStatus(stateDir, instanceId) {
|
|
|
3172
3236
|
}
|
|
3173
3237
|
|
|
3174
3238
|
// src/commands/add.ts
|
|
3239
|
+
var ADD_HELP = `
|
|
3240
|
+
Usage:
|
|
3241
|
+
tap-comms add <claude|codex|gemini> [options]
|
|
3242
|
+
|
|
3243
|
+
Description:
|
|
3244
|
+
Install a runtime instance and configure it to use tap-comms.
|
|
3245
|
+
|
|
3246
|
+
Options:
|
|
3247
|
+
--name <name> Instance name (default: runtime name)
|
|
3248
|
+
--port <port> Port for app-server bridge
|
|
3249
|
+
--agent-name <name> Agent display name for bridge identification
|
|
3250
|
+
--force Re-install even if already configured
|
|
3251
|
+
--headless Enable headless reviewer mode (requires --name)
|
|
3252
|
+
--role <role> Headless role: reviewer, validator, long-running
|
|
3253
|
+
--help, -h Show help
|
|
3254
|
+
|
|
3255
|
+
Examples:
|
|
3256
|
+
npx @hua-labs/tap add claude
|
|
3257
|
+
npx @hua-labs/tap add codex --name reviewer --port 4501 --headless --role reviewer
|
|
3258
|
+
`.trim();
|
|
3259
|
+
function normalizeAgentName(value) {
|
|
3260
|
+
if (typeof value !== "string") {
|
|
3261
|
+
return null;
|
|
3262
|
+
}
|
|
3263
|
+
const trimmed = value.trim();
|
|
3264
|
+
return trimmed ? trimmed : null;
|
|
3265
|
+
}
|
|
3266
|
+
function resolveAgentName2(options) {
|
|
3267
|
+
return normalizeAgentName(options.explicit) ?? normalizeAgentName(options.stored) ?? normalizeAgentName(options.env) ?? normalizeAgentName(options.fallback) ?? null;
|
|
3268
|
+
}
|
|
3175
3269
|
async function addCommand(args) {
|
|
3176
3270
|
const { positional, flags } = parseArgs(args);
|
|
3271
|
+
if (flags["help"] === true || flags["h"] === true) {
|
|
3272
|
+
log(ADD_HELP);
|
|
3273
|
+
return {
|
|
3274
|
+
ok: true,
|
|
3275
|
+
command: "add",
|
|
3276
|
+
code: "TAP_NO_OP",
|
|
3277
|
+
message: ADD_HELP,
|
|
3278
|
+
warnings: [],
|
|
3279
|
+
data: {}
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3177
3282
|
const runtimeArg = positional[0];
|
|
3178
3283
|
if (!runtimeArg) {
|
|
3179
3284
|
return {
|
|
@@ -3199,8 +3304,10 @@ async function addCommand(args) {
|
|
|
3199
3304
|
const instanceName = typeof flags["name"] === "string" ? flags["name"] : void 0;
|
|
3200
3305
|
const instanceId = buildInstanceId(runtime, instanceName);
|
|
3201
3306
|
const portStr = typeof flags["port"] === "string" ? flags["port"] : void 0;
|
|
3202
|
-
const port = portStr ?
|
|
3203
|
-
const agentNameFlag =
|
|
3307
|
+
const port = portStr ? Number(portStr) : null;
|
|
3308
|
+
const agentNameFlag = normalizeAgentName(
|
|
3309
|
+
typeof flags["agent-name"] === "string" ? flags["agent-name"] : null
|
|
3310
|
+
);
|
|
3204
3311
|
const force = flags["force"] === true;
|
|
3205
3312
|
const headlessFlag = flags["headless"] === true;
|
|
3206
3313
|
const roleArg = typeof flags["role"] === "string" ? flags["role"] : void 0;
|
|
@@ -3235,20 +3342,21 @@ async function addCommand(args) {
|
|
|
3235
3342
|
maxRounds: 5,
|
|
3236
3343
|
qualitySeverityFloor: "high"
|
|
3237
3344
|
} : null;
|
|
3238
|
-
if (portStr && (port === null || isNaN(port))) {
|
|
3345
|
+
if (portStr && (port === null || isNaN(port) || port < 1 || port > 65535)) {
|
|
3239
3346
|
return {
|
|
3240
3347
|
ok: false,
|
|
3241
3348
|
command: "add",
|
|
3242
3349
|
runtime,
|
|
3243
3350
|
instanceId,
|
|
3244
3351
|
code: "TAP_INVALID_ARGUMENT",
|
|
3245
|
-
message: `Invalid port: ${portStr}
|
|
3352
|
+
message: `Invalid port: ${portStr}. Must be between 1 and 65535.`,
|
|
3246
3353
|
warnings: [],
|
|
3247
3354
|
data: {}
|
|
3248
3355
|
};
|
|
3249
3356
|
}
|
|
3250
3357
|
const repoRoot = findRepoRoot();
|
|
3251
3358
|
const state = loadState(repoRoot);
|
|
3359
|
+
const adapter = getAdapter(runtime);
|
|
3252
3360
|
if (!state) {
|
|
3253
3361
|
return {
|
|
3254
3362
|
ok: false,
|
|
@@ -3261,7 +3369,39 @@ async function addCommand(args) {
|
|
|
3261
3369
|
data: {}
|
|
3262
3370
|
};
|
|
3263
3371
|
}
|
|
3264
|
-
|
|
3372
|
+
const existingInstance = state.instances[instanceId];
|
|
3373
|
+
const mode = adapter.bridgeMode();
|
|
3374
|
+
const envAgentName = normalizeAgentName(
|
|
3375
|
+
process.env.TAP_AGENT_NAME ?? process.env.CODEX_TAP_AGENT_NAME
|
|
3376
|
+
);
|
|
3377
|
+
const defaultAgentName = mode === "app-server" ? instanceId : null;
|
|
3378
|
+
const resolvedAgentName = resolveAgentName2({
|
|
3379
|
+
explicit: agentNameFlag,
|
|
3380
|
+
env: envAgentName,
|
|
3381
|
+
stored: existingInstance?.agentName ?? null,
|
|
3382
|
+
fallback: defaultAgentName
|
|
3383
|
+
});
|
|
3384
|
+
if (existingInstance?.installed && !force) {
|
|
3385
|
+
if (resolvedAgentName !== existingInstance.agentName) {
|
|
3386
|
+
const updatedState = updateInstanceState(state, instanceId, {
|
|
3387
|
+
...existingInstance,
|
|
3388
|
+
agentName: resolvedAgentName
|
|
3389
|
+
});
|
|
3390
|
+
saveState(repoRoot, updatedState);
|
|
3391
|
+
return {
|
|
3392
|
+
ok: true,
|
|
3393
|
+
command: "add",
|
|
3394
|
+
runtime,
|
|
3395
|
+
instanceId,
|
|
3396
|
+
code: "TAP_ADD_OK",
|
|
3397
|
+
message: resolvedAgentName === null ? `${instanceId} updated` : `${instanceId} agent name updated to "${resolvedAgentName}".`,
|
|
3398
|
+
warnings: [],
|
|
3399
|
+
data: {
|
|
3400
|
+
updatedFields: ["agentName"],
|
|
3401
|
+
agentName: resolvedAgentName
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3265
3405
|
return {
|
|
3266
3406
|
ok: true,
|
|
3267
3407
|
command: "add",
|
|
@@ -3291,14 +3431,12 @@ async function addCommand(args) {
|
|
|
3291
3431
|
logHeader(`@hua-labs/tap add ${instanceId}`);
|
|
3292
3432
|
if (instanceName) log(`Instance name: ${instanceName}`);
|
|
3293
3433
|
if (port !== null) log(`Port: ${port}`);
|
|
3294
|
-
|
|
3295
|
-
const effectiveAgentName = agentNameFlag ?? existingAgentName ?? void 0;
|
|
3434
|
+
if (resolvedAgentName) log(`Agent name: ${resolvedAgentName}`);
|
|
3296
3435
|
const ctx = {
|
|
3297
3436
|
...createAdapterContext(state.commsDir, repoRoot),
|
|
3298
3437
|
instanceId,
|
|
3299
|
-
agentName:
|
|
3438
|
+
agentName: resolvedAgentName ?? void 0
|
|
3300
3439
|
};
|
|
3301
|
-
const adapter = getAdapter(runtime);
|
|
3302
3440
|
const warnings = [];
|
|
3303
3441
|
log("Probing runtime...");
|
|
3304
3442
|
const probe = await adapter.probe(ctx);
|
|
@@ -3378,7 +3516,6 @@ async function addCommand(args) {
|
|
|
3378
3516
|
);
|
|
3379
3517
|
}
|
|
3380
3518
|
let bridge = null;
|
|
3381
|
-
const mode = adapter.bridgeMode();
|
|
3382
3519
|
if (mode === "app-server") {
|
|
3383
3520
|
const bridgeScript = adapter.resolveBridgeScript?.(ctx);
|
|
3384
3521
|
if (!bridgeScript) {
|
|
@@ -3386,36 +3523,34 @@ async function addCommand(args) {
|
|
|
3386
3523
|
warnings.push("Bridge script not found. Run bridge manually.");
|
|
3387
3524
|
} else {
|
|
3388
3525
|
const { config: resolvedCfg } = resolveConfig({}, repoRoot);
|
|
3389
|
-
{
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
warnings.push(`Bridge not started: ${msg}`);
|
|
3411
|
-
}
|
|
3526
|
+
log(`Starting bridge: ${bridgeScript}`);
|
|
3527
|
+
try {
|
|
3528
|
+
bridge = await startBridge({
|
|
3529
|
+
instanceId,
|
|
3530
|
+
runtime,
|
|
3531
|
+
stateDir: ctx.stateDir,
|
|
3532
|
+
commsDir: ctx.commsDir,
|
|
3533
|
+
bridgeScript,
|
|
3534
|
+
platform: ctx.platform,
|
|
3535
|
+
agentName: resolvedAgentName ?? void 0,
|
|
3536
|
+
runtimeCommand: resolvedCfg.runtimeCommand,
|
|
3537
|
+
appServerUrl: resolvedCfg.appServerUrl,
|
|
3538
|
+
repoRoot,
|
|
3539
|
+
port: port ?? void 0,
|
|
3540
|
+
headless
|
|
3541
|
+
});
|
|
3542
|
+
logSuccess(`Bridge started (PID: ${bridge.pid})`);
|
|
3543
|
+
} catch (err) {
|
|
3544
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3545
|
+
logWarn(`Bridge not started: ${msg}`);
|
|
3546
|
+
warnings.push(`Bridge not started: ${msg}`);
|
|
3412
3547
|
}
|
|
3413
3548
|
}
|
|
3414
3549
|
}
|
|
3415
3550
|
const instanceState = {
|
|
3416
3551
|
instanceId,
|
|
3417
3552
|
runtime,
|
|
3418
|
-
agentName:
|
|
3553
|
+
agentName: resolvedAgentName,
|
|
3419
3554
|
port,
|
|
3420
3555
|
installed: true,
|
|
3421
3556
|
configPath: probe.configPath ?? "",
|
|
@@ -3427,7 +3562,7 @@ async function addCommand(args) {
|
|
|
3427
3562
|
lastVerifiedAt: verify.ok ? (/* @__PURE__ */ new Date()).toISOString() : null,
|
|
3428
3563
|
bridge,
|
|
3429
3564
|
headless,
|
|
3430
|
-
warnings: [...result.warnings, ...verify.warnings]
|
|
3565
|
+
warnings: Array.from(/* @__PURE__ */ new Set([...result.warnings, ...verify.warnings]))
|
|
3431
3566
|
};
|
|
3432
3567
|
const newState = updateInstanceState(state, instanceId, instanceState);
|
|
3433
3568
|
saveState(repoRoot, newState);
|
|
@@ -3435,6 +3570,13 @@ async function addCommand(args) {
|
|
|
3435
3570
|
if (result.restartRequired) {
|
|
3436
3571
|
logWarn(`Restart ${runtime} to pick up the new configuration.`);
|
|
3437
3572
|
}
|
|
3573
|
+
if (runtime === "claude") {
|
|
3574
|
+
log("");
|
|
3575
|
+
log("For real-time notifications:");
|
|
3576
|
+
log(" claude --dangerously-load-development-channels server:tap-comms");
|
|
3577
|
+
log("Or polling mode (tools still work):");
|
|
3578
|
+
log(" claude");
|
|
3579
|
+
}
|
|
3438
3580
|
logHeader("Done!");
|
|
3439
3581
|
return {
|
|
3440
3582
|
ok: true,
|
|
@@ -3454,6 +3596,16 @@ async function addCommand(args) {
|
|
|
3454
3596
|
}
|
|
3455
3597
|
|
|
3456
3598
|
// src/commands/status.ts
|
|
3599
|
+
var STATUS_HELP = `
|
|
3600
|
+
Usage:
|
|
3601
|
+
tap-comms status
|
|
3602
|
+
|
|
3603
|
+
Description:
|
|
3604
|
+
Show all installed instances, their bridge status, and configuration info.
|
|
3605
|
+
|
|
3606
|
+
Examples:
|
|
3607
|
+
npx @hua-labs/tap status
|
|
3608
|
+
`.trim();
|
|
3457
3609
|
function resolveStatus(inst, stateDir) {
|
|
3458
3610
|
if (!inst.installed) return "not installed";
|
|
3459
3611
|
switch (inst.bridgeMode) {
|
|
@@ -3480,7 +3632,18 @@ function instanceStatusLine(inst, status) {
|
|
|
3480
3632
|
const warns = inst.warnings.length > 0 ? ` [${inst.warnings.length} warning(s)]` : "";
|
|
3481
3633
|
return `${inst.instanceId.padEnd(20)} ${inst.runtime.padEnd(8)} ${status.padEnd(14)} ${mode.padEnd(14)}${bridgeInfo}${portStr}${restart}${warns}`;
|
|
3482
3634
|
}
|
|
3483
|
-
async function statusCommand(
|
|
3635
|
+
async function statusCommand(args) {
|
|
3636
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3637
|
+
log(STATUS_HELP);
|
|
3638
|
+
return {
|
|
3639
|
+
ok: true,
|
|
3640
|
+
command: "status",
|
|
3641
|
+
code: "TAP_NO_OP",
|
|
3642
|
+
message: STATUS_HELP,
|
|
3643
|
+
warnings: [],
|
|
3644
|
+
data: {}
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3484
3647
|
const repoRoot = findRepoRoot();
|
|
3485
3648
|
const state = loadState(repoRoot);
|
|
3486
3649
|
if (!state) {
|
|
@@ -3709,7 +3872,32 @@ function cleanEmptyParents(obj, keyPath) {
|
|
|
3709
3872
|
}
|
|
3710
3873
|
|
|
3711
3874
|
// src/commands/remove.ts
|
|
3875
|
+
var REMOVE_HELP = `
|
|
3876
|
+
Usage:
|
|
3877
|
+
tap-comms remove <instance>
|
|
3878
|
+
|
|
3879
|
+
Description:
|
|
3880
|
+
Remove a registered instance, stop its bridge, and rollback config changes.
|
|
3881
|
+
|
|
3882
|
+
Arguments:
|
|
3883
|
+
<instance> Instance ID or runtime name (e.g. claude, codex-reviewer)
|
|
3884
|
+
|
|
3885
|
+
Examples:
|
|
3886
|
+
npx @hua-labs/tap remove claude
|
|
3887
|
+
npx @hua-labs/tap remove codex-reviewer
|
|
3888
|
+
`.trim();
|
|
3712
3889
|
async function removeCommand(args) {
|
|
3890
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
3891
|
+
log(REMOVE_HELP);
|
|
3892
|
+
return {
|
|
3893
|
+
ok: true,
|
|
3894
|
+
command: "remove",
|
|
3895
|
+
code: "TAP_NO_OP",
|
|
3896
|
+
message: REMOVE_HELP,
|
|
3897
|
+
warnings: [],
|
|
3898
|
+
data: {}
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3713
3901
|
const identifier = args.find((a) => !a.startsWith("-"));
|
|
3714
3902
|
if (!identifier) {
|
|
3715
3903
|
return {
|
|
@@ -3745,8 +3933,8 @@ async function removeCommand(args) {
|
|
|
3745
3933
|
};
|
|
3746
3934
|
}
|
|
3747
3935
|
const instanceId = resolved.instanceId;
|
|
3748
|
-
const
|
|
3749
|
-
if (!
|
|
3936
|
+
const instance2 = state.instances[instanceId];
|
|
3937
|
+
if (!instance2?.installed) {
|
|
3750
3938
|
return {
|
|
3751
3939
|
ok: true,
|
|
3752
3940
|
command: "remove",
|
|
@@ -3758,7 +3946,7 @@ async function removeCommand(args) {
|
|
|
3758
3946
|
};
|
|
3759
3947
|
}
|
|
3760
3948
|
logHeader(`@hua-labs/tap remove ${instanceId}`);
|
|
3761
|
-
if (
|
|
3949
|
+
if (instance2.bridge) {
|
|
3762
3950
|
const ctx = createAdapterContext(state.commsDir, repoRoot);
|
|
3763
3951
|
const stopped = await stopBridge({
|
|
3764
3952
|
instanceId,
|
|
@@ -3771,7 +3959,7 @@ async function removeCommand(args) {
|
|
|
3771
3959
|
log(`No running bridge for ${instanceId}`);
|
|
3772
3960
|
}
|
|
3773
3961
|
}
|
|
3774
|
-
const result = await rollbackRuntime(instanceId,
|
|
3962
|
+
const result = await rollbackRuntime(instanceId, instance2);
|
|
3775
3963
|
if (result.success) {
|
|
3776
3964
|
logSuccess(`Rolled back ${result.restoredCount} artifact(s)`);
|
|
3777
3965
|
for (const f of result.restoredFiles) logSuccess(`Restored: ${f}`);
|
|
@@ -3783,7 +3971,7 @@ async function removeCommand(args) {
|
|
|
3783
3971
|
ok: true,
|
|
3784
3972
|
command: "remove",
|
|
3785
3973
|
instanceId,
|
|
3786
|
-
runtime:
|
|
3974
|
+
runtime: instance2.runtime,
|
|
3787
3975
|
code: "TAP_REMOVE_OK",
|
|
3788
3976
|
message: `${instanceId} removed successfully`,
|
|
3789
3977
|
warnings: [],
|
|
@@ -3798,7 +3986,7 @@ async function removeCommand(args) {
|
|
|
3798
3986
|
ok: false,
|
|
3799
3987
|
command: "remove",
|
|
3800
3988
|
instanceId,
|
|
3801
|
-
runtime:
|
|
3989
|
+
runtime: instance2.runtime,
|
|
3802
3990
|
code: "TAP_ROLLBACK_FAILED",
|
|
3803
3991
|
message: "Rollback had errors. State preserved for retry.",
|
|
3804
3992
|
warnings: result.errors,
|
|
@@ -3827,7 +4015,7 @@ Subcommands:
|
|
|
3827
4015
|
|
|
3828
4016
|
Options:
|
|
3829
4017
|
--agent-name <name> Agent identity for bridge (or set TAP_AGENT_NAME env)
|
|
3830
|
-
|
|
4018
|
+
Overrides the stored name from 'tap add' when needed
|
|
3831
4019
|
--all Start all registered app-server instances
|
|
3832
4020
|
--busy-mode <steer|wait> How to handle active turns (default: steer)
|
|
3833
4021
|
--poll-seconds <n> Inbox poll interval (default: 5)
|
|
@@ -3863,11 +4051,11 @@ function redactProtectedUrl(url) {
|
|
|
3863
4051
|
try {
|
|
3864
4052
|
const parsed = new URL(url);
|
|
3865
4053
|
if (parsed.searchParams.has("tap_token")) {
|
|
3866
|
-
parsed.searchParams.
|
|
4054
|
+
parsed.searchParams.delete("tap_token");
|
|
3867
4055
|
}
|
|
3868
4056
|
return parsed.toString().replace(/\/$/, "");
|
|
3869
4057
|
} catch {
|
|
3870
|
-
return url.replace(/tap_token=[^&]+/g, "
|
|
4058
|
+
return url.replace(/[?&]tap_token=[^&]+/g, "");
|
|
3871
4059
|
}
|
|
3872
4060
|
}
|
|
3873
4061
|
function loadCurrentBridgeState(stateDir, instanceId, fallback) {
|
|
@@ -3950,37 +4138,37 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3950
4138
|
};
|
|
3951
4139
|
}
|
|
3952
4140
|
const instanceId = resolved.instanceId;
|
|
3953
|
-
let
|
|
3954
|
-
if (!
|
|
4141
|
+
let instance2 = state.instances[instanceId];
|
|
4142
|
+
if (!instance2?.installed) {
|
|
3955
4143
|
return {
|
|
3956
4144
|
ok: false,
|
|
3957
4145
|
command: "bridge",
|
|
3958
4146
|
instanceId,
|
|
3959
|
-
runtime:
|
|
4147
|
+
runtime: instance2?.runtime,
|
|
3960
4148
|
code: "TAP_INSTANCE_NOT_FOUND",
|
|
3961
|
-
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${
|
|
4149
|
+
message: `${instanceId} is not installed. Run: npx @hua-labs/tap add ${instance2?.runtime ?? identifier}`,
|
|
3962
4150
|
warnings: [],
|
|
3963
4151
|
data: {}
|
|
3964
4152
|
};
|
|
3965
4153
|
}
|
|
3966
|
-
const adapter = getAdapter(
|
|
4154
|
+
const adapter = getAdapter(instance2.runtime);
|
|
3967
4155
|
const mode = adapter.bridgeMode();
|
|
3968
4156
|
if (mode !== "app-server") {
|
|
3969
4157
|
return {
|
|
3970
4158
|
ok: true,
|
|
3971
4159
|
command: "bridge",
|
|
3972
4160
|
instanceId,
|
|
3973
|
-
runtime:
|
|
4161
|
+
runtime: instance2.runtime,
|
|
3974
4162
|
code: "TAP_NO_OP",
|
|
3975
4163
|
message: `${instanceId} uses ${mode} mode \u2014 no bridge needed.`,
|
|
3976
4164
|
warnings: [],
|
|
3977
4165
|
data: { bridgeMode: mode }
|
|
3978
4166
|
};
|
|
3979
4167
|
}
|
|
3980
|
-
const resolvedAgentName = agentName ??
|
|
3981
|
-
if (agentName && agentName !==
|
|
3982
|
-
|
|
3983
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
4168
|
+
const resolvedAgentName = agentName ?? instance2.agentName ?? void 0;
|
|
4169
|
+
if (agentName && agentName !== instance2.agentName) {
|
|
4170
|
+
instance2 = { ...instance2, agentName };
|
|
4171
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
3984
4172
|
saveState(repoRoot, updatedState);
|
|
3985
4173
|
state = updatedState;
|
|
3986
4174
|
}
|
|
@@ -3991,7 +4179,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
3991
4179
|
ok: false,
|
|
3992
4180
|
command: "bridge",
|
|
3993
4181
|
instanceId,
|
|
3994
|
-
runtime:
|
|
4182
|
+
runtime: instance2.runtime,
|
|
3995
4183
|
code: "TAP_BRIDGE_SCRIPT_MISSING",
|
|
3996
4184
|
message: `Bridge script not found for ${instanceId}. Ensure the runtime is properly configured.`,
|
|
3997
4185
|
warnings: [],
|
|
@@ -4000,8 +4188,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4000
4188
|
}
|
|
4001
4189
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
4002
4190
|
const runtimeCommand = resolvedConfig.runtimeCommand;
|
|
4003
|
-
const manageAppServer =
|
|
4004
|
-
let effectivePort =
|
|
4191
|
+
const manageAppServer = instance2.runtime === "codex" && flags["no-server"] !== true;
|
|
4192
|
+
let effectivePort = instance2.port;
|
|
4005
4193
|
if (effectivePort == null && manageAppServer) {
|
|
4006
4194
|
effectivePort = await findNextAvailableAppServerPort(
|
|
4007
4195
|
state,
|
|
@@ -4009,8 +4197,8 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4009
4197
|
4501,
|
|
4010
4198
|
instanceId
|
|
4011
4199
|
);
|
|
4012
|
-
|
|
4013
|
-
const updatedState = updateInstanceState(state, instanceId,
|
|
4200
|
+
instance2 = { ...instance2, port: effectivePort };
|
|
4201
|
+
const updatedState = updateInstanceState(state, instanceId, instance2);
|
|
4014
4202
|
saveState(repoRoot, updatedState);
|
|
4015
4203
|
state = updatedState;
|
|
4016
4204
|
}
|
|
@@ -4026,19 +4214,19 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4026
4214
|
if (effectivePort != null) log(`Port: ${effectivePort}`);
|
|
4027
4215
|
if (resolvedAgentName) log(`Agent name: ${resolvedAgentName}`);
|
|
4028
4216
|
const noAuth = flags["no-auth"] === true;
|
|
4029
|
-
if (!manageAppServer &&
|
|
4217
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
4030
4218
|
log("Auto server: disabled (--no-server)");
|
|
4031
4219
|
}
|
|
4032
4220
|
if (noAuth && manageAppServer) {
|
|
4033
4221
|
log("Auth gateway: disabled (--no-auth)");
|
|
4034
4222
|
}
|
|
4035
|
-
const willBeHeadless = flags["headless"] === true ||
|
|
4223
|
+
const willBeHeadless = flags["headless"] === true || instance2.headless?.enabled;
|
|
4036
4224
|
if (willBeHeadless) {
|
|
4037
|
-
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ??
|
|
4225
|
+
const role = (typeof flags["role"] === "string" ? flags["role"] : null) ?? instance2.headless?.role ?? "reviewer";
|
|
4038
4226
|
log(`Headless: ${role}`);
|
|
4039
4227
|
}
|
|
4040
4228
|
try {
|
|
4041
|
-
if (!manageAppServer &&
|
|
4229
|
+
if (!manageAppServer && instance2.runtime === "codex") {
|
|
4042
4230
|
log("Checking app-server health...");
|
|
4043
4231
|
const healthy = await checkAppServerHealth(appServerUrl);
|
|
4044
4232
|
if (healthy) {
|
|
@@ -4049,7 +4237,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4049
4237
|
ok: false,
|
|
4050
4238
|
command: "bridge",
|
|
4051
4239
|
instanceId,
|
|
4052
|
-
runtime:
|
|
4240
|
+
runtime: instance2.runtime,
|
|
4053
4241
|
code: "TAP_BRIDGE_START_FAILED",
|
|
4054
4242
|
message: `App server not reachable at ${appServerUrl}. Start it first: codex app-server --listen ${appServerUrl}`,
|
|
4055
4243
|
warnings: [],
|
|
@@ -4063,7 +4251,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4063
4251
|
ok: false,
|
|
4064
4252
|
command: "bridge",
|
|
4065
4253
|
instanceId,
|
|
4066
|
-
runtime:
|
|
4254
|
+
runtime: instance2.runtime,
|
|
4067
4255
|
code: "TAP_INVALID_ARGUMENT",
|
|
4068
4256
|
message: `Invalid --busy-mode: ${String(busyModeRaw)}. Must be "steer" or "wait".`,
|
|
4069
4257
|
warnings: [],
|
|
@@ -4071,9 +4259,38 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4071
4259
|
};
|
|
4072
4260
|
}
|
|
4073
4261
|
const busyMode = busyModeRaw;
|
|
4074
|
-
const
|
|
4075
|
-
const
|
|
4076
|
-
const
|
|
4262
|
+
const pollSecondsRaw = typeof flags["poll-seconds"] === "string" ? flags["poll-seconds"] : void 0;
|
|
4263
|
+
const reconnectSecondsRaw = typeof flags["reconnect-seconds"] === "string" ? flags["reconnect-seconds"] : void 0;
|
|
4264
|
+
const lookbackRaw = typeof flags["message-lookback-minutes"] === "string" ? flags["message-lookback-minutes"] : void 0;
|
|
4265
|
+
let pollSeconds;
|
|
4266
|
+
let reconnectSeconds;
|
|
4267
|
+
let messageLookbackMinutes;
|
|
4268
|
+
try {
|
|
4269
|
+
pollSeconds = parseIntFlag(pollSecondsRaw, "--poll-seconds", 1, 3600);
|
|
4270
|
+
reconnectSeconds = parseIntFlag(
|
|
4271
|
+
reconnectSecondsRaw,
|
|
4272
|
+
"--reconnect-seconds",
|
|
4273
|
+
1,
|
|
4274
|
+
3600
|
|
4275
|
+
);
|
|
4276
|
+
messageLookbackMinutes = parseIntFlag(
|
|
4277
|
+
lookbackRaw,
|
|
4278
|
+
"--message-lookback-minutes",
|
|
4279
|
+
1,
|
|
4280
|
+
10080
|
|
4281
|
+
);
|
|
4282
|
+
} catch (err) {
|
|
4283
|
+
return {
|
|
4284
|
+
ok: false,
|
|
4285
|
+
command: "bridge",
|
|
4286
|
+
instanceId,
|
|
4287
|
+
runtime: instance2.runtime,
|
|
4288
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
4289
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4290
|
+
warnings: [],
|
|
4291
|
+
data: {}
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4077
4294
|
const threadId = typeof flags["thread-id"] === "string" ? flags["thread-id"] : void 0;
|
|
4078
4295
|
const ephemeral = flags["ephemeral"] === true;
|
|
4079
4296
|
const processExistingMessages = flags["process-existing-messages"] === true;
|
|
@@ -4085,7 +4302,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4085
4302
|
ok: false,
|
|
4086
4303
|
command: "bridge",
|
|
4087
4304
|
instanceId,
|
|
4088
|
-
runtime:
|
|
4305
|
+
runtime: instance2.runtime,
|
|
4089
4306
|
code: "TAP_INVALID_ARGUMENT",
|
|
4090
4307
|
message: `Invalid --role: ${roleArg}. Must be: ${validRoles.join(", ")}`,
|
|
4091
4308
|
warnings: [],
|
|
@@ -4097,10 +4314,10 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4097
4314
|
role: roleArg ?? "reviewer",
|
|
4098
4315
|
maxRounds: 5,
|
|
4099
4316
|
qualitySeverityFloor: "high"
|
|
4100
|
-
} :
|
|
4317
|
+
} : instance2.headless;
|
|
4101
4318
|
const bridge = await startBridge({
|
|
4102
4319
|
instanceId,
|
|
4103
|
-
runtime:
|
|
4320
|
+
runtime: instance2.runtime,
|
|
4104
4321
|
stateDir: ctx.stateDir,
|
|
4105
4322
|
commsDir: ctx.commsDir,
|
|
4106
4323
|
bridgeScript,
|
|
@@ -4141,14 +4358,14 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4141
4358
|
log(`TUI connect: ${bridge.appServer.url}`);
|
|
4142
4359
|
}
|
|
4143
4360
|
}
|
|
4144
|
-
const updated = { ...
|
|
4361
|
+
const updated = { ...instance2, bridge, manageAppServer, noAuth };
|
|
4145
4362
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
4146
4363
|
saveState(repoRoot, newState);
|
|
4147
4364
|
return {
|
|
4148
4365
|
ok: true,
|
|
4149
4366
|
command: "bridge",
|
|
4150
4367
|
instanceId,
|
|
4151
|
-
runtime:
|
|
4368
|
+
runtime: instance2.runtime,
|
|
4152
4369
|
code: "TAP_BRIDGE_START_OK",
|
|
4153
4370
|
message: `Bridge for ${instanceId} started (PID: ${bridge.pid})`,
|
|
4154
4371
|
warnings: [],
|
|
@@ -4161,7 +4378,7 @@ async function bridgeStart(identifier, agentName, flags = {}) {
|
|
|
4161
4378
|
ok: false,
|
|
4162
4379
|
command: "bridge",
|
|
4163
4380
|
instanceId,
|
|
4164
|
-
runtime:
|
|
4381
|
+
runtime: instance2.runtime,
|
|
4165
4382
|
code: "TAP_BRIDGE_START_FAILED",
|
|
4166
4383
|
message: msg,
|
|
4167
4384
|
warnings: [],
|
|
@@ -4263,11 +4480,11 @@ async function bridgeStopOne(identifier) {
|
|
|
4263
4480
|
}
|
|
4264
4481
|
const instanceId = resolved.instanceId;
|
|
4265
4482
|
const ctx = createAdapterContext(state.commsDir, repoRoot);
|
|
4266
|
-
const
|
|
4483
|
+
const instance2 = state.instances[instanceId];
|
|
4267
4484
|
const bridgeState = loadCurrentBridgeState(
|
|
4268
4485
|
ctx.stateDir,
|
|
4269
4486
|
instanceId,
|
|
4270
|
-
|
|
4487
|
+
instance2?.bridge
|
|
4271
4488
|
);
|
|
4272
4489
|
const appServer = bridgeState?.appServer ?? null;
|
|
4273
4490
|
logHeader(`@hua-labs/tap bridge stop ${instanceId}`);
|
|
@@ -4315,8 +4532,8 @@ async function bridgeStopOne(identifier) {
|
|
|
4315
4532
|
}
|
|
4316
4533
|
}
|
|
4317
4534
|
}
|
|
4318
|
-
if (
|
|
4319
|
-
const updated = { ...
|
|
4535
|
+
if (instance2) {
|
|
4536
|
+
const updated = { ...instance2, bridge: null };
|
|
4320
4537
|
const newState = updateInstanceState(state, instanceId, updated);
|
|
4321
4538
|
saveState(repoRoot, newState);
|
|
4322
4539
|
}
|
|
@@ -4388,9 +4605,9 @@ async function bridgeStopAll() {
|
|
|
4388
4605
|
logSuccess(`Stopped bridge for ${instanceId}`);
|
|
4389
4606
|
stopped.push(instanceId);
|
|
4390
4607
|
}
|
|
4391
|
-
const
|
|
4392
|
-
if (
|
|
4393
|
-
state.instances[instanceId] = { ...
|
|
4608
|
+
const instance2 = state.instances[instanceId];
|
|
4609
|
+
if (instance2?.bridge) {
|
|
4610
|
+
state.instances[instanceId] = { ...instance2, bridge: null };
|
|
4394
4611
|
stateChanged = true;
|
|
4395
4612
|
}
|
|
4396
4613
|
}
|
|
@@ -4686,8 +4903,22 @@ async function bridgeRestart(identifier, flags) {
|
|
|
4686
4903
|
};
|
|
4687
4904
|
}
|
|
4688
4905
|
const { config: resolvedConfig } = resolveConfig({}, repoRoot);
|
|
4689
|
-
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] :
|
|
4690
|
-
|
|
4906
|
+
const drainStr = typeof flags["drain-timeout"] === "string" ? flags["drain-timeout"] : void 0;
|
|
4907
|
+
let drainTimeout;
|
|
4908
|
+
try {
|
|
4909
|
+
drainTimeout = parseIntFlag(drainStr, "--drain-timeout", 1, 300) ?? 30;
|
|
4910
|
+
} catch (err) {
|
|
4911
|
+
return {
|
|
4912
|
+
ok: false,
|
|
4913
|
+
command: "bridge",
|
|
4914
|
+
instanceId,
|
|
4915
|
+
runtime: instance.runtime,
|
|
4916
|
+
code: "TAP_INVALID_ARGUMENT",
|
|
4917
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4918
|
+
warnings: [],
|
|
4919
|
+
data: {}
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4691
4922
|
logHeader(`@hua-labs/tap bridge restart ${instanceId}`);
|
|
4692
4923
|
log(`Drain timeout: ${drainTimeout}s`);
|
|
4693
4924
|
try {
|
|
@@ -4834,7 +5065,7 @@ async function bridgeCommand(args) {
|
|
|
4834
5065
|
// src/engine/dashboard.ts
|
|
4835
5066
|
import * as fs15 from "fs";
|
|
4836
5067
|
import * as path15 from "path";
|
|
4837
|
-
import { execSync as
|
|
5068
|
+
import { execSync as execSync4 } from "child_process";
|
|
4838
5069
|
function collectAgents(commsDir) {
|
|
4839
5070
|
const heartbeatsPath = path15.join(commsDir, "heartbeats.json");
|
|
4840
5071
|
if (!fs15.existsSync(heartbeatsPath)) return [];
|
|
@@ -4913,7 +5144,7 @@ function collectBridges(repoRoot) {
|
|
|
4913
5144
|
}
|
|
4914
5145
|
function collectPRs() {
|
|
4915
5146
|
try {
|
|
4916
|
-
const output =
|
|
5147
|
+
const output = execSync4(
|
|
4917
5148
|
"gh pr list --state all --limit 10 --json number,title,author,state,url",
|
|
4918
5149
|
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
|
|
4919
5150
|
);
|
|
@@ -5087,7 +5318,34 @@ async function downCommand(args) {
|
|
|
5087
5318
|
// src/commands/serve.ts
|
|
5088
5319
|
import * as path16 from "path";
|
|
5089
5320
|
import { spawn as spawn2 } from "child_process";
|
|
5321
|
+
var SERVE_HELP = `
|
|
5322
|
+
Usage:
|
|
5323
|
+
tap-comms serve [options]
|
|
5324
|
+
|
|
5325
|
+
Description:
|
|
5326
|
+
Start the tap-comms MCP server over stdio. This command takes over the
|
|
5327
|
+
process \u2014 it is intended to be launched by an MCP host (e.g. Claude Code).
|
|
5328
|
+
|
|
5329
|
+
Options:
|
|
5330
|
+
--comms-dir <path> Override comms directory (also reads TAP_COMMS_DIR env)
|
|
5331
|
+
--help, -h Show help
|
|
5332
|
+
|
|
5333
|
+
Examples:
|
|
5334
|
+
npx @hua-labs/tap serve
|
|
5335
|
+
npx @hua-labs/tap serve --comms-dir /shared/comms
|
|
5336
|
+
`.trim();
|
|
5090
5337
|
async function serveCommand(args) {
|
|
5338
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
5339
|
+
log(SERVE_HELP);
|
|
5340
|
+
return {
|
|
5341
|
+
ok: true,
|
|
5342
|
+
command: "serve",
|
|
5343
|
+
code: "TAP_NO_OP",
|
|
5344
|
+
message: SERVE_HELP,
|
|
5345
|
+
warnings: [],
|
|
5346
|
+
data: {}
|
|
5347
|
+
};
|
|
5348
|
+
}
|
|
5091
5349
|
const repoRoot = findRepoRoot();
|
|
5092
5350
|
let commsDir;
|
|
5093
5351
|
const commsDirIdx = args.indexOf("--comms-dir");
|
|
@@ -5162,7 +5420,7 @@ async function serveCommand(args) {
|
|
|
5162
5420
|
// src/commands/init-worktree.ts
|
|
5163
5421
|
import * as fs16 from "fs";
|
|
5164
5422
|
import * as path17 from "path";
|
|
5165
|
-
import { execSync as
|
|
5423
|
+
import { execSync as execSync5 } from "child_process";
|
|
5166
5424
|
var INIT_WORKTREE_HELP = `
|
|
5167
5425
|
Usage:
|
|
5168
5426
|
tap-comms init-worktree [options]
|
|
@@ -5186,7 +5444,7 @@ function warn(warnings, message) {
|
|
|
5186
5444
|
}
|
|
5187
5445
|
function run(cmd, opts) {
|
|
5188
5446
|
try {
|
|
5189
|
-
return
|
|
5447
|
+
return execSync5(cmd, {
|
|
5190
5448
|
cwd: opts?.cwd,
|
|
5191
5449
|
encoding: "utf-8",
|
|
5192
5450
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -5203,7 +5461,7 @@ function toAbsolute(p) {
|
|
|
5203
5461
|
}
|
|
5204
5462
|
function probeBun(candidate) {
|
|
5205
5463
|
try {
|
|
5206
|
-
const out =
|
|
5464
|
+
const out = execSync5(`"${candidate}" --version`, {
|
|
5207
5465
|
encoding: "utf-8",
|
|
5208
5466
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5209
5467
|
timeout: 5e3
|
|
@@ -5217,7 +5475,7 @@ function findBun() {
|
|
|
5217
5475
|
const candidates = process.platform === "win32" ? ["bun.exe", "bun"] : ["bun"];
|
|
5218
5476
|
for (const name of candidates) {
|
|
5219
5477
|
try {
|
|
5220
|
-
const out =
|
|
5478
|
+
const out = execSync5(
|
|
5221
5479
|
process.platform === "win32" ? `where ${name}` : `which ${name}`,
|
|
5222
5480
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
5223
5481
|
).trim();
|
|
@@ -5448,7 +5706,7 @@ async function initWorktreeCommand(args) {
|
|
|
5448
5706
|
ok: true,
|
|
5449
5707
|
command: "init-worktree",
|
|
5450
5708
|
code: "TAP_NO_OP",
|
|
5451
|
-
message:
|
|
5709
|
+
message: INIT_WORKTREE_HELP,
|
|
5452
5710
|
warnings: [],
|
|
5453
5711
|
data: {}
|
|
5454
5712
|
};
|
|
@@ -5610,8 +5868,38 @@ function renderSnapshot(snapshot) {
|
|
|
5610
5868
|
}
|
|
5611
5869
|
}
|
|
5612
5870
|
}
|
|
5871
|
+
var DASHBOARD_HELP = `
|
|
5872
|
+
Usage:
|
|
5873
|
+
tap-comms dashboard [options]
|
|
5874
|
+
|
|
5875
|
+
Description:
|
|
5876
|
+
Display a unified ops dashboard: agents, bridges, PRs, and warnings.
|
|
5877
|
+
|
|
5878
|
+
Options:
|
|
5879
|
+
--json Output snapshot as JSON
|
|
5880
|
+
--watch Refresh dashboard on an interval
|
|
5881
|
+
--interval <seconds> Refresh interval in seconds (default: 5, min: 2)
|
|
5882
|
+
--comms-dir <path> Override comms directory
|
|
5883
|
+
--help, -h Show help
|
|
5884
|
+
|
|
5885
|
+
Examples:
|
|
5886
|
+
npx @hua-labs/tap dashboard
|
|
5887
|
+
npx @hua-labs/tap dashboard --watch --interval 10
|
|
5888
|
+
npx @hua-labs/tap dashboard --json
|
|
5889
|
+
`.trim();
|
|
5613
5890
|
async function dashboardCommand(args) {
|
|
5614
5891
|
const { flags } = parseArgs(args);
|
|
5892
|
+
if (flags["help"] === true || flags["h"] === true) {
|
|
5893
|
+
log(DASHBOARD_HELP);
|
|
5894
|
+
return {
|
|
5895
|
+
ok: true,
|
|
5896
|
+
command: "dashboard",
|
|
5897
|
+
code: "TAP_NO_OP",
|
|
5898
|
+
message: DASHBOARD_HELP,
|
|
5899
|
+
warnings: [],
|
|
5900
|
+
data: {}
|
|
5901
|
+
};
|
|
5902
|
+
}
|
|
5615
5903
|
const jsonMode = flags["json"] === true;
|
|
5616
5904
|
const watchMode = flags["watch"] === true;
|
|
5617
5905
|
const intervalStr = typeof flags["interval"] === "string" ? flags["interval"] : "5";
|
|
@@ -5674,7 +5962,7 @@ import {
|
|
|
5674
5962
|
statSync as statSync2,
|
|
5675
5963
|
unlinkSync as unlinkSync3
|
|
5676
5964
|
} from "fs";
|
|
5677
|
-
import { execSync as
|
|
5965
|
+
import { execSync as execSync6 } from "child_process";
|
|
5678
5966
|
import { join as join17 } from "path";
|
|
5679
5967
|
var PASS = "pass";
|
|
5680
5968
|
var WARN = "warn";
|
|
@@ -5941,7 +6229,7 @@ function checkMcpServer(repoRoot) {
|
|
|
5941
6229
|
let cmdAvailable = existsSync16(cmd);
|
|
5942
6230
|
if (!cmdAvailable) {
|
|
5943
6231
|
try {
|
|
5944
|
-
|
|
6232
|
+
execSync6(`"${cmd}" --version`, {
|
|
5945
6233
|
stdio: "pipe",
|
|
5946
6234
|
timeout: 5e3
|
|
5947
6235
|
});
|
|
@@ -6118,7 +6406,35 @@ function renderCheck(check, fixMode) {
|
|
|
6118
6406
|
const msg = check.message ? ` \u2014 ${check.message}${fixable}` : "";
|
|
6119
6407
|
return ` ${icon} ${check.name}${msg}`;
|
|
6120
6408
|
}
|
|
6409
|
+
var DOCTOR_HELP = `
|
|
6410
|
+
Usage:
|
|
6411
|
+
tap-comms doctor [options]
|
|
6412
|
+
|
|
6413
|
+
Description:
|
|
6414
|
+
Diagnose tap infrastructure health: comms directory, instances, bridges,
|
|
6415
|
+
message lifecycle, and MCP server configuration.
|
|
6416
|
+
|
|
6417
|
+
Options:
|
|
6418
|
+
--fix Auto-repair detected issues where possible
|
|
6419
|
+
--comms-dir <path> Override comms directory
|
|
6420
|
+
--help, -h Show help
|
|
6421
|
+
|
|
6422
|
+
Examples:
|
|
6423
|
+
npx @hua-labs/tap doctor
|
|
6424
|
+
npx @hua-labs/tap doctor --fix
|
|
6425
|
+
`.trim();
|
|
6121
6426
|
async function doctorCommand(args) {
|
|
6427
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
6428
|
+
log(DOCTOR_HELP);
|
|
6429
|
+
return {
|
|
6430
|
+
ok: true,
|
|
6431
|
+
command: "doctor",
|
|
6432
|
+
code: "TAP_NO_OP",
|
|
6433
|
+
message: DOCTOR_HELP,
|
|
6434
|
+
warnings: [],
|
|
6435
|
+
data: {}
|
|
6436
|
+
};
|
|
6437
|
+
}
|
|
6122
6438
|
const repoRoot = findRepoRoot();
|
|
6123
6439
|
const overrides = {};
|
|
6124
6440
|
let fixMode = false;
|
|
@@ -6231,7 +6547,7 @@ async function doctorCommand(args) {
|
|
|
6231
6547
|
}
|
|
6232
6548
|
|
|
6233
6549
|
// src/commands/comms.ts
|
|
6234
|
-
import { execSync as
|
|
6550
|
+
import { execSync as execSync7, spawnSync as spawnSync4 } from "child_process";
|
|
6235
6551
|
import * as fs17 from "fs";
|
|
6236
6552
|
import * as path18 from "path";
|
|
6237
6553
|
var COMMS_HELP = `
|
|
@@ -6263,7 +6579,7 @@ function commsPull(commsDir) {
|
|
|
6263
6579
|
};
|
|
6264
6580
|
}
|
|
6265
6581
|
try {
|
|
6266
|
-
const output =
|
|
6582
|
+
const output = execSync7("git pull --rebase", {
|
|
6267
6583
|
cwd: commsDir,
|
|
6268
6584
|
encoding: "utf-8",
|
|
6269
6585
|
stdio: "pipe"
|
|
@@ -6305,8 +6621,8 @@ function commsPush(commsDir) {
|
|
|
6305
6621
|
};
|
|
6306
6622
|
}
|
|
6307
6623
|
try {
|
|
6308
|
-
|
|
6309
|
-
const status =
|
|
6624
|
+
execSync7("git add -A", { cwd: commsDir, stdio: "pipe" });
|
|
6625
|
+
const status = execSync7("git status --porcelain", {
|
|
6310
6626
|
cwd: commsDir,
|
|
6311
6627
|
encoding: "utf-8",
|
|
6312
6628
|
stdio: "pipe"
|
|
@@ -6323,11 +6639,23 @@ function commsPush(commsDir) {
|
|
|
6323
6639
|
};
|
|
6324
6640
|
}
|
|
6325
6641
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6642
|
+
const commitResult = spawnSync4(
|
|
6643
|
+
"git",
|
|
6644
|
+
["commit", "-m", `chore(comms): sync ${timestamp}`],
|
|
6645
|
+
{ cwd: commsDir, stdio: "pipe", encoding: "utf-8" }
|
|
6646
|
+
);
|
|
6647
|
+
if (commitResult.status !== 0) {
|
|
6648
|
+
const msg = commitResult.stderr || `git commit exited with code ${commitResult.status}`;
|
|
6649
|
+
return {
|
|
6650
|
+
ok: false,
|
|
6651
|
+
command: "comms",
|
|
6652
|
+
code: "TAP_COMMS_PUSH_FAILED",
|
|
6653
|
+
message: `Commit failed: ${msg}`,
|
|
6654
|
+
warnings: [],
|
|
6655
|
+
data: { commsDir }
|
|
6656
|
+
};
|
|
6657
|
+
}
|
|
6658
|
+
execSync7("git push", { cwd: commsDir, stdio: "pipe" });
|
|
6331
6659
|
logSuccess("Comms push complete");
|
|
6332
6660
|
return {
|
|
6333
6661
|
ok: true,
|
|
@@ -6393,7 +6721,12 @@ function emitResult(result, jsonMode) {
|
|
|
6393
6721
|
} else {
|
|
6394
6722
|
logError(result.message);
|
|
6395
6723
|
}
|
|
6724
|
+
const emittedWarnings = /* @__PURE__ */ new Set();
|
|
6396
6725
|
for (const w of result.warnings) {
|
|
6726
|
+
if (emittedWarnings.has(w) || wasWarningLogged(w)) {
|
|
6727
|
+
continue;
|
|
6728
|
+
}
|
|
6729
|
+
emittedWarnings.add(w);
|
|
6397
6730
|
logWarn(w);
|
|
6398
6731
|
}
|
|
6399
6732
|
}
|
|
@@ -6462,6 +6795,7 @@ function normalizeCommandName(command) {
|
|
|
6462
6795
|
async function main() {
|
|
6463
6796
|
const rawArgs = process.argv.slice(2);
|
|
6464
6797
|
const { jsonMode, cleanArgs } = extractJsonFlag(rawArgs);
|
|
6798
|
+
resetLoggedWarnings();
|
|
6465
6799
|
setJsonMode(jsonMode);
|
|
6466
6800
|
const command = cleanArgs[0];
|
|
6467
6801
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -6519,7 +6853,7 @@ async function main() {
|
|
|
6519
6853
|
break;
|
|
6520
6854
|
case "serve": {
|
|
6521
6855
|
const serveResult = await serveCommand(commandArgs);
|
|
6522
|
-
if (!serveResult.ok) {
|
|
6856
|
+
if (!serveResult.ok || serveResult.code === "TAP_NO_OP") {
|
|
6523
6857
|
emitResult(serveResult, jsonMode);
|
|
6524
6858
|
}
|
|
6525
6859
|
process.exit(exitCode(serveResult));
|