@miraland-labs/conduit-bridge 0.16.19 → 0.16.21

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Local Bridge CLI for [Conduit](https://github.com/miralandlabs/conduit). Connects a computer to one organization, claims work, and drives a local agent.
4
4
 
5
- **Package version:** `0.16.19` — `ops.env` is enrollment/bootstrap defaults only: an explicit workspace does not inherit an old repository, and project switches do not rewrite global project state. `ops enroll` exchanges a single-use token, registers the machine, binds project affinity, provisions fuel keys, brings detected drivers online, and starts the runner in one command. On Linux, `ops install` refuses any effective systemd drop-in. A switch intent remains queued until the target runner heartbeat proves Bound. After every Bridge publish, operators must re-run `ops install` (LaunchAgent pins an absolute `cli.js`).
5
+ **Package version:** `0.16.21` — mutate_repo with `pr_create` opens a pull request even when `change_scope` is empty. `ops disconnect` now asks before it removes the runner service and clears credentials; pass `--yes` to skip the question. `ops.env` is enrollment/bootstrap defaults only: an explicit workspace does not inherit an old repository, and project switches do not rewrite global project state. `ops enroll` exchanges a single-use token, registers the machine, binds project affinity, provisions fuel keys, brings detected drivers online, and starts the runner in one command. On Linux, `ops install` refuses any effective systemd drop-in. A switch intent remains queued until the target runner heartbeat proves Bound. After every Bridge publish, operators must re-run `ops install` (LaunchAgent pins an absolute `cli.js`).
6
6
 
7
7
  ## Prerequisites
8
8
 
package/dist/cli.js CHANGED
@@ -371,7 +371,7 @@ async function initOps() {
371
371
  console.log(`Config file (separate): ${envPath}`);
372
372
  console.log("");
373
373
  console.log("Works the same on macOS, Linux, and Windows via:");
374
- console.log(` ${bridgeUsage("ops", "<connect|install|switch|online|offline|status|disconnect|uninstall>")}`);
374
+ console.log(` ${bridgeUsage("ops", "<connect|install|switch|online|offline|status|doctor|disconnect|uninstall>")}`);
375
375
  console.log("");
376
376
  console.log("1. Set enrollment/bootstrap defaults once:");
377
377
  console.log(` Create folder: ${configDir}`);
package/dist/driver.js CHANGED
@@ -131,7 +131,7 @@ export function buildAssignmentPrompt(context) {
131
131
  if (rework)
132
132
  lines.push("", `REWORK FEEDBACK — an independent review returned this delivery; address every point\n${rework}`);
133
133
  const mustCommit = context.grants.includes("repo_write") && context.grants.includes("branch_create");
134
- const mustOpenPr = mustCommit && context.grants.includes("pr_create") && Boolean(changeScope?.length);
134
+ const mustOpenPr = mustCommit && context.grants.includes("pr_create");
135
135
  const executionClass = context.executionClass
136
136
  ?? requireStampedExecutionClass(spec.execution_class);
137
137
  // Phase B: control-plane instruction is additive evidence/class prose.
@@ -29,8 +29,10 @@ export async function commitsAheadOfBase(workspace, base) {
29
29
  }
30
30
  }
31
31
  export function needsPullRequest(report, spec, grants) {
32
+ void spec;
33
+ // Empty change_scope is "whole repo", not "no land". Run 7 committed CANARY.md with pr_create
34
+ // granted and never opened a PR because this used to require a non-empty scope.
32
35
  return Boolean(grants.includes("pr_create")
33
- && (spec.change_scope?.length ?? 0) > 0
34
36
  && report.head_commit
35
37
  && !report.pull_request_url);
36
38
  }
@@ -181,7 +183,7 @@ export async function ensureDeliveryPullRequest(input) {
181
183
  const push = input.pushOriginHead ?? pushOriginHead;
182
184
  const countAhead = input.countCommitsAhead ?? commitsAheadOfBase;
183
185
  let report = adoptPullRequestUrlFromEvidence(input.report, input.repositoryFingerprint);
184
- if ((input.spec.change_scope?.length ?? 0) > 0) {
186
+ if (input.grants.includes("pr_create") || (input.spec.change_scope?.length ?? 0) > 0) {
185
187
  const head = await readHead(input.workspace);
186
188
  if (report.head_commit && !commitsMatch(report.head_commit, head)) {
187
189
  console.error(`Agent head_commit ${report.head_commit} does not match workspace HEAD ${head}; using workspace HEAD`);
package/dist/execution.js CHANGED
@@ -1764,8 +1764,8 @@ export function validateDeliveryReport(report, spec, grants = [], actualPaths) {
1764
1764
  throw new Error("Agent reported repository changes without the repo_write grant");
1765
1765
  }
1766
1766
  // Merge-on-accept lands GitHub PRs. Local attempt commits without a PR never reach main.
1767
+ // Empty change_scope means whole-repo, not "skip the PR" (Run 7).
1767
1768
  if (grants.includes("pr_create")
1768
- && (spec.change_scope?.length ?? 0) > 0
1769
1769
  && report.head_commit
1770
1770
  && !report.pull_request_url) {
1771
1771
  throw new Error("Repository changes require a pull_request_url when pr_create is granted");
package/dist/ops.js CHANGED
@@ -6,6 +6,8 @@ import { spawnSync } from "node:child_process";
6
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
+ import { stdin as input, stdout as output } from "node:process";
10
+ import { createInterface } from "node:readline/promises";
9
11
  import { parseArgs } from "node:util";
10
12
  import { ConduitClient } from "./client.js";
11
13
  import { loadConfig } from "./config.js";
@@ -51,7 +53,7 @@ export function expandOpsValue(value, home = homedir()) {
51
53
  }
52
54
  next = next.replace(/%USERPROFILE%/gi, home).replace(/%HOME%/gi, home);
53
55
  next = next.replace(/\$HOME\b/g, home).replace(/\$\{HOME\}/g, home);
54
- if (next.startsWith("~/") || next === "~")
56
+ if (next.startsWith("~/") || next.startsWith("~\\") || next === "~")
55
57
  next = join(home, next.slice(2));
56
58
  return next;
57
59
  }
@@ -67,7 +69,12 @@ export function parseOpsEnvFile(text, home = homedir()) {
67
69
  const key = line.slice(0, eq).trim();
68
70
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
69
71
  continue;
70
- out[key] = expandOpsValue(line.slice(eq + 1), home);
72
+ // Drop a trailing annotation such as `CONDUIT_WORKSPACE=$HOME/repo # laptop`, so it does not
73
+ // become part of the path. A quoted value keeps everything inside its quotes, and a `#` with no
74
+ // space before it stays in the value.
75
+ const rawValue = line.slice(eq + 1).trim();
76
+ const quoted = rawValue.startsWith('"') || rawValue.startsWith("'");
77
+ out[key] = expandOpsValue(quoted ? rawValue : rawValue.replace(/\s+#.*$/, ""), home);
71
78
  }
72
79
  return out;
73
80
  }
@@ -185,6 +192,20 @@ export function shellQuoteArgs(args) {
185
192
  return `"${arg.replaceAll('"', '\\"')}"`;
186
193
  }).join(" ");
187
194
  }
195
+ /** Ask the operator to approve a step that cannot be undone. Non-interactive shells must pass --yes. */
196
+ async function confirmOps(question) {
197
+ if (!input.isTTY || !output.isTTY) {
198
+ throw new Error("This step needs confirmation. Re-run with --yes to approve it without a prompt.");
199
+ }
200
+ const rl = createInterface({ input, output });
201
+ try {
202
+ const answer = (await rl.question(question)).trim().toLowerCase();
203
+ return answer === "y" || answer === "yes";
204
+ }
205
+ finally {
206
+ rl.close();
207
+ }
208
+ }
188
209
  function defaultRunBridge(args, options = {}) {
189
210
  // Re-enter this package's CLI in a child so ops stays a thin orchestrator.
190
211
  const cli = process.argv[1];
@@ -205,6 +226,13 @@ export async function runOps(verb, argv = [], deps = {}) {
205
226
  const runBridge = deps.runBridge ?? defaultRunBridge;
206
227
  const env = deps.env ?? loadOpsEnv();
207
228
  if (verb === "disconnect") {
229
+ // Ask here, not in the child command: this verb also removes the runner service, and the
230
+ // child's own prompt would come too late to stop that.
231
+ const approved = argv.includes("--yes") || argv.includes("-y") || await (deps.confirm ?? confirmOps)("Disengage this computer from the organization, remove the runner service, and clear local credentials? [y/N] ");
232
+ if (!approved) {
233
+ console.log("Cancelled.");
234
+ return;
235
+ }
208
236
  runBridge(["uninstall-service"], { allowFail: true });
209
237
  runBridge(["disconnect", "--yes"]);
210
238
  console.log("Disconnected. Run: npx @miraland-labs/conduit-bridge@latest ops connect");
@@ -4,5 +4,5 @@ if "%~1"=="" (
4
4
  echo Usage: %~nx0 ^<connect^|install^|online^|offline^|status^|doctor^|disconnect^|uninstall^> [args…]
5
5
  exit /b 1
6
6
  )
7
- npx --yes @miraland-labs/conduit-bridge@latest ops %*
7
+ call npx --yes @miraland-labs/conduit-bridge@latest ops %*
8
8
  if errorlevel 1 exit /b %errorlevel%
package/ops/connect.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use connect.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops connect %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops connect %*
4
4
  if errorlevel 1 exit /b %errorlevel%
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use disconnect.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops disconnect %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops disconnect %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/ops/doctor.cmd CHANGED
@@ -1,3 +1,3 @@
1
1
  @echo off
2
- npx --yes @miraland-labs/conduit-bridge@latest ops doctor %*
2
+ call npx --yes @miraland-labs/conduit-bridge@latest ops doctor %*
3
3
  if errorlevel 1 exit /b %errorlevel%
package/ops/install.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use install.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops install %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops install %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/ops/offline.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use offline.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops offline %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops offline %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/ops/online.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use online.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops online %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops online %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/ops/status.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use status.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops status %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops status %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/ops/uninstall.cmd CHANGED
@@ -1,4 +1,4 @@
1
1
  @echo off
2
2
  REM Windows launcher. macOS/Linux: use uninstall.sh
3
- npx --yes @miraland-labs/conduit-bridge@latest ops uninstall %*
3
+ call npx --yes @miraland-labs/conduit-bridge@latest ops uninstall %*
4
4
  if errorlevel 1 exit /b %errorlevel%
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.19",
3
+ "version": "0.16.21",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {