@cozibud/harness 0.2.0 → 0.2.1

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
@@ -28,6 +28,12 @@ child context go to your signed-in Codex client and its model service.
28
28
  Your Codex login stays local. Pairing credentials are protected with your
29
29
  Windows account's DPAPI on Windows, or a user-only file on other systems.
30
30
  Disconnect the computer in Cozibud's Watch settings to revoke its access.
31
+ Each Cozibud account can pair two computers. The one connected longest answers
32
+ requests from the web app, the phone app and the watch; the other is a backup
33
+ that takes over when the first stops reporting in. Run `pair` again on a computer
34
+ to replace its pairing, or choose the household and account by signing in to the
35
+ one you want when you approve the link. Codex uses whichever account `codex login`
36
+ signed in (set `CODEX_HOME` to keep a separate Codex profile).
31
37
  Use `COZIBUD_RELAY_URL` to choose another HTTPS server before pairing; pairings
32
38
  are scoped to that origin. The relay requires no inbound computer port.
33
39
  The separate local HTTP adapter must remain on loopback.
package/dist/pairing.js CHANGED
@@ -73,6 +73,8 @@ export async function pairRelay(options) {
73
73
  interval += 5;
74
74
  continue;
75
75
  }
76
+ if (result.error?.code === "bridge_limit")
77
+ throw new Error("Your account already has two computers paired. Disconnect one in Cozibud under Settings → Assistants & API → Voice with a local agent, then run pair again.");
76
78
  throw new Error("Pairing was denied or expired. Run pair again.");
77
79
  }
78
80
  throw new Error("Pairing expired. Run pair again.");
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { mkdtemp, rm } from "node:fs/promises";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
- import { delimiter, isAbsolute, join } from "node:path";
5
+ import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
6
6
  import { StringDecoder } from "node:string_decoder";
7
7
  import { ensureCursorWorkspace } from "./cursor-workspace.js";
8
8
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -307,17 +307,46 @@ export function executable(configured) {
307
307
  for (const directory of directories) {
308
308
  const base = directory ? join(directory, configured) : configured;
309
309
  const script = /\.ps1$/i.test(base) ? base : `${base}.ps1`;
310
- if (existsSync(script))
311
- return {
312
- command: join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
313
- prefixArgs: ["-NoLogo", "-NoProfile", "-File", script],
314
- };
310
+ if (!existsSync(script))
311
+ continue;
312
+ const shimmed = nodeShimTarget(script);
313
+ if (shimmed)
314
+ return shimmed;
315
+ return {
316
+ command: join(process.env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"),
317
+ prefixArgs: ["-NoLogo", "-NoProfile", "-File", script],
318
+ };
315
319
  }
316
320
  }
317
321
  return /\.(?:c|m)?js$/i.test(configured)
318
322
  ? { command: process.execPath, prefixArgs: [configured] }
319
323
  : { command: configured, prefixArgs: [] };
320
324
  }
325
+ /**
326
+ * npm, pnpm and Yarn install a CLI on Windows as a PowerShell shim that runs a JavaScript entry point
327
+ * with Node. Running that entry point directly keeps arguments intact: `powershell -File` treats a bare
328
+ * `-` (read the prompt from stdin) as an unnamed parameter and fails before the CLI starts.
329
+ */
330
+ function nodeShimTarget(script) {
331
+ let source;
332
+ try {
333
+ source = readFileSync(script, "utf8");
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ if (source.length > 16_384)
339
+ return null;
340
+ const target = /"\$basedir[\\/]([^"$`]+\.[cm]?js)"/i.exec(source)?.[1];
341
+ if (!target)
342
+ return null;
343
+ const basedir = dirname(script);
344
+ const entry = resolve(basedir, target);
345
+ if (!existsSync(entry))
346
+ return null;
347
+ const bundledNode = join(basedir, "node.exe");
348
+ return { command: existsSync(bundledNode) ? bundledNode : process.execPath, prefixArgs: [entry] };
349
+ }
321
350
  const codexExecutable = executable(process.env.COZIBUD_CODEX_PATH || "codex");
322
351
  const claudeExecutable = executable(process.env.COZIBUD_CLAUDE_PATH || "claude");
323
352
  const geminiExecutable = executable(process.env.COZIBUD_GEMINI_PATH || "gemini");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cozibud/harness",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Cozibud local-client bridge and CLI.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",