@miraland-labs/conduit-bridge 0.11.12 → 0.12.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/README.md CHANGED
@@ -51,7 +51,7 @@ Optional local shortcuts after `init-ops` (same verbs):
51
51
 
52
52
  **Windows note:** `ops install` brings lanes online and prints a `runner --workspace …` command to keep open (no LaunchAgent). macOS/Linux install a background service.
53
53
 
54
- `ops.env` keys: `CONDUIT_URL`, `CONDUIT_ORG` (optional), `CONDUIT_WORKSPACE`, `CONDUIT_REPO` (optional), `CONDUIT_DRIVERS` (default `cursor`), `CONDUIT_ROLES` (default `implement research review`). Values expand `$HOME`, `%USERPROFILE%`, and `~`.
54
+ `ops.env` keys: `CONDUIT_URL`, `CONDUIT_ORG` (optional), `CONDUIT_WORKSPACE`, `CONDUIT_REPO` (optional), `CONDUIT_DRIVERS` (default: auto-detect installed agents), `CONDUIT_ROLES` (default `implement research review`). Values expand `$HOME`, `%USERPROFILE%`, and `~`.
55
55
 
56
56
  ## Advanced CLI
57
57
 
package/dist/cli.js CHANGED
@@ -359,7 +359,7 @@ async function initOps() {
359
359
  async function opsCommand() {
360
360
  const verb = process.argv[3];
361
361
  if (!verb || !OPS_VERBS.includes(verb)) {
362
- throw new Error(`Usage: ${bridgeUsage("ops", "<connect|install|switch|online|offline|status|doctor|disconnect|uninstall>", "[driver…]")}`);
362
+ throw new Error(`Usage: ${bridgeUsage("ops", "<connect|install|switch|online|offline|status|doctor|disconnect|uninstall>", "[driver…]")} (install also takes --workspace <path> [--repo <url>])`);
363
363
  }
364
364
  await runOps(verb, process.argv.slice(4));
365
365
  }
package/dist/ops.js CHANGED
@@ -9,6 +9,8 @@ import { dirname, join, resolve } from "node:path";
9
9
  import { parseArgs } from "node:util";
10
10
  import { ConduitClient } from "./client.js";
11
11
  import { loadConfig } from "./config.js";
12
+ import { detectInstalledClients } from "./detect.js";
13
+ import { driverIdsFromDetectedLabels } from "./drivers.js";
12
14
  import { describePreflightIssue, runBridgePreflight } from "./preflight.js";
13
15
  export const OPS_VERBS = [
14
16
  "connect", "install", "switch", "online", "offline", "status", "doctor", "disconnect", "uninstall",
@@ -73,7 +75,7 @@ export function loadOpsEnv(home = homedir(), cwd = process.cwd()) {
73
75
  CONDUIT_ORG: pick("CONDUIT_ORG", ""),
74
76
  CONDUIT_WORKSPACE: pick("CONDUIT_WORKSPACE", ""),
75
77
  CONDUIT_REPO: pick("CONDUIT_REPO", ""),
76
- CONDUIT_DRIVERS: pick("CONDUIT_DRIVERS", "cursor"),
78
+ CONDUIT_DRIVERS: pick("CONDUIT_DRIVERS", ""),
77
79
  CONDUIT_ROLES: pick("CONDUIT_ROLES", "implement research review"),
78
80
  loadedFrom,
79
81
  };
@@ -120,12 +122,21 @@ export function writeOpsEnvFile(path, patch, existingText) {
120
122
  }
121
123
  writeFileSync(path, `${next.filter((line, index) => !(index === next.length - 1 && line === "")).join("\n").replace(/\n*$/, "\n")}`, { mode: 0o600 });
122
124
  }
123
- export function resolveDrivers(env, argv) {
125
+ /** Explicit ids win; otherwise CONDUIT_DRIVERS; otherwise agents detected on PATH (fail when none). */
126
+ export async function resolveDrivers(env, argv, detect = detectInstalledClients) {
124
127
  const fromArgs = argv.map((item) => item.trim()).filter(Boolean);
125
- const drivers = fromArgs.length ? fromArgs : splitOpsList(env.CONDUIT_DRIVERS);
126
- if (!drivers.length)
127
- throw new Error("No drivers. Set CONDUIT_DRIVERS or pass ids.");
128
- return drivers;
128
+ if (fromArgs.length)
129
+ return fromArgs;
130
+ const fromEnv = splitOpsList(env.CONDUIT_DRIVERS);
131
+ if (fromEnv.length)
132
+ return fromEnv;
133
+ const detected = driverIdsFromDetectedLabels(await detect());
134
+ if (detected.length) {
135
+ console.log(`CONDUIT_DRIVERS not set — using detected agents: ${detected.join(", ")}`);
136
+ return detected;
137
+ }
138
+ throw new Error("No coding agent found on this computer. Install one (Claude Code, Codex, Cursor, OpenCode, Pi, Kiro, Antigravity), " +
139
+ "then retry — or set CONDUIT_DRIVERS / pass driver ids explicitly.");
129
140
  }
130
141
  /** Quote args for a copy-pasteable shell/cmd line (paths with spaces). */
131
142
  export function shellQuoteArgs(args) {
@@ -171,7 +182,7 @@ export async function runOps(verb, argv = [], deps = {}) {
171
182
  console.log(`URL: ${env.CONDUIT_URL || "(unset)"}`);
172
183
  console.log(`Workspace: ${env.CONDUIT_WORKSPACE || "(unset)"}`);
173
184
  console.log(`Repo: ${env.CONDUIT_REPO || "(none)"}`);
174
- console.log(`Drivers: ${env.CONDUIT_DRIVERS}`);
185
+ console.log(`Drivers: ${env.CONDUIT_DRIVERS || "(auto-detect installed agents)"}`);
175
186
  console.log(`Roles: ${env.CONDUIT_ROLES}`);
176
187
  console.log("");
177
188
  }
@@ -202,11 +213,45 @@ export async function runOps(verb, argv = [], deps = {}) {
202
213
  }
203
214
  // Lane toggles only need Bridge config + optional driver ids — not a full ops.env.
204
215
  if (verb === "online" || verb === "offline") {
205
- const drivers = resolveDrivers(env, argv);
216
+ const drivers = await resolveDrivers(env, argv);
206
217
  runBridge(["drivers", verb, ...drivers]);
207
218
  return;
208
219
  }
209
- requireOpsEnv(env);
220
+ // `ops install --workspace <path> [--repo <url>]` persists declared intent into ops.env so the
221
+ // Connect UI path works without hand-editing a config file first.
222
+ let installEnv = env;
223
+ let installArgv = argv;
224
+ if (verb === "install") {
225
+ const { values, positionals } = parseArgs({
226
+ args: argv,
227
+ options: {
228
+ workspace: { type: "string" },
229
+ repo: { type: "string" },
230
+ },
231
+ allowPositionals: true,
232
+ });
233
+ installArgv = positionals;
234
+ const declaredWorkspace = values.workspace?.trim();
235
+ const declaredRepo = values.repo?.trim();
236
+ if (declaredWorkspace || declaredRepo) {
237
+ const envPath = env.loadedFrom ?? defaultOpsEnvPath();
238
+ writeOpsEnvFile(envPath, {
239
+ ...(declaredWorkspace ? { CONDUIT_WORKSPACE: declaredWorkspace } : {}),
240
+ ...(declaredRepo ? { CONDUIT_REPO: declaredRepo } : {}),
241
+ });
242
+ console.log(`Saved ${[
243
+ declaredWorkspace ? `workspace ${declaredWorkspace}` : "",
244
+ declaredRepo ? `repo ${declaredRepo}` : "",
245
+ ].filter(Boolean).join(" and ")} to ${envPath}`);
246
+ installEnv = {
247
+ ...env,
248
+ ...(declaredWorkspace ? { CONDUIT_WORKSPACE: declaredWorkspace } : {}),
249
+ ...(declaredRepo ? { CONDUIT_REPO: declaredRepo } : {}),
250
+ loadedFrom: env.loadedFrom ?? envPath,
251
+ };
252
+ }
253
+ }
254
+ requireOpsEnv(verb === "install" ? installEnv : env);
210
255
  if (verb === "switch") {
211
256
  const { values } = parseArgs({
212
257
  args: argv,
@@ -278,10 +323,11 @@ export async function runOps(verb, argv = [], deps = {}) {
278
323
  return;
279
324
  }
280
325
  if (verb === "install") {
281
- if (!env.CONDUIT_WORKSPACE)
282
- throw new Error(`Set CONDUIT_WORKSPACE in ${defaultOpsEnvPath()}`);
283
- const workspace = resolve(expandOpsValue(env.CONDUIT_WORKSPACE));
284
- const drivers = resolveDrivers(env, argv);
326
+ if (!installEnv.CONDUIT_WORKSPACE) {
327
+ throw new Error(`No workspace declared. Rerun with --workspace /path/to/repo, or set CONDUIT_WORKSPACE in ${defaultOpsEnvPath()}`);
328
+ }
329
+ const workspace = resolve(expandOpsValue(installEnv.CONDUIT_WORKSPACE));
330
+ const drivers = await resolveDrivers(installEnv, installArgv);
285
331
  for (const id of drivers) {
286
332
  if (LOCAL_FUEL_DRIVERS.has(id))
287
333
  runBridge(["drivers", "fuel", id, "local"]);
@@ -291,8 +337,8 @@ export async function runOps(verb, argv = [], deps = {}) {
291
337
  runBridge(["ops", "doctor"]);
292
338
  if (host === "win32") {
293
339
  const runnerArgs = ["runner", "--workspace", workspace];
294
- if (env.CONDUIT_REPO)
295
- runnerArgs.push("--ensure-checkout", env.CONDUIT_REPO);
340
+ if (installEnv.CONDUIT_REPO)
341
+ runnerArgs.push("--ensure-checkout", installEnv.CONDUIT_REPO);
296
342
  console.log("Windows: background install-service is not available.");
297
343
  console.log("Keep a terminal open and run:");
298
344
  console.log(` npx @miraland-labs/conduit-bridge@latest ${shellQuoteArgs(runnerArgs)}`);
@@ -300,8 +346,8 @@ export async function runOps(verb, argv = [], deps = {}) {
300
346
  return;
301
347
  }
302
348
  const installArgs = ["install-service", "--workspace", workspace];
303
- if (env.CONDUIT_REPO)
304
- installArgs.push("--ensure-checkout", env.CONDUIT_REPO);
349
+ if (installEnv.CONDUIT_REPO)
350
+ installArgs.push("--ensure-checkout", installEnv.CONDUIT_REPO);
305
351
  console.log(`Installing runner for ${workspace} (drivers: ${drivers.join(", ")})`);
306
352
  runBridge(installArgs);
307
353
  console.log("Done. Check with: npx @miraland-labs/conduit-bridge@latest ops status");
package/ops/env.example CHANGED
@@ -11,6 +11,8 @@ CONDUIT_URL=https://api.conduit.miraland.io
11
11
  # CONDUIT_ORG=your-org-slug
12
12
  CONDUIT_WORKSPACE=$HOME/path/to/your-repo
13
13
  # CONDUIT_REPO=https://github.com/your-org/your-repo.git
14
- CONDUIT_DRIVERS=cursor
14
+ # Leave CONDUIT_DRIVERS unset to auto-detect the coding agents installed on this computer.
15
+ # Pin explicitly only when you want a subset, e.g.:
16
+ # CONDUIT_DRIVERS=claude-code codex
15
17
  # Product-standard roles — leave all three so Start/retry plans can match without reconnect.
16
18
  CONDUIT_ROLES=implement research review
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.11.12",
3
+ "version": "0.12.0",
4
4
  "description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity agents for a Conduit organization",
5
5
  "type": "module",
6
6
  "bin": {