@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.
Files changed (259) hide show
  1. package/.env.example +15 -0
  2. package/README.md +178 -0
  3. package/dist/mcp-server/src/bin.d.ts +3 -0
  4. package/dist/mcp-server/src/bin.d.ts.map +1 -0
  5. package/dist/mcp-server/src/bin.js +10 -0
  6. package/dist/mcp-server/src/bin.js.map +1 -0
  7. package/dist/mcp-server/src/cli/setup.d.ts +2 -0
  8. package/dist/mcp-server/src/cli/setup.d.ts.map +1 -0
  9. package/dist/mcp-server/src/cli/setup.js +274 -0
  10. package/dist/mcp-server/src/cli/setup.js.map +1 -0
  11. package/dist/mcp-server/src/config/defaults.d.ts +16 -0
  12. package/dist/mcp-server/src/config/defaults.d.ts.map +1 -0
  13. package/dist/mcp-server/src/config/defaults.js +19 -0
  14. package/dist/mcp-server/src/config/defaults.js.map +1 -0
  15. package/dist/mcp-server/src/config/env.d.ts +73 -0
  16. package/dist/mcp-server/src/config/env.d.ts.map +1 -0
  17. package/dist/mcp-server/src/config/env.js +72 -0
  18. package/dist/mcp-server/src/config/env.js.map +1 -0
  19. package/dist/mcp-server/src/config/index.d.ts +3 -0
  20. package/dist/mcp-server/src/config/index.d.ts.map +1 -0
  21. package/dist/mcp-server/src/config/index.js +22 -0
  22. package/dist/mcp-server/src/config/index.js.map +1 -0
  23. package/dist/mcp-server/src/context/generator.d.ts +16 -0
  24. package/dist/mcp-server/src/context/generator.d.ts.map +1 -0
  25. package/dist/mcp-server/src/context/generator.js +61 -0
  26. package/dist/mcp-server/src/context/generator.js.map +1 -0
  27. package/dist/mcp-server/src/context/index.d.ts +3 -0
  28. package/dist/mcp-server/src/context/index.d.ts.map +1 -0
  29. package/dist/mcp-server/src/context/index.js +10 -0
  30. package/dist/mcp-server/src/context/index.js.map +1 -0
  31. package/dist/mcp-server/src/context/templates.d.ts +8 -0
  32. package/dist/mcp-server/src/context/templates.d.ts.map +1 -0
  33. package/dist/mcp-server/src/context/templates.js +41 -0
  34. package/dist/mcp-server/src/context/templates.js.map +1 -0
  35. package/dist/mcp-server/src/git/branch.d.ts +13 -0
  36. package/dist/mcp-server/src/git/branch.d.ts.map +1 -0
  37. package/dist/mcp-server/src/git/branch.js +49 -0
  38. package/dist/mcp-server/src/git/branch.js.map +1 -0
  39. package/dist/mcp-server/src/git/diff.d.ts +10 -0
  40. package/dist/mcp-server/src/git/diff.d.ts.map +1 -0
  41. package/dist/mcp-server/src/git/diff.js +39 -0
  42. package/dist/mcp-server/src/git/diff.js.map +1 -0
  43. package/dist/mcp-server/src/git/index.d.ts +5 -0
  44. package/dist/mcp-server/src/git/index.d.ts.map +1 -0
  45. package/dist/mcp-server/src/git/index.js +16 -0
  46. package/dist/mcp-server/src/git/index.js.map +1 -0
  47. package/dist/mcp-server/src/git/merge.d.ts +6 -0
  48. package/dist/mcp-server/src/git/merge.d.ts.map +1 -0
  49. package/dist/mcp-server/src/git/merge.js +30 -0
  50. package/dist/mcp-server/src/git/merge.js.map +1 -0
  51. package/dist/mcp-server/src/git/worktree.d.ts +11 -0
  52. package/dist/mcp-server/src/git/worktree.d.ts.map +1 -0
  53. package/dist/mcp-server/src/git/worktree.js +38 -0
  54. package/dist/mcp-server/src/git/worktree.js.map +1 -0
  55. package/dist/mcp-server/src/http-wrapper.d.ts +6 -0
  56. package/dist/mcp-server/src/http-wrapper.d.ts.map +1 -0
  57. package/dist/mcp-server/src/http-wrapper.js +85 -0
  58. package/dist/mcp-server/src/http-wrapper.js.map +1 -0
  59. package/dist/mcp-server/src/index.d.ts +2 -0
  60. package/dist/mcp-server/src/index.d.ts.map +1 -0
  61. package/dist/mcp-server/src/index.js +28 -0
  62. package/dist/mcp-server/src/index.js.map +1 -0
  63. package/dist/mcp-server/src/platform-client/client.d.ts +17 -0
  64. package/dist/mcp-server/src/platform-client/client.d.ts.map +1 -0
  65. package/dist/mcp-server/src/platform-client/client.js +68 -0
  66. package/dist/mcp-server/src/platform-client/client.js.map +1 -0
  67. package/dist/mcp-server/src/platform-client/index.d.ts +5 -0
  68. package/dist/mcp-server/src/platform-client/index.d.ts.map +1 -0
  69. package/dist/mcp-server/src/platform-client/index.js +10 -0
  70. package/dist/mcp-server/src/platform-client/index.js.map +1 -0
  71. package/dist/mcp-server/src/platform-client/mock-client.d.ts +28 -0
  72. package/dist/mcp-server/src/platform-client/mock-client.d.ts.map +1 -0
  73. package/dist/mcp-server/src/platform-client/mock-client.js +75 -0
  74. package/dist/mcp-server/src/platform-client/mock-client.js.map +1 -0
  75. package/dist/mcp-server/src/platform-client/polling.d.ts +9 -0
  76. package/dist/mcp-server/src/platform-client/polling.d.ts.map +1 -0
  77. package/dist/mcp-server/src/platform-client/polling.js +40 -0
  78. package/dist/mcp-server/src/platform-client/polling.js.map +1 -0
  79. package/dist/mcp-server/src/platform-client/types.d.ts +2 -0
  80. package/dist/mcp-server/src/platform-client/types.d.ts.map +1 -0
  81. package/dist/mcp-server/src/platform-client/types.js +3 -0
  82. package/dist/mcp-server/src/platform-client/types.js.map +1 -0
  83. package/dist/mcp-server/src/provisioning/authorized-keys.d.ts +14 -0
  84. package/dist/mcp-server/src/provisioning/authorized-keys.d.ts.map +1 -0
  85. package/dist/mcp-server/src/provisioning/authorized-keys.js +48 -0
  86. package/dist/mcp-server/src/provisioning/authorized-keys.js.map +1 -0
  87. package/dist/mcp-server/src/provisioning/cleanup.d.ts +19 -0
  88. package/dist/mcp-server/src/provisioning/cleanup.d.ts.map +1 -0
  89. package/dist/mcp-server/src/provisioning/cleanup.js +96 -0
  90. package/dist/mcp-server/src/provisioning/cleanup.js.map +1 -0
  91. package/dist/mcp-server/src/provisioning/index.d.ts +6 -0
  92. package/dist/mcp-server/src/provisioning/index.d.ts.map +1 -0
  93. package/dist/mcp-server/src/provisioning/index.js +24 -0
  94. package/dist/mcp-server/src/provisioning/index.js.map +1 -0
  95. package/dist/mcp-server/src/provisioning/linux-user.d.ts +15 -0
  96. package/dist/mcp-server/src/provisioning/linux-user.d.ts.map +1 -0
  97. package/dist/mcp-server/src/provisioning/linux-user.js +62 -0
  98. package/dist/mcp-server/src/provisioning/linux-user.js.map +1 -0
  99. package/dist/mcp-server/src/provisioning/privileged.d.ts +40 -0
  100. package/dist/mcp-server/src/provisioning/privileged.d.ts.map +1 -0
  101. package/dist/mcp-server/src/provisioning/privileged.js +123 -0
  102. package/dist/mcp-server/src/provisioning/privileged.js.map +1 -0
  103. package/dist/mcp-server/src/provisioning/ssh-config.d.ts +21 -0
  104. package/dist/mcp-server/src/provisioning/ssh-config.d.ts.map +1 -0
  105. package/dist/mcp-server/src/provisioning/ssh-config.js +161 -0
  106. package/dist/mcp-server/src/provisioning/ssh-config.js.map +1 -0
  107. package/dist/mcp-server/src/provisioning/tmux-session.d.ts +37 -0
  108. package/dist/mcp-server/src/provisioning/tmux-session.d.ts.map +1 -0
  109. package/dist/mcp-server/src/provisioning/tmux-session.js +123 -0
  110. package/dist/mcp-server/src/provisioning/tmux-session.js.map +1 -0
  111. package/dist/mcp-server/src/server.d.ts +3 -0
  112. package/dist/mcp-server/src/server.d.ts.map +1 -0
  113. package/dist/mcp-server/src/server.js +67 -0
  114. package/dist/mcp-server/src/server.js.map +1 -0
  115. package/dist/mcp-server/src/state/gig-store.d.ts +19 -0
  116. package/dist/mcp-server/src/state/gig-store.d.ts.map +1 -0
  117. package/dist/mcp-server/src/state/gig-store.js +52 -0
  118. package/dist/mcp-server/src/state/gig-store.js.map +1 -0
  119. package/dist/mcp-server/src/state/index.d.ts +4 -0
  120. package/dist/mcp-server/src/state/index.d.ts.map +1 -0
  121. package/dist/mcp-server/src/state/index.js +8 -0
  122. package/dist/mcp-server/src/state/index.js.map +1 -0
  123. package/dist/mcp-server/src/state/persistence.d.ts +13 -0
  124. package/dist/mcp-server/src/state/persistence.d.ts.map +1 -0
  125. package/dist/mcp-server/src/state/persistence.js +48 -0
  126. package/dist/mcp-server/src/state/persistence.js.map +1 -0
  127. package/dist/mcp-server/src/state/types.d.ts +15 -0
  128. package/dist/mcp-server/src/state/types.d.ts.map +1 -0
  129. package/dist/mcp-server/src/state/types.js +3 -0
  130. package/dist/mcp-server/src/state/types.js.map +1 -0
  131. package/dist/mcp-server/src/tools/dismiss-human.d.ts +25 -0
  132. package/dist/mcp-server/src/tools/dismiss-human.d.ts.map +1 -0
  133. package/dist/mcp-server/src/tools/dismiss-human.js +78 -0
  134. package/dist/mcp-server/src/tools/dismiss-human.js.map +1 -0
  135. package/dist/mcp-server/src/tools/index.d.ts +9 -0
  136. package/dist/mcp-server/src/tools/index.d.ts.map +1 -0
  137. package/dist/mcp-server/src/tools/index.js +20 -0
  138. package/dist/mcp-server/src/tools/index.js.map +1 -0
  139. package/dist/mcp-server/src/tools/list-humans.d.ts +18 -0
  140. package/dist/mcp-server/src/tools/list-humans.d.ts.map +1 -0
  141. package/dist/mcp-server/src/tools/list-humans.js +35 -0
  142. package/dist/mcp-server/src/tools/list-humans.js.map +1 -0
  143. package/dist/mcp-server/src/tools/message-human.d.ts +10 -0
  144. package/dist/mcp-server/src/tools/message-human.d.ts.map +1 -0
  145. package/dist/mcp-server/src/tools/message-human.js +19 -0
  146. package/dist/mcp-server/src/tools/message-human.js.map +1 -0
  147. package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.d.ts +19 -0
  148. package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.d.ts.map +1 -0
  149. package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.js +22 -0
  150. package/dist/mcp-server/src/tools/schemas/dismiss-human.schema.js.map +1 -0
  151. package/dist/mcp-server/src/tools/schemas/list-humans.schema.d.ts +4 -0
  152. package/dist/mcp-server/src/tools/schemas/list-humans.schema.d.ts.map +1 -0
  153. package/dist/mcp-server/src/tools/schemas/list-humans.schema.js +7 -0
  154. package/dist/mcp-server/src/tools/schemas/list-humans.schema.js.map +1 -0
  155. package/dist/mcp-server/src/tools/schemas/message-human.schema.d.ts +13 -0
  156. package/dist/mcp-server/src/tools/schemas/message-human.schema.d.ts.map +1 -0
  157. package/dist/mcp-server/src/tools/schemas/message-human.schema.js +9 -0
  158. package/dist/mcp-server/src/tools/schemas/message-human.schema.js.map +1 -0
  159. package/dist/mcp-server/src/tools/schemas/summon-human.schema.d.ts +22 -0
  160. package/dist/mcp-server/src/tools/schemas/summon-human.schema.d.ts.map +1 -0
  161. package/dist/mcp-server/src/tools/schemas/summon-human.schema.js +18 -0
  162. package/dist/mcp-server/src/tools/schemas/summon-human.schema.js.map +1 -0
  163. package/dist/mcp-server/src/tools/summon-human.d.ts +31 -0
  164. package/dist/mcp-server/src/tools/summon-human.d.ts.map +1 -0
  165. package/dist/mcp-server/src/tools/summon-human.js +137 -0
  166. package/dist/mcp-server/src/tools/summon-human.js.map +1 -0
  167. package/dist/mcp-server/src/tunnel/client.d.ts +16 -0
  168. package/dist/mcp-server/src/tunnel/client.d.ts.map +1 -0
  169. package/dist/mcp-server/src/tunnel/client.js +100 -0
  170. package/dist/mcp-server/src/tunnel/client.js.map +1 -0
  171. package/dist/mcp-server/src/tunnel/index.d.ts +6 -0
  172. package/dist/mcp-server/src/tunnel/index.d.ts.map +1 -0
  173. package/dist/mcp-server/src/tunnel/index.js +28 -0
  174. package/dist/mcp-server/src/tunnel/index.js.map +1 -0
  175. package/dist/mcp-server/src/utils/errors.d.ts +28 -0
  176. package/dist/mcp-server/src/utils/errors.d.ts.map +1 -0
  177. package/dist/mcp-server/src/utils/errors.js +66 -0
  178. package/dist/mcp-server/src/utils/errors.js.map +1 -0
  179. package/dist/mcp-server/src/utils/exec.d.ts +7 -0
  180. package/dist/mcp-server/src/utils/exec.d.ts.map +1 -0
  181. package/dist/mcp-server/src/utils/exec.js +22 -0
  182. package/dist/mcp-server/src/utils/exec.js.map +1 -0
  183. package/dist/mcp-server/src/utils/ip.d.ts +6 -0
  184. package/dist/mcp-server/src/utils/ip.d.ts.map +1 -0
  185. package/dist/mcp-server/src/utils/ip.js +33 -0
  186. package/dist/mcp-server/src/utils/ip.js.map +1 -0
  187. package/dist/mcp-server/src/utils/logger.d.ts +20 -0
  188. package/dist/mcp-server/src/utils/logger.d.ts.map +1 -0
  189. package/dist/mcp-server/src/utils/logger.js +41 -0
  190. package/dist/mcp-server/src/utils/logger.js.map +1 -0
  191. package/dist/shared/src/contractor.types.d.ts +20 -0
  192. package/dist/shared/src/contractor.types.d.ts.map +1 -0
  193. package/dist/shared/src/contractor.types.js +3 -0
  194. package/dist/shared/src/contractor.types.js.map +1 -0
  195. package/dist/shared/src/gig.types.d.ts +32 -0
  196. package/dist/shared/src/gig.types.d.ts.map +1 -0
  197. package/dist/shared/src/gig.types.js +21 -0
  198. package/dist/shared/src/gig.types.js.map +1 -0
  199. package/dist/shared/src/index.d.ts +5 -0
  200. package/dist/shared/src/index.d.ts.map +1 -0
  201. package/dist/shared/src/index.js +21 -0
  202. package/dist/shared/src/index.js.map +1 -0
  203. package/dist/shared/src/mcp-tool.types.d.ts +45 -0
  204. package/dist/shared/src/mcp-tool.types.d.ts.map +1 -0
  205. package/dist/shared/src/mcp-tool.types.js +3 -0
  206. package/dist/shared/src/mcp-tool.types.js.map +1 -0
  207. package/dist/shared/src/platform-api.types.d.ts +73 -0
  208. package/dist/shared/src/platform-api.types.d.ts.map +1 -0
  209. package/dist/shared/src/platform-api.types.js +3 -0
  210. package/dist/shared/src/platform-api.types.js.map +1 -0
  211. package/package.json +41 -0
  212. package/src/bin.ts +7 -0
  213. package/src/cli/setup.ts +317 -0
  214. package/src/config/defaults.ts +21 -0
  215. package/src/config/env.ts +74 -0
  216. package/src/config/index.ts +2 -0
  217. package/src/context/generator.ts +71 -0
  218. package/src/context/index.ts +6 -0
  219. package/src/context/templates.ts +41 -0
  220. package/src/git/branch.ts +46 -0
  221. package/src/git/diff.ts +34 -0
  222. package/src/git/index.ts +4 -0
  223. package/src/git/merge.ts +36 -0
  224. package/src/git/worktree.ts +42 -0
  225. package/src/http-wrapper.ts +94 -0
  226. package/src/index.ts +32 -0
  227. package/src/platform-client/client.ts +93 -0
  228. package/src/platform-client/index.ts +4 -0
  229. package/src/platform-client/mock-client.ts +92 -0
  230. package/src/platform-client/polling.ts +53 -0
  231. package/src/platform-client/types.ts +9 -0
  232. package/src/provisioning/authorized-keys.ts +52 -0
  233. package/src/provisioning/cleanup.ts +106 -0
  234. package/src/provisioning/index.ts +13 -0
  235. package/src/provisioning/linux-user.ts +66 -0
  236. package/src/provisioning/privileged.ts +128 -0
  237. package/src/provisioning/ssh-config.ts +197 -0
  238. package/src/provisioning/tmux-session.ts +136 -0
  239. package/src/server.ts +111 -0
  240. package/src/state/gig-store.ts +56 -0
  241. package/src/state/index.ts +3 -0
  242. package/src/state/persistence.ts +42 -0
  243. package/src/state/types.ts +14 -0
  244. package/src/tools/dismiss-human.ts +103 -0
  245. package/src/tools/index.ts +9 -0
  246. package/src/tools/list-humans.ts +54 -0
  247. package/src/tools/message-human.ts +28 -0
  248. package/src/tools/schemas/dismiss-human.schema.ts +21 -0
  249. package/src/tools/schemas/list-humans.schema.ts +6 -0
  250. package/src/tools/schemas/message-human.schema.ts +8 -0
  251. package/src/tools/schemas/summon-human.schema.ts +19 -0
  252. package/src/tools/summon-human.ts +180 -0
  253. package/src/tunnel/client.ts +116 -0
  254. package/src/tunnel/index.ts +26 -0
  255. package/src/utils/errors.ts +64 -0
  256. package/src/utils/exec.ts +29 -0
  257. package/src/utils/ip.ts +31 -0
  258. package/src/utils/logger.ts +55 -0
  259. package/tsconfig.json +20 -0
@@ -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,2 @@
1
+ export { getEnv, setEnv, type Env } from "./env.js";
2
+ export * from "./defaults.js";
@@ -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,6 @@
1
+ export { generateContext, type ContextInput } from "./generator.js";
2
+ export {
3
+ stuckAgentSection,
4
+ domainGapSection,
5
+ verificationSection,
6
+ } from "./templates.js";
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export { getCurrentBranch, getRepoName, getRepoRoot } from "./branch.js";
2
+ export { createWorktree, removeWorktree } from "./worktree.js";
3
+ export { mergeContractorChanges } from "./merge.js";
4
+ export { getFilesChanged, getDiffSummary } from "./diff.js";
@@ -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
+ }