@hua-labs/tap 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -12
- package/dist/cli.mjs +62 -27
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +17 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
# @hua-labs/tap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`tap` is a CLI that turns your repo into a shared workspace for Claude, Codex, and Gemini (experimental) so multiple AI agents can coordinate on the same codebase without custom glue code.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Why Would I Use It?
|
|
6
|
+
|
|
7
|
+
- You use more than one coding agent and want them to share context without copy-pasting prompts between tools.
|
|
8
|
+
- You want reviews, handoffs, and agent-to-agent messages to live in files inside the repo instead of hidden app state.
|
|
9
|
+
- You want a working multi-agent setup in minutes instead of hand-editing MCP configs and bridge processes yourself.
|
|
6
10
|
|
|
7
11
|
## Quick Start
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
Try it in a fresh repo:
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
# 1. Initialize comms directory and state
|
|
13
16
|
npx @hua-labs/tap init
|
|
14
|
-
|
|
15
|
-
# 2. Add runtimes
|
|
16
17
|
npx @hua-labs/tap add claude
|
|
17
18
|
npx @hua-labs/tap add codex
|
|
18
|
-
npx @hua-labs/tap add gemini
|
|
19
|
-
|
|
20
|
-
# 3. Check status
|
|
19
|
+
npx @hua-labs/tap add gemini # experimental
|
|
21
20
|
npx @hua-labs/tap status
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
This creates a shared comms/state layer and wires supported runtimes into it.
|
|
24
|
+
|
|
25
|
+
Gemini support is currently experimental and polling-only.
|
|
26
|
+
|
|
27
|
+
> `npx @hua-labs/tap` ships a bundled managed MCP server entry and runs that bundled `.mjs` with `node`. `bun` is only required when tap falls back to repo-local TypeScript sources during monorepo or local-dev workflows.
|
|
25
28
|
|
|
26
29
|
## Commands
|
|
27
30
|
|
|
@@ -46,7 +49,7 @@ Add a runtime. Probes config, plans patches, applies, and verifies.
|
|
|
46
49
|
```bash
|
|
47
50
|
npx @hua-labs/tap add claude
|
|
48
51
|
npx @hua-labs/tap add codex
|
|
49
|
-
npx @hua-labs/tap add gemini
|
|
52
|
+
npx @hua-labs/tap add gemini # experimental
|
|
50
53
|
npx @hua-labs/tap add claude --force # re-install
|
|
51
54
|
```
|
|
52
55
|
|
|
@@ -99,7 +102,7 @@ For npm installs, `serve` runs the bundled `mcp-server.mjs` entry with `node`. I
|
|
|
99
102
|
| ------- | ----------------------- | ---------------------- | ------------------ |
|
|
100
103
|
| Claude | `.mcp.json` | native-push (fs.watch) | No daemon needed |
|
|
101
104
|
| Codex | `~/.codex/config.toml` | WebSocket bridge | Daemon per session |
|
|
102
|
-
| Gemini
|
|
105
|
+
| Gemini (experimental) | `.gemini/settings.json` | polling | No daemon needed |
|
|
103
106
|
|
|
104
107
|
## `--json` Flag
|
|
105
108
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1432,16 +1432,22 @@ import * as os2 from "os";
|
|
|
1432
1432
|
import * as path8 from "path";
|
|
1433
1433
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1434
1434
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1435
|
+
function resolveProbeCommand(candidate) {
|
|
1436
|
+
return resolveCommandPath(candidate) ?? candidate;
|
|
1437
|
+
}
|
|
1438
|
+
function probeCommandVersion(command) {
|
|
1439
|
+
return spawnSync2(command, ["--version"], {
|
|
1440
|
+
encoding: "utf-8",
|
|
1441
|
+
windowsHide: true
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1435
1444
|
function probeCommand(candidates) {
|
|
1436
1445
|
for (const candidate of candidates) {
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1439
|
-
shell: process.platform === "win32"
|
|
1440
|
-
});
|
|
1446
|
+
const resolvedCommand = resolveProbeCommand(candidate);
|
|
1447
|
+
const result = probeCommandVersion(resolvedCommand);
|
|
1441
1448
|
if (result.status === 0) {
|
|
1442
1449
|
const version2 = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim() || null;
|
|
1443
|
-
|
|
1444
|
-
return { command: absolutePath ?? candidate, version: version2 };
|
|
1450
|
+
return { command: resolvedCommand, version: version2 };
|
|
1445
1451
|
}
|
|
1446
1452
|
}
|
|
1447
1453
|
return { command: null, version: null };
|
|
@@ -1543,19 +1549,16 @@ function findPreferredBunCommand() {
|
|
|
1543
1549
|
const candidates = process.platform === "win32" ? [path8.join(home, ".bun", "bin", "bun.exe"), "bun", "bun.cmd"] : [path8.join(home, ".bun", "bin", "bun"), "bun"];
|
|
1544
1550
|
for (const candidate of candidates) {
|
|
1545
1551
|
if (path8.isAbsolute(candidate) && !fs9.existsSync(candidate)) continue;
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
shell: process.platform === "win32"
|
|
1549
|
-
});
|
|
1552
|
+
const resolvedCommand = resolveProbeCommand(candidate);
|
|
1553
|
+
const result = probeCommandVersion(resolvedCommand);
|
|
1550
1554
|
if (result.status === 0) {
|
|
1551
|
-
return path8.isAbsolute(
|
|
1555
|
+
return path8.isAbsolute(resolvedCommand) ? toForwardSlashPath(resolvedCommand) : resolvedCommand;
|
|
1552
1556
|
}
|
|
1553
1557
|
}
|
|
1554
1558
|
return null;
|
|
1555
1559
|
}
|
|
1556
1560
|
function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
1557
1561
|
const sourcePath = findTapCommsServerEntry(ctx);
|
|
1558
|
-
const bunCommand = findPreferredBunCommand();
|
|
1559
1562
|
const warnings = [];
|
|
1560
1563
|
const issues = [];
|
|
1561
1564
|
const env = {
|
|
@@ -1575,7 +1578,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1575
1578
|
}
|
|
1576
1579
|
const isBundled = sourcePath.endsWith(".mjs");
|
|
1577
1580
|
const isEphemeralSource = isEphemeralPath(sourcePath);
|
|
1578
|
-
let command
|
|
1581
|
+
let command;
|
|
1579
1582
|
let args = [toForwardSlashPath(sourcePath)];
|
|
1580
1583
|
if (isEphemeralSource && isBundled) {
|
|
1581
1584
|
command = "npx";
|
|
@@ -1589,7 +1592,7 @@ function buildManagedMcpServerSpec(ctx, instanceId) {
|
|
|
1589
1592
|
);
|
|
1590
1593
|
command = nodeProbe.command ?? "node";
|
|
1591
1594
|
} else {
|
|
1592
|
-
command =
|
|
1595
|
+
command = findPreferredBunCommand();
|
|
1593
1596
|
}
|
|
1594
1597
|
if (!command) {
|
|
1595
1598
|
issues.push(
|
|
@@ -8463,6 +8466,38 @@ function sameCommandToken(left, right) {
|
|
|
8463
8466
|
function sameStringArray(left, right) {
|
|
8464
8467
|
return left.length === right.length && left.every((value, index) => sameCommandToken(value, right[index] ?? ""));
|
|
8465
8468
|
}
|
|
8469
|
+
function normalizeCommandBasename(command) {
|
|
8470
|
+
const token = command.split(/[\\/]/).pop() ?? command;
|
|
8471
|
+
return token.toLowerCase().replace(/\.(cmd|exe|ps1|bat)$/i, "");
|
|
8472
|
+
}
|
|
8473
|
+
function findFirstLauncherTarget(args) {
|
|
8474
|
+
for (const arg of args) {
|
|
8475
|
+
if (!arg || arg === "--" || arg.startsWith("-")) {
|
|
8476
|
+
continue;
|
|
8477
|
+
}
|
|
8478
|
+
return arg;
|
|
8479
|
+
}
|
|
8480
|
+
return null;
|
|
8481
|
+
}
|
|
8482
|
+
function looksLikePackageSpecifier(value) {
|
|
8483
|
+
const normalized = value.trim();
|
|
8484
|
+
if (!normalized || /^[A-Za-z]:[\\/]/.test(normalized) || normalized.startsWith("/") || normalized.startsWith("\\") || normalized.startsWith(".") || /\.(?:[cm]?js|tsx?|json|ps1|cmd|exe)$/i.test(normalized)) {
|
|
8485
|
+
return false;
|
|
8486
|
+
}
|
|
8487
|
+
return /^(?:@[^/\\]+\/)?[A-Za-z0-9][A-Za-z0-9._-]*(?:@[A-Za-z0-9][A-Za-z0-9._.-]*)?$/.test(
|
|
8488
|
+
normalized
|
|
8489
|
+
);
|
|
8490
|
+
}
|
|
8491
|
+
function getNpxPackageLauncher(command, args) {
|
|
8492
|
+
if (normalizeCommandBasename(command) !== "npx") {
|
|
8493
|
+
return null;
|
|
8494
|
+
}
|
|
8495
|
+
const packageName = findFirstLauncherTarget(args);
|
|
8496
|
+
if (!packageName || !looksLikePackageSpecifier(packageName)) {
|
|
8497
|
+
return null;
|
|
8498
|
+
}
|
|
8499
|
+
return [command, ...args].join(" ");
|
|
8500
|
+
}
|
|
8466
8501
|
function appendWarningMessage(message, extra) {
|
|
8467
8502
|
return message.includes(extra) ? message : `${message}; ${extra}`;
|
|
8468
8503
|
}
|
|
@@ -8918,10 +8953,11 @@ function checkMessageLifecycle(commsDir) {
|
|
|
8918
8953
|
const total = countFiles(inbox);
|
|
8919
8954
|
const recent1h = recentFileCount(inbox, 60 * 60 * 1e3);
|
|
8920
8955
|
const recent10m = recentFileCount(inbox, 10 * 60 * 1e3);
|
|
8956
|
+
const messageSummary = `${total} total, ${recent1h} in last 1h, ${recent10m} in last 10m`;
|
|
8921
8957
|
checks.push({
|
|
8922
8958
|
name: "message flow",
|
|
8923
|
-
status: recent10m > 0 ? PASS :
|
|
8924
|
-
message:
|
|
8959
|
+
status: recent10m > 0 ? PASS : WARN,
|
|
8960
|
+
message: total === 0 ? `${messageSummary} (expected before first exchange)` : messageSummary
|
|
8925
8961
|
});
|
|
8926
8962
|
const receiptsPath = join28(commsDir, "receipts", "receipts.json");
|
|
8927
8963
|
if (existsSync29(receiptsPath)) {
|
|
@@ -9001,15 +9037,7 @@ function checkMcpServer(repoRoot) {
|
|
|
9001
9037
|
const cmd = hasTapComms.command;
|
|
9002
9038
|
let cmdAvailable = existsSync29(cmd);
|
|
9003
9039
|
if (!cmdAvailable) {
|
|
9004
|
-
|
|
9005
|
-
const result = spawnSync6(cmd, ["--version"], {
|
|
9006
|
-
stdio: "pipe",
|
|
9007
|
-
timeout: 5e3,
|
|
9008
|
-
shell: process.platform === "win32"
|
|
9009
|
-
});
|
|
9010
|
-
cmdAvailable = result.status === 0;
|
|
9011
|
-
} catch {
|
|
9012
|
-
}
|
|
9040
|
+
cmdAvailable = probeCommand([cmd]).command !== null;
|
|
9013
9041
|
}
|
|
9014
9042
|
checks.push({
|
|
9015
9043
|
name: "MCP command binary",
|
|
@@ -9017,7 +9045,14 @@ function checkMcpServer(repoRoot) {
|
|
|
9017
9045
|
message: cmdAvailable ? cmd : `Not found: ${cmd} (checked PATH and absolute)`
|
|
9018
9046
|
});
|
|
9019
9047
|
}
|
|
9020
|
-
|
|
9048
|
+
const npxPackageLauncher = hasTapComms.command && hasTapComms.args ? getNpxPackageLauncher(hasTapComms.command, hasTapComms.args) : null;
|
|
9049
|
+
if (npxPackageLauncher) {
|
|
9050
|
+
checks.push({
|
|
9051
|
+
name: "MCP server script",
|
|
9052
|
+
status: PASS,
|
|
9053
|
+
message: `Package launcher: ${npxPackageLauncher}`
|
|
9054
|
+
});
|
|
9055
|
+
} else if (hasTapComms.args?.[0]) {
|
|
9021
9056
|
const mcpScript = hasTapComms.args[0];
|
|
9022
9057
|
checks.push({
|
|
9023
9058
|
name: "MCP server script",
|
|
@@ -9483,7 +9518,7 @@ async function doctorCommand(args) {
|
|
|
9483
9518
|
message: `${passes} passed, ${warns} warnings, ${fails} failures`,
|
|
9484
9519
|
warnings: finalChecks.filter((c) => c.status === "warn").map((c) => `${c.name}: ${c.message}`),
|
|
9485
9520
|
data: {
|
|
9486
|
-
checks: finalChecks.map(({ fix, ...rest }) => rest),
|
|
9521
|
+
checks: finalChecks.map(({ fix: _fix, ...rest }) => rest),
|
|
9487
9522
|
summary: { total: finalChecks.length, passes, warns, fails },
|
|
9488
9523
|
fixed
|
|
9489
9524
|
}
|