@bazaar.ai/mcp-human-agents 0.1.0
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/.env.example +15 -0
- package/README.md +178 -0
- package/dist/mcp-server/src/bin.d.ts +3 -0
- package/dist/mcp-server/src/bin.d.ts.map +1 -0
- package/dist/mcp-server/src/bin.js +10 -0
- package/dist/mcp-server/src/bin.js.map +1 -0
- package/dist/mcp-server/src/cli/setup.d.ts +2 -0
- package/dist/mcp-server/src/cli/setup.d.ts.map +1 -0
- package/dist/mcp-server/src/cli/setup.js +274 -0
- package/dist/mcp-server/src/cli/setup.js.map +1 -0
- package/dist/mcp-server/src/config/defaults.d.ts +16 -0
- package/dist/mcp-server/src/config/defaults.d.ts.map +1 -0
- package/dist/mcp-server/src/config/defaults.js +19 -0
- package/dist/mcp-server/src/config/defaults.js.map +1 -0
- package/dist/mcp-server/src/config/env.d.ts +73 -0
- package/dist/mcp-server/src/config/env.d.ts.map +1 -0
- package/dist/mcp-server/src/config/env.js +72 -0
- package/dist/mcp-server/src/config/env.js.map +1 -0
- package/dist/mcp-server/src/config/index.d.ts +3 -0
- package/dist/mcp-server/src/config/index.d.ts.map +1 -0
- package/dist/mcp-server/src/config/index.js +22 -0
- package/dist/mcp-server/src/config/index.js.map +1 -0
- package/dist/mcp-server/src/context/generator.d.ts +16 -0
- package/dist/mcp-server/src/context/generator.d.ts.map +1 -0
- package/dist/mcp-server/src/context/generator.js +61 -0
- package/dist/mcp-server/src/context/generator.js.map +1 -0
- package/dist/mcp-server/src/context/index.d.ts +3 -0
- package/dist/mcp-server/src/context/index.d.ts.map +1 -0
- package/dist/mcp-server/src/context/index.js +10 -0
- package/dist/mcp-server/src/context/index.js.map +1 -0
- package/dist/mcp-server/src/context/templates.d.ts +8 -0
- package/dist/mcp-server/src/context/templates.d.ts.map +1 -0
- package/dist/mcp-server/src/context/templates.js +41 -0
- package/dist/mcp-server/src/context/templates.js.map +1 -0
- package/dist/mcp-server/src/git/branch.d.ts +13 -0
- package/dist/mcp-server/src/git/branch.d.ts.map +1 -0
- package/dist/mcp-server/src/git/branch.js +49 -0
- package/dist/mcp-server/src/git/branch.js.map +1 -0
- package/dist/mcp-server/src/git/diff.d.ts +10 -0
- package/dist/mcp-server/src/git/diff.d.ts.map +1 -0
- package/dist/mcp-server/src/git/diff.js +39 -0
- package/dist/mcp-server/src/git/diff.js.map +1 -0
- package/dist/mcp-server/src/git/index.d.ts +5 -0
- package/dist/mcp-server/src/git/index.d.ts.map +1 -0
- package/dist/mcp-server/src/git/index.js +16 -0
- package/dist/mcp-server/src/git/index.js.map +1 -0
- package/dist/mcp-server/src/git/merge.d.ts +6 -0
- package/dist/mcp-server/src/git/merge.d.ts.map +1 -0
- package/dist/mcp-server/src/git/merge.js +30 -0
- package/dist/mcp-server/src/git/merge.js.map +1 -0
- package/dist/mcp-server/src/git/worktree.d.ts +11 -0
- package/dist/mcp-server/src/git/worktree.d.ts.map +1 -0
- package/dist/mcp-server/src/git/worktree.js +38 -0
- package/dist/mcp-server/src/git/worktree.js.map +1 -0
- package/dist/mcp-server/src/http-wrapper.d.ts +6 -0
- package/dist/mcp-server/src/http-wrapper.d.ts.map +1 -0
- package/dist/mcp-server/src/http-wrapper.js +85 -0
- package/dist/mcp-server/src/http-wrapper.js.map +1 -0
- package/dist/mcp-server/src/index.d.ts +2 -0
- package/dist/mcp-server/src/index.d.ts.map +1 -0
- package/dist/mcp-server/src/index.js +28 -0
- package/dist/mcp-server/src/index.js.map +1 -0
- package/dist/mcp-server/src/platform-client/client.d.ts +17 -0
- package/dist/mcp-server/src/platform-client/client.d.ts.map +1 -0
- package/dist/mcp-server/src/platform-client/client.js +68 -0
- package/dist/mcp-server/src/platform-client/client.js.map +1 -0
- package/dist/mcp-server/src/platform-client/index.d.ts +5 -0
- package/dist/mcp-server/src/platform-client/index.d.ts.map +1 -0
- package/dist/mcp-server/src/platform-client/index.js +10 -0
- package/dist/mcp-server/src/platform-client/index.js.map +1 -0
- package/dist/mcp-server/src/platform-client/mock-client.d.ts +28 -0
- package/dist/mcp-server/src/platform-client/mock-client.d.ts.map +1 -0
- package/dist/mcp-server/src/platform-client/mock-client.js +75 -0
- package/dist/mcp-server/src/platform-client/mock-client.js.map +1 -0
- package/dist/mcp-server/src/platform-client/polling.d.ts +9 -0
- package/dist/mcp-server/src/platform-client/polling.d.ts.map +1 -0
- package/dist/mcp-server/src/platform-client/polling.js +40 -0
- package/dist/mcp-server/src/platform-client/polling.js.map +1 -0
- package/dist/mcp-server/src/platform-client/types.d.ts +2 -0
- package/dist/mcp-server/src/platform-client/types.d.ts.map +1 -0
- package/dist/mcp-server/src/platform-client/types.js +3 -0
- package/dist/mcp-server/src/platform-client/types.js.map +1 -0
- package/dist/mcp-server/src/provisioning/authorized-keys.d.ts +14 -0
- package/dist/mcp-server/src/provisioning/authorized-keys.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/authorized-keys.js +48 -0
- package/dist/mcp-server/src/provisioning/authorized-keys.js.map +1 -0
- package/dist/mcp-server/src/provisioning/cleanup.d.ts +19 -0
- package/dist/mcp-server/src/provisioning/cleanup.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/cleanup.js +96 -0
- package/dist/mcp-server/src/provisioning/cleanup.js.map +1 -0
- package/dist/mcp-server/src/provisioning/index.d.ts +6 -0
- package/dist/mcp-server/src/provisioning/index.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/index.js +24 -0
- package/dist/mcp-server/src/provisioning/index.js.map +1 -0
- package/dist/mcp-server/src/provisioning/linux-user.d.ts +15 -0
- package/dist/mcp-server/src/provisioning/linux-user.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/linux-user.js +62 -0
- package/dist/mcp-server/src/provisioning/linux-user.js.map +1 -0
- package/dist/mcp-server/src/provisioning/privileged.d.ts +40 -0
- package/dist/mcp-server/src/provisioning/privileged.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/privileged.js +123 -0
- package/dist/mcp-server/src/provisioning/privileged.js.map +1 -0
- package/dist/mcp-server/src/provisioning/ssh-config.d.ts +21 -0
- package/dist/mcp-server/src/provisioning/ssh-config.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/ssh-config.js +161 -0
- package/dist/mcp-server/src/provisioning/ssh-config.js.map +1 -0
- package/dist/mcp-server/src/provisioning/tmux-session.d.ts +37 -0
- package/dist/mcp-server/src/provisioning/tmux-session.d.ts.map +1 -0
- package/dist/mcp-server/src/provisioning/tmux-session.js +123 -0
- package/dist/mcp-server/src/provisioning/tmux-session.js.map +1 -0
- package/dist/mcp-server/src/server.d.ts +3 -0
- package/dist/mcp-server/src/server.d.ts.map +1 -0
- package/dist/mcp-server/src/server.js +67 -0
- package/dist/mcp-server/src/server.js.map +1 -0
- package/dist/mcp-server/src/state/gig-store.d.ts +19 -0
- package/dist/mcp-server/src/state/gig-store.d.ts.map +1 -0
- package/dist/mcp-server/src/state/gig-store.js +52 -0
- package/dist/mcp-server/src/state/gig-store.js.map +1 -0
- package/dist/mcp-server/src/state/index.d.ts +4 -0
- package/dist/mcp-server/src/state/index.d.ts.map +1 -0
- package/dist/mcp-server/src/state/index.js +8 -0
- package/dist/mcp-server/src/state/index.js.map +1 -0
- package/dist/mcp-server/src/state/persistence.d.ts +13 -0
- package/dist/mcp-server/src/state/persistence.d.ts.map +1 -0
- package/dist/mcp-server/src/state/persistence.js +48 -0
- package/dist/mcp-server/src/state/persistence.js.map +1 -0
- package/dist/mcp-server/src/state/types.d.ts +15 -0
- package/dist/mcp-server/src/state/types.d.ts.map +1 -0
- package/dist/mcp-server/src/state/types.js +3 -0
- package/dist/mcp-server/src/state/types.js.map +1 -0
- package/dist/mcp-server/src/tools/dismiss-human.d.ts +25 -0
- package/dist/mcp-server/src/tools/dismiss-human.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/dismiss-human.js +78 -0
- package/dist/mcp-server/src/tools/dismiss-human.js.map +1 -0
- package/dist/mcp-server/src/tools/index.d.ts +9 -0
- package/dist/mcp-server/src/tools/index.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/index.js +20 -0
- package/dist/mcp-server/src/tools/index.js.map +1 -0
- package/dist/mcp-server/src/tools/list-humans.d.ts +18 -0
- package/dist/mcp-server/src/tools/list-humans.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/list-humans.js +35 -0
- package/dist/mcp-server/src/tools/list-humans.js.map +1 -0
- package/dist/mcp-server/src/tools/message-human.d.ts +10 -0
- package/dist/mcp-server/src/tools/message-human.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/message-human.js +19 -0
- package/dist/mcp-server/src/tools/message-human.js.map +1 -0
- package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.d.ts +19 -0
- package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.js +22 -0
- package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.js.map +1 -0
- package/dist/mcp-server/src/tools/schemas/list-humans.schema.d.ts +4 -0
- package/dist/mcp-server/src/tools/schemas/list-humans.schema.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/schemas/list-humans.schema.js +7 -0
- package/dist/mcp-server/src/tools/schemas/list-humans.schema.js.map +1 -0
- package/dist/mcp-server/src/tools/schemas/message-human.schema.d.ts +13 -0
- package/dist/mcp-server/src/tools/schemas/message-human.schema.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/schemas/message-human.schema.js +9 -0
- package/dist/mcp-server/src/tools/schemas/message-human.schema.js.map +1 -0
- package/dist/mcp-server/src/tools/schemas/summon-human.schema.d.ts +22 -0
- package/dist/mcp-server/src/tools/schemas/summon-human.schema.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/schemas/summon-human.schema.js +18 -0
- package/dist/mcp-server/src/tools/schemas/summon-human.schema.js.map +1 -0
- package/dist/mcp-server/src/tools/summon-human.d.ts +31 -0
- package/dist/mcp-server/src/tools/summon-human.d.ts.map +1 -0
- package/dist/mcp-server/src/tools/summon-human.js +137 -0
- package/dist/mcp-server/src/tools/summon-human.js.map +1 -0
- package/dist/mcp-server/src/tunnel/client.d.ts +16 -0
- package/dist/mcp-server/src/tunnel/client.d.ts.map +1 -0
- package/dist/mcp-server/src/tunnel/client.js +100 -0
- package/dist/mcp-server/src/tunnel/client.js.map +1 -0
- package/dist/mcp-server/src/tunnel/index.d.ts +6 -0
- package/dist/mcp-server/src/tunnel/index.d.ts.map +1 -0
- package/dist/mcp-server/src/tunnel/index.js +28 -0
- package/dist/mcp-server/src/tunnel/index.js.map +1 -0
- package/dist/mcp-server/src/utils/errors.d.ts +28 -0
- package/dist/mcp-server/src/utils/errors.d.ts.map +1 -0
- package/dist/mcp-server/src/utils/errors.js +66 -0
- package/dist/mcp-server/src/utils/errors.js.map +1 -0
- package/dist/mcp-server/src/utils/exec.d.ts +7 -0
- package/dist/mcp-server/src/utils/exec.d.ts.map +1 -0
- package/dist/mcp-server/src/utils/exec.js +22 -0
- package/dist/mcp-server/src/utils/exec.js.map +1 -0
- package/dist/mcp-server/src/utils/ip.d.ts +6 -0
- package/dist/mcp-server/src/utils/ip.d.ts.map +1 -0
- package/dist/mcp-server/src/utils/ip.js +33 -0
- package/dist/mcp-server/src/utils/ip.js.map +1 -0
- package/dist/mcp-server/src/utils/logger.d.ts +20 -0
- package/dist/mcp-server/src/utils/logger.d.ts.map +1 -0
- package/dist/mcp-server/src/utils/logger.js +41 -0
- package/dist/mcp-server/src/utils/logger.js.map +1 -0
- package/dist/shared/src/contractor.types.d.ts +20 -0
- package/dist/shared/src/contractor.types.d.ts.map +1 -0
- package/dist/shared/src/contractor.types.js +3 -0
- package/dist/shared/src/contractor.types.js.map +1 -0
- package/dist/shared/src/gig.types.d.ts +32 -0
- package/dist/shared/src/gig.types.d.ts.map +1 -0
- package/dist/shared/src/gig.types.js +21 -0
- package/dist/shared/src/gig.types.js.map +1 -0
- package/dist/shared/src/index.d.ts +5 -0
- package/dist/shared/src/index.d.ts.map +1 -0
- package/dist/shared/src/index.js +21 -0
- package/dist/shared/src/index.js.map +1 -0
- package/dist/shared/src/mcp-tool.types.d.ts +45 -0
- package/dist/shared/src/mcp-tool.types.d.ts.map +1 -0
- package/dist/shared/src/mcp-tool.types.js +3 -0
- package/dist/shared/src/mcp-tool.types.js.map +1 -0
- package/dist/shared/src/platform-api.types.d.ts +73 -0
- package/dist/shared/src/platform-api.types.d.ts.map +1 -0
- package/dist/shared/src/platform-api.types.js +3 -0
- package/dist/shared/src/platform-api.types.js.map +1 -0
- package/package.json +41 -0
- package/src/bin.ts +7 -0
- package/src/cli/setup.ts +317 -0
- package/src/config/defaults.ts +21 -0
- package/src/config/env.ts +74 -0
- package/src/config/index.ts +2 -0
- package/src/context/generator.ts +71 -0
- package/src/context/index.ts +6 -0
- package/src/context/templates.ts +41 -0
- package/src/git/branch.ts +46 -0
- package/src/git/diff.ts +34 -0
- package/src/git/index.ts +4 -0
- package/src/git/merge.ts +36 -0
- package/src/git/worktree.ts +42 -0
- package/src/http-wrapper.ts +94 -0
- package/src/index.ts +32 -0
- package/src/platform-client/client.ts +93 -0
- package/src/platform-client/index.ts +4 -0
- package/src/platform-client/mock-client.ts +92 -0
- package/src/platform-client/polling.ts +53 -0
- package/src/platform-client/types.ts +9 -0
- package/src/provisioning/authorized-keys.ts +52 -0
- package/src/provisioning/cleanup.ts +106 -0
- package/src/provisioning/index.ts +13 -0
- package/src/provisioning/linux-user.ts +66 -0
- package/src/provisioning/privileged.ts +128 -0
- package/src/provisioning/ssh-config.ts +197 -0
- package/src/provisioning/tmux-session.ts +136 -0
- package/src/server.ts +111 -0
- package/src/state/gig-store.ts +56 -0
- package/src/state/index.ts +3 -0
- package/src/state/persistence.ts +42 -0
- package/src/state/types.ts +14 -0
- package/src/tools/dismiss-human.ts +103 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/list-humans.ts +54 -0
- package/src/tools/message-human.ts +28 -0
- package/src/tools/schemas/dismiss-human.schema.ts +21 -0
- package/src/tools/schemas/list-humans.schema.ts +6 -0
- package/src/tools/schemas/message-human.schema.ts +8 -0
- package/src/tools/schemas/summon-human.schema.ts +19 -0
- package/src/tools/summon-human.ts +180 -0
- package/src/tunnel/client.ts +116 -0
- package/src/tunnel/index.ts +26 -0
- package/src/utils/errors.ts +64 -0
- package/src/utils/exec.ts +29 -0
- package/src/utils/ip.ts +31 -0
- package/src/utils/logger.ts +55 -0
- package/tsconfig.json +20 -0
package/src/cli/setup.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { execSync, type ExecSyncOptions } from "node:child_process";
|
|
3
|
+
import { stdin, stdout, stderr } from "node:process";
|
|
4
|
+
|
|
5
|
+
// TEMPORARY: ngrok tunnel for early testing. Replace with production URL
|
|
6
|
+
// (e.g. https://api.bazaar.ai) before public launch.
|
|
7
|
+
const PLATFORM_API_URL_DEFAULT = "https://ingrid-hypocycloidal-kaliyah.ngrok-free.dev";
|
|
8
|
+
|
|
9
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function log(msg: string): void {
|
|
12
|
+
stderr.write(`${msg}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function success(msg: string): void {
|
|
16
|
+
stderr.write(`\x1b[32m✓\x1b[0m ${msg}\n`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function warn(msg: string): void {
|
|
20
|
+
stderr.write(`\x1b[33m!\x1b[0m ${msg}\n`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fail(msg: string): void {
|
|
24
|
+
stderr.write(`\x1b[31m✗\x1b[0m ${msg}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function run(cmd: string, opts?: ExecSyncOptions): string {
|
|
28
|
+
const result = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], ...opts });
|
|
29
|
+
return String(result).trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function canRun(cmd: string): boolean {
|
|
33
|
+
try {
|
|
34
|
+
run(cmd);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Setup Steps ──────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async function checkClaude(): Promise<boolean> {
|
|
44
|
+
if (canRun("claude --version")) {
|
|
45
|
+
success("Claude Code CLI found");
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
fail("Claude Code CLI not found. Install it first: https://docs.anthropic.com/en/docs/claude-code");
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkPrereqs(): { missing: string[] } {
|
|
53
|
+
const required = [
|
|
54
|
+
{ cmd: "sshd -v 2>&1 || true", name: "openssh-server" },
|
|
55
|
+
{ cmd: "which tmux", name: "tmux" },
|
|
56
|
+
{ cmd: "which git", name: "git" },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const missing: string[] = [];
|
|
60
|
+
for (const { cmd, name } of required) {
|
|
61
|
+
if (canRun(cmd)) {
|
|
62
|
+
success(`${name} found`);
|
|
63
|
+
} else {
|
|
64
|
+
warn(`${name} not found`);
|
|
65
|
+
missing.push(name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { missing };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function promptApiKey(rl: ReturnType<typeof createInterface>): Promise<string> {
|
|
72
|
+
// Check CLI args first
|
|
73
|
+
const keyArg = process.argv.find((a) => a.startsWith("--api-key="));
|
|
74
|
+
if (keyArg) {
|
|
75
|
+
const key = keyArg.split("=")[1];
|
|
76
|
+
if (key && key.startsWith("bzr_live_")) {
|
|
77
|
+
return key;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
log("");
|
|
82
|
+
log("You need a Bazaar API key. Get one from the dashboard:");
|
|
83
|
+
log(" → Go to API Keys in the sidebar, then click Create Key");
|
|
84
|
+
log("");
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
const key = await rl.question("Paste your API key (bzr_live_...): ");
|
|
88
|
+
const trimmed = key.trim();
|
|
89
|
+
if (trimmed.startsWith("bzr_live_") && trimmed.length > 20) {
|
|
90
|
+
return trimmed;
|
|
91
|
+
}
|
|
92
|
+
warn("API key should start with 'bzr_live_' — try again");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function promptPlatformUrl(rl: ReturnType<typeof createInterface>): Promise<string> {
|
|
97
|
+
// Check CLI args
|
|
98
|
+
const urlArg = process.argv.find((a) => a.startsWith("--platform-url="));
|
|
99
|
+
if (urlArg) {
|
|
100
|
+
return urlArg.split("=").slice(1).join("=");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log("");
|
|
104
|
+
const url = await rl.question(
|
|
105
|
+
`Platform API URL [${PLATFORM_API_URL_DEFAULT}]: `,
|
|
106
|
+
);
|
|
107
|
+
return url.trim() || PLATFORM_API_URL_DEFAULT;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function validateApiKey(
|
|
111
|
+
apiKey: string,
|
|
112
|
+
platformUrl: string,
|
|
113
|
+
): Promise<boolean> {
|
|
114
|
+
try {
|
|
115
|
+
const res = await fetch(`${platformUrl}/api/users/me`, {
|
|
116
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
117
|
+
});
|
|
118
|
+
if (res.ok) {
|
|
119
|
+
const data = (await res.json()) as Record<string, string>;
|
|
120
|
+
success(`Authenticated as ${data.email || data.name || "user"}`);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
fail(`API key validation failed: ${res.status}`);
|
|
124
|
+
return false;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
fail(`Cannot reach platform at ${platformUrl}: ${err}`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function configureSudoers(
|
|
132
|
+
rl: ReturnType<typeof createInterface>,
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const user = run("whoami");
|
|
135
|
+
if (user === "root") {
|
|
136
|
+
success("Running as root — no sudoers configuration needed");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
log("");
|
|
141
|
+
log("The MCP server needs sudo access for contractor provisioning");
|
|
142
|
+
log("(user creation, SSH config, tmux). This writes a NOPASSWD sudoers");
|
|
143
|
+
log(`rule scoped to specific commands for user '${user}'.`);
|
|
144
|
+
log("");
|
|
145
|
+
|
|
146
|
+
const answer = await rl.question("Configure sudoers now? [Y/n]: ");
|
|
147
|
+
if (answer.trim().toLowerCase() === "n") {
|
|
148
|
+
warn("Skipped sudoers — you'll need to configure this manually or run as root");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const sudoersContent = `# Bazaar MCP server — contractor provisioning
|
|
153
|
+
${user} ALL=(root) NOPASSWD: /usr/sbin/useradd, /usr/sbin/userdel, /usr/sbin/usermod, /usr/bin/pkill
|
|
154
|
+
${user} ALL=(root) NOPASSWD: /usr/sbin/sshd, /bin/systemctl reload sshd, /bin/systemctl reload ssh, /usr/sbin/service ssh reload
|
|
155
|
+
${user} ALL=(root) NOPASSWD: /usr/bin/tee, /bin/chmod, /bin/chown, /bin/cp, /bin/mkdir
|
|
156
|
+
${user} ALL=(ALL:ALL) NOPASSWD: /usr/bin/tmux
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
execSync(
|
|
161
|
+
`echo '${sudoersContent.replace(/'/g, "'\\''")}' | sudo tee /etc/sudoers.d/bazaar-mcp > /dev/null && sudo chmod 0440 /etc/sudoers.d/bazaar-mcp`,
|
|
162
|
+
{ stdio: "pipe" },
|
|
163
|
+
);
|
|
164
|
+
// Validate
|
|
165
|
+
run("sudo visudo -c");
|
|
166
|
+
success("Sudoers rule installed at /etc/sudoers.d/bazaar-mcp");
|
|
167
|
+
} catch (err) {
|
|
168
|
+
warn(`Failed to configure sudoers: ${err}`);
|
|
169
|
+
warn("You may need to run this manually — see the README");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createStateDir(): void {
|
|
174
|
+
try {
|
|
175
|
+
run("sudo mkdir -p /var/lib/human-agents && sudo chmod 777 /var/lib/human-agents");
|
|
176
|
+
success("State directory created at /var/lib/human-agents");
|
|
177
|
+
} catch {
|
|
178
|
+
warn("Could not create /var/lib/human-agents — state will use fallback");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function registerMcpServer(
|
|
183
|
+
apiKey: string,
|
|
184
|
+
platformUrl: string,
|
|
185
|
+
): boolean {
|
|
186
|
+
const isRoot = run("whoami") === "root";
|
|
187
|
+
|
|
188
|
+
// Build the env vars for claude mcp add
|
|
189
|
+
const envFlags = [
|
|
190
|
+
`-e PLATFORM_API_URL=${platformUrl}`,
|
|
191
|
+
`-e PLATFORM_API_KEY=${apiKey}`,
|
|
192
|
+
`-e USE_MOCK_PLATFORM=false`,
|
|
193
|
+
`-e TUNNEL_ENABLED=true`,
|
|
194
|
+
isRoot ? "" : `-e PROVISIONING_USE_SUDO=true`,
|
|
195
|
+
]
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join(" ");
|
|
198
|
+
|
|
199
|
+
// Use npx to run the package (works whether installed locally or globally)
|
|
200
|
+
const cmd = `claude mcp add bazaar ${envFlags} -- npx -y @bazaar.ai/mcp-human-agents`;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
run(cmd, { stdio: "pipe" });
|
|
204
|
+
success("MCP server registered with Claude Code");
|
|
205
|
+
return true;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
// If `claude mcp add` isn't available, show the manual command
|
|
208
|
+
warn("Could not auto-register. Run this manually:");
|
|
209
|
+
log("");
|
|
210
|
+
log(` ${cmd}`);
|
|
211
|
+
log("");
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
export async function runSetup(): Promise<void> {
|
|
219
|
+
const rl = createInterface({ input: stdin, output: stderr });
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
log("");
|
|
223
|
+
log("╔══════════════════════════════════════════╗");
|
|
224
|
+
log("║ Bazaar MCP Server Setup ║");
|
|
225
|
+
log("╚══════════════════════════════════════════╝");
|
|
226
|
+
log("");
|
|
227
|
+
|
|
228
|
+
// 1. Check Claude CLI
|
|
229
|
+
const hasClaude = await checkClaude();
|
|
230
|
+
|
|
231
|
+
// 2. Check system prereqs
|
|
232
|
+
log("");
|
|
233
|
+
log("Checking system prerequisites...");
|
|
234
|
+
const { missing } = checkPrereqs();
|
|
235
|
+
|
|
236
|
+
if (missing.length > 0) {
|
|
237
|
+
log("");
|
|
238
|
+
const answer = await rl.question(
|
|
239
|
+
`Install missing packages (${missing.join(", ")})? [Y/n]: `,
|
|
240
|
+
);
|
|
241
|
+
if (answer.trim().toLowerCase() !== "n") {
|
|
242
|
+
try {
|
|
243
|
+
run(`sudo apt-get update -qq && sudo apt-get install -y ${missing.join(" ")}`, {
|
|
244
|
+
stdio: "inherit",
|
|
245
|
+
});
|
|
246
|
+
success("Packages installed");
|
|
247
|
+
} catch {
|
|
248
|
+
warn("Failed to install packages — please install manually");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 3. Get API key
|
|
254
|
+
const apiKey = await promptApiKey(rl);
|
|
255
|
+
|
|
256
|
+
// 4. Get platform URL
|
|
257
|
+
const platformUrl = await promptPlatformUrl(rl);
|
|
258
|
+
|
|
259
|
+
// 5. Validate API key
|
|
260
|
+
log("");
|
|
261
|
+
log("Validating API key...");
|
|
262
|
+
const valid = await validateApiKey(apiKey, platformUrl);
|
|
263
|
+
if (!valid) {
|
|
264
|
+
warn("Continuing anyway — you can fix the API key later");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 6. Configure sudoers
|
|
268
|
+
await configureSudoers(rl);
|
|
269
|
+
|
|
270
|
+
// 7. Create state directory
|
|
271
|
+
createStateDir();
|
|
272
|
+
|
|
273
|
+
// 8. Start sshd if not running
|
|
274
|
+
try {
|
|
275
|
+
run("pgrep sshd > /dev/null || sudo service ssh start");
|
|
276
|
+
success("sshd is running");
|
|
277
|
+
} catch {
|
|
278
|
+
warn("Could not start sshd — contractor SSH access may not work");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 9. Register MCP server
|
|
282
|
+
log("");
|
|
283
|
+
log("Registering MCP server with Claude Code...");
|
|
284
|
+
if (hasClaude) {
|
|
285
|
+
registerMcpServer(apiKey, platformUrl);
|
|
286
|
+
} else {
|
|
287
|
+
warn("Claude CLI not found — showing manual command:");
|
|
288
|
+
log("");
|
|
289
|
+
const isRoot = run("whoami") === "root";
|
|
290
|
+
const envFlags = [
|
|
291
|
+
`-e PLATFORM_API_URL=${platformUrl}`,
|
|
292
|
+
`-e PLATFORM_API_KEY=${apiKey}`,
|
|
293
|
+
`-e USE_MOCK_PLATFORM=false`,
|
|
294
|
+
`-e TUNNEL_ENABLED=true`,
|
|
295
|
+
isRoot ? "" : `-e PROVISIONING_USE_SUDO=true`,
|
|
296
|
+
]
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
.join(" ");
|
|
299
|
+
log(` claude mcp add bazaar ${envFlags} -- npx -y @bazaar.ai/mcp-human-agents`);
|
|
300
|
+
log("");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Done
|
|
304
|
+
log("");
|
|
305
|
+
log("╔══════════════════════════════════════════╗");
|
|
306
|
+
log("║ Setup Complete! ║");
|
|
307
|
+
log("╚══════════════════════════════════════════╝");
|
|
308
|
+
log("");
|
|
309
|
+
log("Next steps:");
|
|
310
|
+
log(" 1. Restart Claude Code to load the new MCP server");
|
|
311
|
+
log(' 2. Ask Claude to "summon a human contractor"');
|
|
312
|
+
log(" 3. The contractor connects via the web terminal on the dashboard");
|
|
313
|
+
log("");
|
|
314
|
+
} finally {
|
|
315
|
+
rl.close();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Default timeout for shell commands (ms). */
|
|
2
|
+
export const EXEC_TIMEOUT_MS = 30_000;
|
|
3
|
+
|
|
4
|
+
/** How often to poll the platform for contractor assignment (ms). */
|
|
5
|
+
export const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
6
|
+
|
|
7
|
+
/** Max time to wait for a contractor to be assigned (ms). */
|
|
8
|
+
export const DEFAULT_POLL_TIMEOUT_MS = 300_000;
|
|
9
|
+
|
|
10
|
+
/** Seconds to warn a contractor before killing their tmux session. */
|
|
11
|
+
export const DEFAULT_CLEANUP_WARNING_SECONDS = 30;
|
|
12
|
+
|
|
13
|
+
/** Marker comments for sshd_config blocks managed by human-layer. */
|
|
14
|
+
export const SSHD_MARKER_PREFIX = "# BEGIN human-layer:";
|
|
15
|
+
export const SSHD_MARKER_SUFFIX = "# END human-layer:";
|
|
16
|
+
|
|
17
|
+
/** Path to sshd_config. */
|
|
18
|
+
export const SSHD_CONFIG_PATH = "/etc/ssh/sshd_config";
|
|
19
|
+
|
|
20
|
+
/** tmux session name prefix. */
|
|
21
|
+
export const TMUX_SESSION_PREFIX = "hl-";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// TEMPORARY: The default platform URL points to an ngrok tunnel bound to the
|
|
4
|
+
// founder's account. This is fine for early testing but must be replaced with
|
|
5
|
+
// a proper cloud deployment (e.g. https://api.bazaar.ai) before public launch.
|
|
6
|
+
const DEFAULT_PLATFORM_URL = "https://ingrid-hypocycloidal-kaliyah.ngrok-free.dev";
|
|
7
|
+
|
|
8
|
+
const EnvSchema = z.object({
|
|
9
|
+
PLATFORM_API_URL: z.string().url().default(DEFAULT_PLATFORM_URL),
|
|
10
|
+
PLATFORM_API_KEY: z.string().default(""),
|
|
11
|
+
/**
|
|
12
|
+
* Hostname or IP the contractor will SSH into. Only needed when
|
|
13
|
+
* TUNNEL_ENABLED=false (legacy direct-SSH mode). When the dev box is
|
|
14
|
+
* exposed via `ngrok tcp`, set this to the ngrok host (e.g.
|
|
15
|
+
* `0.tcp.ngrok.io`). Default `"auto"` triggers cloud metadata + local
|
|
16
|
+
* network detection.
|
|
17
|
+
*/
|
|
18
|
+
VM_EXTERNAL_IP: z.string().default("auto"),
|
|
19
|
+
/**
|
|
20
|
+
* Public SSH port. Only needed when TUNNEL_ENABLED=false.
|
|
21
|
+
*/
|
|
22
|
+
VM_EXTERNAL_SSH_PORT: z.coerce.number().int().positive().default(22),
|
|
23
|
+
/**
|
|
24
|
+
* Optional explicit project ID to attach gigs to. If unset, the API
|
|
25
|
+
* will auto-create a default project for the user behind the API key.
|
|
26
|
+
*/
|
|
27
|
+
PLATFORM_PROJECT_ID: z.string().optional(),
|
|
28
|
+
POLL_INTERVAL_MS: z.coerce.number().int().positive().default(3_000),
|
|
29
|
+
POLL_TIMEOUT_MS: z.coerce.number().int().positive().default(300_000),
|
|
30
|
+
CLEANUP_WARNING_SECONDS: z.coerce.number().int().positive().default(30),
|
|
31
|
+
USE_MOCK_PLATFORM: z
|
|
32
|
+
.string()
|
|
33
|
+
.transform((v) => v === "true")
|
|
34
|
+
.default("false"),
|
|
35
|
+
STATE_FILE_PATH: z
|
|
36
|
+
.string()
|
|
37
|
+
.default("/var/lib/human-agents/state.json"),
|
|
38
|
+
/**
|
|
39
|
+
* When true, the provisioning code prepends `sudo -n` to privileged
|
|
40
|
+
* commands. Set this to `true` when the MCP server is NOT running as
|
|
41
|
+
* root and instead has NOPASSWD sudo for the relevant commands.
|
|
42
|
+
* See `human-layer/mcp-server/README.md` for the sudoers template.
|
|
43
|
+
*/
|
|
44
|
+
PROVISIONING_USE_SUDO: z
|
|
45
|
+
.string()
|
|
46
|
+
.transform((v) => v === "true")
|
|
47
|
+
.default("false"),
|
|
48
|
+
/**
|
|
49
|
+
* When true, the MCP server opens a reverse WebSocket tunnel to the
|
|
50
|
+
* platform so contractors can connect without ngrok or public IPs.
|
|
51
|
+
* When false, falls back to legacy direct-SSH mode using
|
|
52
|
+
* VM_EXTERNAL_IP / VM_EXTERNAL_SSH_PORT.
|
|
53
|
+
*/
|
|
54
|
+
TUNNEL_ENABLED: z
|
|
55
|
+
.string()
|
|
56
|
+
.transform((v) => v === "true")
|
|
57
|
+
.default("true"),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type Env = z.infer<typeof EnvSchema>;
|
|
61
|
+
|
|
62
|
+
let cachedEnv: Env | null = null;
|
|
63
|
+
|
|
64
|
+
export function getEnv(): Env {
|
|
65
|
+
if (!cachedEnv) {
|
|
66
|
+
cachedEnv = EnvSchema.parse(process.env);
|
|
67
|
+
}
|
|
68
|
+
return cachedEnv;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** For testing: override the cached env. */
|
|
72
|
+
export function setEnv(env: Env): void {
|
|
73
|
+
cachedEnv = env;
|
|
74
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
|
|
5
|
+
export interface ContextInput {
|
|
6
|
+
reason: string;
|
|
7
|
+
context: string;
|
|
8
|
+
skills: string[];
|
|
9
|
+
urgency: "low" | "medium" | "high";
|
|
10
|
+
repo: string;
|
|
11
|
+
branch: string;
|
|
12
|
+
worktreePath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate and write CONTEXT.md to the worktree.
|
|
17
|
+
* This file is the contractor's briefing — everything they need
|
|
18
|
+
* to understand the problem and start working.
|
|
19
|
+
*/
|
|
20
|
+
export async function generateContext(input: ContextInput): Promise<string> {
|
|
21
|
+
const filePath = join(input.worktreePath, "CONTEXT.md");
|
|
22
|
+
|
|
23
|
+
const urgencyLabel = {
|
|
24
|
+
low: "Low — take your time",
|
|
25
|
+
medium: "Medium — needed soon",
|
|
26
|
+
high: "High — urgent fix needed",
|
|
27
|
+
}[input.urgency];
|
|
28
|
+
|
|
29
|
+
const content = `# Contractor Context
|
|
30
|
+
|
|
31
|
+
> This file was auto-generated by the Human Layer MCP server.
|
|
32
|
+
> Read this before starting work.
|
|
33
|
+
|
|
34
|
+
## What Needs Help
|
|
35
|
+
|
|
36
|
+
${input.reason}
|
|
37
|
+
|
|
38
|
+
## Urgency
|
|
39
|
+
|
|
40
|
+
${urgencyLabel}
|
|
41
|
+
|
|
42
|
+
## Skills Requested
|
|
43
|
+
|
|
44
|
+
${input.skills.map((s) => `- ${s}`).join("\n")}
|
|
45
|
+
|
|
46
|
+
## Context
|
|
47
|
+
|
|
48
|
+
${input.context}
|
|
49
|
+
|
|
50
|
+
## Repository
|
|
51
|
+
|
|
52
|
+
- **Repo:** ${input.repo}
|
|
53
|
+
- **Branch:** ${input.branch}
|
|
54
|
+
- **Working directory:** ${input.worktreePath}
|
|
55
|
+
|
|
56
|
+
## What Success Looks Like
|
|
57
|
+
|
|
58
|
+
Resolve the issue described above. Commit your changes to this branch.
|
|
59
|
+
If you have questions or need clarification, check the tmux message area
|
|
60
|
+
or leave comments in the code.
|
|
61
|
+
|
|
62
|
+
## When You're Done
|
|
63
|
+
|
|
64
|
+
The AI agent or user will dismiss the session. Your changes will be
|
|
65
|
+
reviewed and optionally merged. Make sure to commit before leaving.
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
await writeFile(filePath, content, "utf-8");
|
|
69
|
+
logger.info("CONTEXT.md written", { path: filePath });
|
|
70
|
+
return filePath;
|
|
71
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional context sections that can be appended to CONTEXT.md
|
|
3
|
+
* based on the scenario.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function stuckAgentSection(
|
|
7
|
+
attempts: number,
|
|
8
|
+
lastError: string,
|
|
9
|
+
): string {
|
|
10
|
+
return `
|
|
11
|
+
## AI Agent Status
|
|
12
|
+
|
|
13
|
+
The AI agent has been stuck on this problem for **${attempts} attempts**.
|
|
14
|
+
Last error encountered:
|
|
15
|
+
|
|
16
|
+
\`\`\`
|
|
17
|
+
${lastError}
|
|
18
|
+
\`\`\`
|
|
19
|
+
|
|
20
|
+
The agent was unable to resolve this on its own and has requested
|
|
21
|
+
human assistance.
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function domainGapSection(domain: string): string {
|
|
26
|
+
return `
|
|
27
|
+
## Domain Knowledge Needed
|
|
28
|
+
|
|
29
|
+
This task requires expertise in **${domain}** that the AI agent does
|
|
30
|
+
not have. Your domain knowledge is the key value-add here.
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function verificationSection(environment: string): string {
|
|
35
|
+
return `
|
|
36
|
+
## Verification Needed
|
|
37
|
+
|
|
38
|
+
The AI agent needs someone to verify behavior in a real **${environment}**
|
|
39
|
+
environment that it cannot access directly.
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { exec } from "../utils/exec.js";
|
|
2
|
+
import { GitError } from "../utils/errors.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the current branch name.
|
|
6
|
+
*/
|
|
7
|
+
export async function getCurrentBranch(cwd?: string): Promise<string> {
|
|
8
|
+
try {
|
|
9
|
+
const { stdout } = await exec("git rev-parse --abbrev-ref HEAD", {
|
|
10
|
+
cwd,
|
|
11
|
+
});
|
|
12
|
+
return stdout.trim();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
throw new GitError(`Failed to get current branch: ${error}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the current repo name (from remote or directory).
|
|
20
|
+
*/
|
|
21
|
+
export async function getRepoName(cwd?: string): Promise<string> {
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await exec(
|
|
24
|
+
"git remote get-url origin 2>/dev/null || basename $(git rev-parse --show-toplevel)",
|
|
25
|
+
{ cwd },
|
|
26
|
+
);
|
|
27
|
+
const url = stdout.trim();
|
|
28
|
+
// Extract repo name from URL
|
|
29
|
+
const match = url.match(/\/([^/]+?)(?:\.git)?$/);
|
|
30
|
+
return match ? match[1] : url;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new GitError(`Failed to get repo name: ${error}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the top-level directory of the git repo.
|
|
38
|
+
*/
|
|
39
|
+
export async function getRepoRoot(cwd?: string): Promise<string> {
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await exec("git rev-parse --show-toplevel", { cwd });
|
|
42
|
+
return stdout.trim();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new GitError(`Failed to get repo root: ${error}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/git/diff.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { exec } from "../utils/exec.js";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the list of files changed by the contractor.
|
|
6
|
+
* Uses git diff to compare the state before and after.
|
|
7
|
+
*/
|
|
8
|
+
export async function getFilesChanged(cwd?: string): Promise<string[]> {
|
|
9
|
+
try {
|
|
10
|
+
const { stdout } = await exec("git diff --name-only HEAD~1", { cwd });
|
|
11
|
+
const files = stdout
|
|
12
|
+
.trim()
|
|
13
|
+
.split("\n")
|
|
14
|
+
.filter((f) => f.length > 0);
|
|
15
|
+
logger.debug("Files changed", { count: files.length });
|
|
16
|
+
return files;
|
|
17
|
+
} catch {
|
|
18
|
+
// If HEAD~1 doesn't exist or no changes
|
|
19
|
+
logger.debug("Could not determine files changed");
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get a summary of changes (for completion report).
|
|
26
|
+
*/
|
|
27
|
+
export async function getDiffSummary(cwd?: string): Promise<string> {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await exec("git diff --stat HEAD~1", { cwd });
|
|
30
|
+
return stdout.trim();
|
|
31
|
+
} catch {
|
|
32
|
+
return "No changes detected";
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/git/index.ts
ADDED
package/src/git/merge.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { exec } from "../utils/exec.js";
|
|
2
|
+
import { GitError } from "../utils/errors.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merge contractor's changes from a worktree branch back
|
|
7
|
+
* into the working branch.
|
|
8
|
+
*/
|
|
9
|
+
export async function mergeContractorChanges(
|
|
10
|
+
worktreePath: string,
|
|
11
|
+
cwd?: string,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
// Get the branch name from the worktree
|
|
15
|
+
const { stdout: branch } = await exec(
|
|
16
|
+
"git rev-parse --abbrev-ref HEAD",
|
|
17
|
+
{ cwd: worktreePath },
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// First, commit any uncommitted changes in the worktree
|
|
21
|
+
try {
|
|
22
|
+
await exec(
|
|
23
|
+
'git add -A && git commit -m "Contractor changes (auto-commit)"',
|
|
24
|
+
{ cwd: worktreePath },
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
// No changes to commit — that's fine
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Merge into the main working directory
|
|
31
|
+
await exec(`git merge ${branch.trim()}`, { cwd });
|
|
32
|
+
logger.info("Contractor changes merged", { from: branch.trim() });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new GitError(`Failed to merge contractor changes: ${error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|