bridge-agent 0.3.4 → 0.3.5
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 +9 -0
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.mjs +10 -0
package/README.md
CHANGED
|
@@ -94,6 +94,15 @@ In production, set `BRIDGE_MCP_URL` to enable HTTP MCP transport:
|
|
|
94
94
|
BRIDGE_MCP_URL=https://your-server.com bridge-agent start
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
### Bridge MCP is per-panel
|
|
98
|
+
|
|
99
|
+
`bridge-mcp` is the MCP stdio server used by each panel. The Jerico daemon
|
|
100
|
+
spawns it automatically with per-panel credentials via `--mcp-config`. You
|
|
101
|
+
do not need to register `bridge-mcp` as a global MCP server in Claude Code
|
|
102
|
+
— if you do, it will fail with `Missing env vars for stdio mode` because
|
|
103
|
+
per-panel context (workspace, project, panel IDs) isn't available outside
|
|
104
|
+
a daemon-spawned panel.
|
|
105
|
+
|
|
97
106
|
## Supported Agents
|
|
98
107
|
|
|
99
108
|
| Agent | Key | Notes |
|
package/dist/index.js
CHANGED
|
@@ -392,4 +392,4 @@ ${xe("bridge_get_my_task","bridge_complete_task","bridge_fail_task","bridge_get_
|
|
|
392
392
|
</plist>
|
|
393
393
|
`;try{return(0,T.writeFileSync)(ft,t,"utf-8"),!0}catch(n){return console.warn("[bridge] launchd.plist.write.failed",{error:String(n)}),!1}}function Ta(){try{return(0,le.execSync)(`launchctl kickstart -kp gui/$(id -u)/${Es} 2>/dev/null; launchctl unload "${ft}" 2>/dev/null; launchctl load "${ft}"`,{stdio:"pipe"}),{ok:!0,permissionDenied:!1}}catch(r){let e=String(r);return{ok:!1,permissionDenied:e.includes("Permission denied")||e.includes("not allowed")||e.includes("bootstrap")}}}function Pa(r){try{let e=(0,le.spawn)(r,["start"],{detached:!0,stdio:"ignore",env:{...process.env,PATH:xs(),BRIDGE_DAEMON:"1"}});e.unref(),setTimeout(()=>{e.pid&&process.kill(e.pid,0)?(console.log("[bridge] daemon.pid",{pid:e.pid}),console.log("[bridge] background.ok",{log:pr})):console.error("[bridge] background.failed \u2014 check: tail -f",{log:gr})},2e3)}catch(e){console.error("[bridge] background.spawn.failed",{error:String(e)})}}function Na(){let r=parseInt(process.env.HEALTH_PORT??"3101",10),e=Date.now()+6e3,t=()=>{if(Date.now()>e){console.error("[bridge] health.verify.timeout \u2014 daemon may have crashed immediately");return}try{let s=require("node:http").get(`http://127.0.0.1:${r}/health`,i=>{i.statusCode===200?console.log("[bridge] health.verify.ok"):setTimeout(t,500)});s.on("error",()=>{setTimeout(t,500)}),s.setTimeout(1e3,()=>{s.destroy(),setTimeout(t,500)})}catch{setTimeout(t,500)}};setTimeout(t,1e3)}function Ra(){let r=new Ve;ws(r);let e=parseInt(process.env.HEALTH_PORT??"3101",10),t=(0,Ss.createServer)((n,s)=>{let i=bs(),o=JSON.stringify({status:"ok",connected:i,uptime:process.uptime()});s.writeHead(i?200:503,{"Content-Type":"application/json"}),s.end(o)});t.listen(e,"127.0.0.1",()=>{console.log(`[bridge] health. listening on 127.0.0.1:${e}`)}),t.on("error",n=>{console.error("[bridge] health.error",{error:n.message})})}function ks(){let r=process.env.BRIDGE_DAEMON==="1"||process.argv.includes("--daemon");if(console.log("[bridge] Starting bridge-agent daemon..."),r){Ra();return}Ia()||(console.warn("[bridge] start.aborted.already.running"),process.exit(1));let e=Ca(),t=Aa(e),{ok:n,permissionDenied:s}=t?Ta():{ok:!1,permissionDenied:!1};if(n){console.log("[bridge] launchd.ok \u2014 managed, auto-restart enabled"),console.log("[bridge] logs: tail -f",{out:pr,err:gr}),Na(),process.exit(0);return}s&&(console.warn("[bridge] launchd.permission.denied"),console.warn("[bridge] \u2192 Auto-start on login requires:"),console.warn(`[bridge] sudo launchctl bootstrap gui/$(id -u) "${ft}"`),console.warn(`[bridge] Falling back to background process...
|
|
394
394
|
`)),Pa(e),process.exit(0)}var Os=_(require("https")),Cs=_(require("http"));Ee();function Da(r){return(r??"").trim()}async function Is(r,e=!1,t){console.log("[bridge] Starting auth flow..."),console.log(`[bridge] Server: ${r}`),console.log("[bridge] Open this URL to generate a daemon token:"),console.log(` ${r}/connect`);let n=Da(t);n&&console.log("[bridge] Using token from --token"),e&&(n?console.log("[bridge] --no-browser ignored because --token is provided."):(console.log("[bridge] --no-browser: exiting after printing URL."),process.exit(0)));let s=n;s||(console.log(),console.log("[bridge] After authenticating, paste your token here:"),s=await ja()),s||(console.error("[bridge] No token provided. Exiting."),process.exit(1)),await La(r,s)||(console.error("[bridge] Token validation failed. Please try again."),process.exit(1));let l=r.replace(/^https?:\/\//,a=>a.startsWith("https")?"wss://":"ws://").replace(/\/?$/,"/ws/daemon");oe({server:l,token:s,name:process.env.HOSTNAME??"My Machine"}),console.log("[bridge] Auth successful! Config saved to ~/.bridge/config.json"),console.log("[bridge] Run: bridge-agent start"),process.exit(0)}async function ja(){return new Promise(r=>{process.stdout.write("Token: ");let e="";process.stdin.setEncoding("utf-8"),process.stdin.on("data",t=>{e+=t,e.includes(`
|
|
395
|
-
`)&&(process.stdin.pause(),r(e.trim()))}),process.stdin.resume()})}async function La(r,e){return new Promise(t=>{let n=new URL("/api/tokens/validate",r),s=n.protocol==="https:",i=s?Os.default:Cs.default,o={hostname:n.hostname,port:n.port||(s?443:80),path:n.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`}},l=i.request(o,a=>{t(a.statusCode===200)});l.on("error",()=>t(!1)),l.end()})}var As=_(require("https")),Ts=_(require("http")),mr=_(require("fs")),_r=_(require("path")),Ps=require("node:crypto");Ee();function Ma(r){return r.replace(/^wss?:/,e=>e==="wss:"?"https:":"http:").replace(/\/ws(\/.*)?$/,"")}async function Ns(r,e,t){let n=ie(),s=(0,Ps.createHash)("sha256").update(n.token).digest("hex"),i=Ma(n.server),o=_r.default.resolve(t);_r.default.isAbsolute(o)||(console.error("[bridge] link-project: path must be absolute"),process.exit(1)),mr.default.existsSync(o)||(console.error("[bridge] link-project: path does not exist:",o),process.exit(1)),mr.default.statSync(o).isDirectory()||(console.error("[bridge] link-project: path must be a directory:",o),process.exit(1));let a=new URL(`/api/workspaces/${r}/projects/${e}/machine-paths`,i),c=a.protocol==="https:",d=c?As.default:Ts.default;n.projectPaths={...n.projectPaths??{},[e]:o},oe(n),console.log("[cli] link-project.local_json_written",{projectId:e,path:o});let u=JSON.stringify({daemonId:s,localPath:o}),h=await new Promise((g,p)=>{let f=d.request({hostname:a.hostname,port:a.port||(c?443:80),path:a.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.token}`,"Content-Length":Buffer.byteLength(u)}},m=>{let x="";m.on("data",b=>{x+=b}),m.on("end",()=>{if(m.statusCode===200)g(200);else{try{let b=JSON.parse(x);console.error("[bridge] link-project failed:",b.error??`HTTP ${m.statusCode}`)}catch{console.error("[bridge] link-project failed:",`HTTP ${m.statusCode}`)}g(m.statusCode??0)}})});f.on("error",m=>{p(m)}),f.write(u),f.end()});h===200?(console.log("[cli] link-project.server_success",{projectId:e}),console.log("[cli] link-project.success (dual-write)"),console.log(` workspace: ${r}`),console.log(` project: ${e}`),console.log(` daemon: ${s.slice(0,16)}\u2026`),console.log(` path: ${o}`),console.log("[cli] Next spawn for this project will use the linked path."),process.exit(0)):(console.warn("[cli] link-project.server_fail",{projectId:e,statusCode:h}),console.log("[cli] Local override still active \u2014 path will work on this machine"),process.exit(0))}Ee();function $a(r){return r.replace(/^wss?:/,e=>e==="wss:"?"https:":"http:").replace(/\/ws(\/.*)?$/,"")}async function Rs(){let r=ie(),e=$a(r.server),t=await fetch(`${e}/api/admin/cleanup-orphans`,{method:"POST",headers:{Authorization:`Bearer ${r.token}`,"Content-Type":"application/json"},body:"{}"});t.ok||(console.error(`[cli] cleanup-orphans: HTTP ${t.status}`),process.exit(1));let{deleted:n}=await t.json();console.log(`[cli] cleanup-orphans: deleted ${n} orphaned path ${n===1?"entry":"entries"}`),process.exit(0)}var de=new Pr;de.name("bridge-agent").description("Bridge local agent \u2014 connects your AI tools to Jerico").version("0.3.
|
|
395
|
+
`)&&(process.stdin.pause(),r(e.trim()))}),process.stdin.resume()})}async function La(r,e){return new Promise(t=>{let n=new URL("/api/tokens/validate",r),s=n.protocol==="https:",i=s?Os.default:Cs.default,o={hostname:n.hostname,port:n.port||(s?443:80),path:n.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`}},l=i.request(o,a=>{t(a.statusCode===200)});l.on("error",()=>t(!1)),l.end()})}var As=_(require("https")),Ts=_(require("http")),mr=_(require("fs")),_r=_(require("path")),Ps=require("node:crypto");Ee();function Ma(r){return r.replace(/^wss?:/,e=>e==="wss:"?"https:":"http:").replace(/\/ws(\/.*)?$/,"")}async function Ns(r,e,t){let n=ie(),s=(0,Ps.createHash)("sha256").update(n.token).digest("hex"),i=Ma(n.server),o=_r.default.resolve(t);_r.default.isAbsolute(o)||(console.error("[bridge] link-project: path must be absolute"),process.exit(1)),mr.default.existsSync(o)||(console.error("[bridge] link-project: path does not exist:",o),process.exit(1)),mr.default.statSync(o).isDirectory()||(console.error("[bridge] link-project: path must be a directory:",o),process.exit(1));let a=new URL(`/api/workspaces/${r}/projects/${e}/machine-paths`,i),c=a.protocol==="https:",d=c?As.default:Ts.default;n.projectPaths={...n.projectPaths??{},[e]:o},oe(n),console.log("[cli] link-project.local_json_written",{projectId:e,path:o});let u=JSON.stringify({daemonId:s,localPath:o}),h=await new Promise((g,p)=>{let f=d.request({hostname:a.hostname,port:a.port||(c?443:80),path:a.pathname,method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.token}`,"Content-Length":Buffer.byteLength(u)}},m=>{let x="";m.on("data",b=>{x+=b}),m.on("end",()=>{if(m.statusCode===200)g(200);else{try{let b=JSON.parse(x);console.error("[bridge] link-project failed:",b.error??`HTTP ${m.statusCode}`)}catch{console.error("[bridge] link-project failed:",`HTTP ${m.statusCode}`)}g(m.statusCode??0)}})});f.on("error",m=>{p(m)}),f.write(u),f.end()});h===200?(console.log("[cli] link-project.server_success",{projectId:e}),console.log("[cli] link-project.success (dual-write)"),console.log(` workspace: ${r}`),console.log(` project: ${e}`),console.log(` daemon: ${s.slice(0,16)}\u2026`),console.log(` path: ${o}`),console.log("[cli] Next spawn for this project will use the linked path."),process.exit(0)):(console.warn("[cli] link-project.server_fail",{projectId:e,statusCode:h}),console.log("[cli] Local override still active \u2014 path will work on this machine"),process.exit(0))}Ee();function $a(r){return r.replace(/^wss?:/,e=>e==="wss:"?"https:":"http:").replace(/\/ws(\/.*)?$/,"")}async function Rs(){let r=ie(),e=$a(r.server),t=await fetch(`${e}/api/admin/cleanup-orphans`,{method:"POST",headers:{Authorization:`Bearer ${r.token}`,"Content-Type":"application/json"},body:"{}"});t.ok||(console.error(`[cli] cleanup-orphans: HTTP ${t.status}`),process.exit(1));let{deleted:n}=await t.json();console.log(`[cli] cleanup-orphans: deleted ${n} orphaned path ${n===1?"entry":"entries"}`),process.exit(0)}var de=new Pr;de.name("bridge-agent").description("Bridge local agent \u2014 connects your AI tools to Jerico").version("0.3.5");de.command("start").description("Start the bridge-agent daemon").action(()=>{ks()});de.command("auth").description("Authenticate with Bridge server").requiredOption("-s, --server <url>","Server URL (e.g., https://your-server.com)").option("-t, --token <token>","Use token non-interactively").option("--no-browser","Print auth URL without opening browser or interactive prompt").action(r=>{Is(r.server,!r.browser,r.token)});de.command("link-project <workspace-id> <project-id> <local-path>").description("Link a local directory to a project for this machine (Issue #152)").action((r,e,t)=>{Ns(r,e,t)});de.command("cleanup-orphans").description("Remove orphaned daemon_project_paths rows for this user").action(()=>{Rs()});de.command("status").description("Show connection status").action(async()=>{try{let{loadConfig:r}=await Promise.resolve().then(()=>(Ee(),as)),e=r();console.log("[bridge] Config found"),console.log(" Server:",e.server),console.log(" Name:",e.name)}catch{console.log("[bridge] Not authenticated. Run: bridge-agent auth")}});de.parse();
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bridge-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Bridge local agent — connects your AI tools to Jerico",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"bridge-agent": "dist/index.js"
|
|
8
|
+
"bridge-agent": "dist/index.js",
|
|
9
|
+
"bridge-mcp": "dist/bridge-mcp.cjs"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"dist/",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -41,3 +41,13 @@ for (const root of searchRoots) {
|
|
|
41
41
|
if (fixed === 0) {
|
|
42
42
|
console.log('[bridge-agent] postinstall: no node-pty spawn-helper found to fix')
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
// Ensure bridge-mcp binary is executable (npm usually handles `bin` entries, but
|
|
46
|
+
// pnpm stores and some preserve-symlinks installs miss it).
|
|
47
|
+
try {
|
|
48
|
+
const mcpBin = resolve(__dirname, '../dist/bridge-mcp.cjs')
|
|
49
|
+
chmodSync(mcpBin, 0o755)
|
|
50
|
+
console.log('[bridge-agent] postinstall: bridge-mcp bin chmod +x')
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.warn('[bridge-agent] postinstall: could not chmod bridge-mcp:', err.message)
|
|
53
|
+
}
|