@byok-sdk/client 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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/adapters/claude/claude-adapter.d.ts +155 -0
  4. package/dist/adapters/claude/events.d.ts +91 -0
  5. package/dist/adapters/claude/permission-mapping.d.ts +142 -0
  6. package/dist/adapters/claude/process-client.d.ts +103 -0
  7. package/dist/adapters/claude/resolve-approval-mcp-bin.d.ts +45 -0
  8. package/dist/adapters/claude/resolve-bin.d.ts +27 -0
  9. package/dist/adapters/codex/codex-adapter.d.ts +97 -0
  10. package/dist/adapters/codex/events.d.ts +41 -0
  11. package/dist/adapters/codex/permission-mapping.d.ts +107 -0
  12. package/dist/adapters/codex/process-runner.d.ts +86 -0
  13. package/dist/adapters/codex/resolve-bin.d.ts +25 -0
  14. package/dist/adapters/pi/events.d.ts +58 -0
  15. package/dist/adapters/pi/permission-mapping.d.ts +46 -0
  16. package/dist/adapters/pi/pi-adapter.d.ts +29 -0
  17. package/dist/adapters/pi/resolve-bin.d.ts +44 -0
  18. package/dist/adapters/pi/rpc-client.d.ts +101 -0
  19. package/dist/bin/approval-mcp-server.d.ts +69 -0
  20. package/dist/bin/audit-log.d.ts +212 -0
  21. package/dist/bin/byok-agent.d.ts +2 -0
  22. package/dist/bin/byok-agent.js +12347 -0
  23. package/dist/bin/byok-agent.js.map +1 -0
  24. package/dist/bin/byok-approval-mcp.d.ts +2 -0
  25. package/dist/bin/byok-approval-mcp.js +498 -0
  26. package/dist/bin/byok-approval-mcp.js.map +1 -0
  27. package/dist/bin/commands/approvals.d.ts +31 -0
  28. package/dist/bin/commands/approve-reject.d.ts +29 -0
  29. package/dist/bin/commands/doctor.d.ts +15 -0
  30. package/dist/bin/commands/pair.d.ts +7 -0
  31. package/dist/bin/commands/runtimes.d.ts +7 -0
  32. package/dist/bin/commands/service.d.ts +54 -0
  33. package/dist/bin/commands/start.d.ts +21 -0
  34. package/dist/bin/commands/status.d.ts +25 -0
  35. package/dist/bin/commands/support-bundle.d.ts +7 -0
  36. package/dist/bin/commands/tasks.d.ts +27 -0
  37. package/dist/bin/commands/unpair.d.ts +124 -0
  38. package/dist/bin/commands/workspaces.d.ts +16 -0
  39. package/dist/bin/config.d.ts +41 -0
  40. package/dist/bin/control-client.d.ts +50 -0
  41. package/dist/bin/format.d.ts +67 -0
  42. package/dist/bin/runtime-probe.d.ts +34 -0
  43. package/dist/bin/tasks-view.d.ts +49 -0
  44. package/dist/daemon/approvals.d.ts +87 -0
  45. package/dist/daemon/auth-manager.d.ts +52 -0
  46. package/dist/daemon/blob-client.d.ts +22 -0
  47. package/dist/daemon/connection-manager.d.ts +502 -0
  48. package/dist/daemon/control-protocol.d.ts +299 -0
  49. package/dist/daemon/control-server.d.ts +61 -0
  50. package/dist/daemon/create-daemon.d.ts +364 -0
  51. package/dist/daemon/cursor-store.d.ts +30 -0
  52. package/dist/daemon/daemon-owner.d.ts +24 -0
  53. package/dist/daemon/deterministic-jitter.d.ts +20 -0
  54. package/dist/daemon/device-keys.d.ts +45 -0
  55. package/dist/daemon/device-proof-signer.d.ts +41 -0
  56. package/dist/daemon/environment.d.ts +96 -0
  57. package/dist/daemon/git-workspace-store.d.ts +49 -0
  58. package/dist/daemon/git-workspace.d.ts +80 -0
  59. package/dist/daemon/http-client.d.ts +8 -0
  60. package/dist/daemon/journal/journal.d.ts +342 -0
  61. package/dist/daemon/journal/sqlite-journal.d.ts +173 -0
  62. package/dist/daemon/journal/sqlite-support.d.ts +96 -0
  63. package/dist/daemon/journal/storage-policy.d.ts +368 -0
  64. package/dist/daemon/long-poll-transport.d.ts +142 -0
  65. package/dist/daemon/observer.d.ts +345 -0
  66. package/dist/daemon/operational-health.d.ts +87 -0
  67. package/dist/daemon/policy.d.ts +16 -0
  68. package/dist/daemon/progress-batcher.d.ts +31 -0
  69. package/dist/daemon/runtime-capabilities.d.ts +36 -0
  70. package/dist/daemon/session-workspace-store.d.ts +75 -0
  71. package/dist/daemon/store.d.ts +54 -0
  72. package/dist/daemon/task-runner.d.ts +862 -0
  73. package/dist/daemon/truth-memory-client.d.ts +94 -0
  74. package/dist/daemon/url.d.ts +60 -0
  75. package/dist/daemon/ws-transport.d.ts +110 -0
  76. package/dist/diagnostics/diagnostics.d.ts +114 -0
  77. package/dist/diagnostics/support-bundle.d.ts +81 -0
  78. package/dist/index.d.ts +42 -0
  79. package/dist/index.js +10056 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/lifecycle/create-service-lifecycle.d.ts +59 -0
  82. package/dist/lifecycle/exec-runner.d.ts +141 -0
  83. package/dist/lifecycle/launchd.d.ts +55 -0
  84. package/dist/lifecycle/service-types.d.ts +177 -0
  85. package/dist/lifecycle/systemd.d.ts +46 -0
  86. package/dist/lifecycle/winsw.d.ts +64 -0
  87. package/dist/types.d.ts +178 -0
  88. package/dist/util/async-queue.d.ts +45 -0
  89. package/dist/util/atomic-write.d.ts +86 -0
  90. package/dist/util/secure-dir.d.ts +124 -0
  91. package/package.json +66 -0
@@ -0,0 +1,31 @@
1
+ import { connectControlClient } from '../control-client';
2
+ export interface ApprovalsListDeps {
3
+ log?: (line: string) => void;
4
+ error?: (line: string) => void;
5
+ /** DI for tests: substitute the real control-socket connection attempt. */
6
+ connectControl?: typeof connectControlClient;
7
+ /** DI for tests: pin "now" for deterministic age rendering — see `formatApprovalsListLines`. */
8
+ now?: () => number;
9
+ }
10
+ /**
11
+ * Finding F4 (cross-model adversarial review): `byok-agent approvals` —
12
+ * before this command existed, an operator had no way to ever learn a
13
+ * pending approval's `approvalId` short of reading raw `audit.jsonl`
14
+ * entries by hand (and even that only worked if the daemon happened to
15
+ * surface one there at all): `approve`/`reject` both *require* an
16
+ * `approvalId`, but nothing in this CLI ever printed one.
17
+ *
18
+ * Lists the control socket's `approvals.list` result — the exact same
19
+ * `ApprovalRegistry` entries `approve`/`reject` resolve against
20
+ * (`create-daemon.ts`'s method registry, backed by `daemon/approvals.ts`) —
21
+ * as one line per pending approval: `approvalId`, `taskId`, `age`, and a
22
+ * (possibly truncated — see `formatApprovalsListLines`) summary excerpt.
23
+ *
24
+ * No persisted-state fallback, mirroring `approve`/`reject`
25
+ * (`commands/approve-reject.ts`): a pending approval only ever means
26
+ * anything against a LIVE daemon, so daemon-unreachable is reported as a
27
+ * clear, specific error (and this command exits non-zero) rather than a
28
+ * silent "no approvals" — those are different outcomes and a script
29
+ * parsing this output needs to be able to tell them apart.
30
+ */
31
+ export declare function runApprovalsCommand(storeDir: string, productId: string, deps?: ApprovalsListDeps): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { connectControlClient } from '../control-client';
2
+ export interface ApproveRejectDeps {
3
+ log?: (line: string) => void;
4
+ error?: (line: string) => void;
5
+ /** DI for tests: substitute the real control-socket connection attempt. */
6
+ connectControl?: typeof connectControlClient;
7
+ }
8
+ /**
9
+ * M4 Phase 2: `approve`/`reject` now genuinely work from a separate,
10
+ * short-lived CLI invocation — they call the control socket's
11
+ * `approvals.resolve` method (`create-daemon.ts`'s method registry, backed
12
+ * by `daemon/approvals.ts`'s `ApprovalRegistry`) rather than requiring a
13
+ * `Daemon` this same process just started (which never worked at all: see
14
+ * git history for the pre-M4 version of this module, which called
15
+ * `daemon.approve`/`daemon.reject` directly and always failed with
16
+ * "daemon is not started" for exactly that reason — `byok-agent.ts` used to
17
+ * not even dispatch to these commands as a result).
18
+ *
19
+ * Still honest about what these can resolve TODAY: no bundled runtime
20
+ * adapter (pi/claude/codex) raises an approval yet (see `create-daemon.ts`'s
21
+ * `toRuntimeInfoCapabilities` doc comment), so nothing ever calls
22
+ * `ApprovalRegistry.register()` in this SDK version — every `approve`/
23
+ * `reject` against a real daemon today resolves to the registry's own
24
+ * `not_found` error, UNLESS the daemon isn't reachable at all, in which case
25
+ * that's reported first. Both failure modes are reported with a clear,
26
+ * specific message rather than a generic "not started" one.
27
+ */
28
+ export declare function runApproveCommand(storeDir: string, productId: string, approvalId: string, deps?: ApproveRejectDeps): Promise<void>;
29
+ export declare function runRejectCommand(storeDir: string, productId: string, approvalId: string, reason: string | undefined, deps?: ApproveRejectDeps): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import type { DaemonConfig } from '../../index';
2
+ import { type CollectDiagnosticsOptions } from '../../diagnostics/diagnostics';
3
+ export interface DoctorOptions extends CollectDiagnosticsOptions {
4
+ json?: boolean;
5
+ fix?: boolean;
6
+ confirmed?: boolean;
7
+ log?: (line: string) => void;
8
+ }
9
+ export declare class DoctorConfirmationRequiredError extends Error {
10
+ constructor();
11
+ }
12
+ export declare class DoctorDaemonRunningError extends Error {
13
+ constructor();
14
+ }
15
+ export declare function runDoctorCommand(config: DaemonConfig, options?: DoctorOptions): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import { type Daemon, type DaemonConfig } from '../../index';
2
+ export interface PairDeps {
3
+ log?: (line: string) => void;
4
+ /** DI for tests: skip constructing a real `createDaemon(config)` and drive a stub/pre-built instance instead. */
5
+ daemon?: Pick<Daemon, 'pair'>;
6
+ }
7
+ export declare function runPairCommand(config: DaemonConfig, code: string, deps?: PairDeps): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import { type DaemonConfig, type RuntimeAdapter } from '../../index';
2
+ export interface RuntimesDeps {
3
+ log?: (line: string) => void;
4
+ /** DI for tests: probe these adapters instead of constructing the real bundled pi/claude/codex set. */
5
+ adapters?: RuntimeAdapter[];
6
+ }
7
+ export declare function runRuntimesCommand(config: DaemonConfig, deps?: RuntimesDeps): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { type DaemonConfig, type ServiceDefinition, type ServiceLifecycle } from '../../index';
2
+ export interface ServiceCommandDeps {
3
+ log?: (line: string) => void;
4
+ /** DI for tests: drive a pre-built lifecycle instead of the real platform-dispatched `createServiceLifecycle`. */
5
+ lifecycle?: ServiceLifecycle;
6
+ }
7
+ /**
8
+ * Builds the SAME `ServiceDefinition` every one of `install`/`uninstall`/
9
+ * `service-start`/`service-stop`/`service-status` derives — critical so
10
+ * `uninstall`/`service-stop`/`service-status` compute the exact same
11
+ * service name (and, on Windows, install directory) `install` used,
12
+ * without requiring the operator to re-supply identical ad hoc flags to
13
+ * five separate invocations. `--name`/`--agent-bin`/`--node-bin`/
14
+ * `--winsw-bin`/`--winsw-install-dir` are override escape hatches; every
15
+ * default is derived deterministically from `config` alone (mirrors
16
+ * `bin/config.ts`'s own doc comment: every subcommand loads config the same
17
+ * way).
18
+ *
19
+ * `agentBin` defaults to `process.argv[1]` — the actual script path Node is
20
+ * currently running, always correct regardless of how this CLI was
21
+ * invoked (global install, `npx`, a product's own `node_modules/.bin`
22
+ * symlink) — NOT an `import.meta`-based guess; see
23
+ * `lifecycle/create-service-lifecycle.ts`'s doc comment for why this
24
+ * module family never tries to resolve that path itself. A product
25
+ * shipping a single compiled binary (`templates/packaging/`) instead of
26
+ * `node + byok-agent.js` should pass `--agent-bin`/`--node-bin` explicitly,
27
+ * or construct a `ServiceDefinition` directly via `createServiceLifecycle`
28
+ * rather than this convenience CLI subcommand.
29
+ *
30
+ * `configPath` is resolved to an ABSOLUTE path before being baked into the
31
+ * generated service's `--config <path>` argument: a service launched by
32
+ * launchd/systemd/WinSW starts with the OS service manager's OWN minimal
33
+ * environment and working directory, not the operator's interactive shell
34
+ * — a relative path here would resolve against whatever cwd the service
35
+ * manager happens to choose, not where the operator ran `install` from.
36
+ */
37
+ export declare function buildServiceDefinition(config: DaemonConfig, configPath: string, rest: string[]): ServiceDefinition;
38
+ /**
39
+ * Generates the platform service definition (plist/unit/WinSW xml),
40
+ * registers it with the OS service manager, and starts it — see
41
+ * `lifecycle/create-service-lifecycle.ts` for the per-platform mechanics.
42
+ * On Windows, `--winsw-bin <path>` is required (the product-bundled WinSW
43
+ * executable — Decision-6: this SDK never bundles/downloads it itself; see
44
+ * `templates/service/winsw/README.md`).
45
+ */
46
+ export declare function runInstallCommand(config: DaemonConfig, configPath: string, rest: string[], deps?: ServiceCommandDeps): Promise<void>;
47
+ /** Stops (if running) and fully removes the service registration + generated definition file. Safe to call when not installed. */
48
+ export declare function runUninstallCommand(config: DaemonConfig, configPath: string, rest: string[], deps?: ServiceCommandDeps): Promise<void>;
49
+ /** Starts an already-installed service (named so it never collides with the existing `start`, which runs the daemon in the foreground — see `byok-agent.ts`'s header comment). */
50
+ export declare function runServiceStartCommand(config: DaemonConfig, configPath: string, rest: string[], deps?: ServiceCommandDeps): Promise<void>;
51
+ /** Stops a running service without uninstalling it. */
52
+ export declare function runServiceStopCommand(config: DaemonConfig, configPath: string, rest: string[], deps?: ServiceCommandDeps): Promise<void>;
53
+ /** Reports installed/running state straight from the platform's own service manager. */
54
+ export declare function runServiceStatusCommand(config: DaemonConfig, configPath: string, rest: string[], deps?: ServiceCommandDeps): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { type Daemon, type DaemonConfig } from '../../index';
2
+ export interface StartDeps {
3
+ log?: (line: string) => void;
4
+ error?: (line: string) => void;
5
+ /** DI for tests: drive a pre-built (e.g. stub-adapter-backed) `Daemon` instead of the real `createDaemon(config)`. */
6
+ daemon?: Daemon;
7
+ /** Test hook: fires once `daemon.start()` has resolved and the banner line has been logged — lets a test abort deterministically instead of racing real timing. Unused by the real CLI entry. */
8
+ onReady?: () => void;
9
+ /** Required: there is no sane default "never stop" signal. The real CLI entry (`byok-agent.ts`) wires this to SIGINT/SIGTERM; tests wire their own `AbortController`. */
10
+ signal: AbortSignal;
11
+ }
12
+ /**
13
+ * Runs the daemon in the foreground until `deps.signal` aborts. Every
14
+ * `DaemonEvent` it observes is (a) appended to `<storeDir>/audit.jsonl` —
15
+ * the only channel a separate `status`/`tasks`/`runtimes` invocation has
16
+ * into this process' state, see `byok-agent.ts`'s header comment — and (b)
17
+ * printed as one line to stdout via the SAME formatter `tasks --follow`
18
+ * uses when tailing that log, so watching `start`'s own stdout and tailing
19
+ * the audit log show identical text.
20
+ */
21
+ export declare function runStartCommand(config: DaemonConfig, deps: StartDeps): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { type DaemonConfig, type RuntimeAdapter } from '../../index';
2
+ import { connectControlClient } from '../control-client';
3
+ export interface StatusDeps {
4
+ log?: (line: string) => void;
5
+ /** DI for tests: probe these adapters instead of constructing the real bundled pi/claude/codex set. */
6
+ adapters?: RuntimeAdapter[];
7
+ /** DI for tests: substitute the real control-socket connection attempt. */
8
+ connectControl?: typeof connectControlClient;
9
+ }
10
+ /**
11
+ * `paired`/`deviceId` are read straight off `DeviceStore` on disk (NOT via
12
+ * a freshly-constructed `Daemon.status()`) — a `Daemon` this process just
13
+ * constructed has never called `start()`/`pair()`, so its in-memory
14
+ * `AuthManager` never loaded the on-disk record and would always report
15
+ * `paired: false` even for a device that's genuinely paired. See
16
+ * `byok-agent.ts`'s header comment for the full read-model rationale.
17
+ *
18
+ * M4 Phase 2: after the persisted-state view above, this ALSO tries the
19
+ * control socket (see `control-client.ts`) — if a `byok-agent start` (or
20
+ * the installed service) is actually running right now, its live pid/
21
+ * uptime/transport/active-tasks are appended, clearly marked `live-`; if
22
+ * not reachable, one line says so and the persisted view above stands on
23
+ * its own, exactly as before this feature existed.
24
+ */
25
+ export declare function runStatusCommand(config: DaemonConfig, deps?: StatusDeps): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import type { DaemonConfig } from '../../index';
2
+ import type { CollectDiagnosticsOptions } from '../../diagnostics/diagnostics';
3
+ export interface SupportBundleOptions extends CollectDiagnosticsOptions {
4
+ outputPath: string;
5
+ log?: (line: string) => void;
6
+ }
7
+ export declare function runSupportBundleCommand(config: DaemonConfig, options: SupportBundleOptions): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { type DaemonConfig } from '../../index';
2
+ import { connectControlClient } from '../control-client';
3
+ export interface TasksListDeps {
4
+ log?: (line: string) => void;
5
+ }
6
+ /** `byok-agent tasks` (no `--follow`): current known tasks, reconstructed from the audit log — see `byok-agent.ts`'s header comment. */
7
+ export declare function runTasksListCommand(config: DaemonConfig, deps?: TasksListDeps): Promise<void>;
8
+ export interface TasksFollowDeps {
9
+ log?: (line: string) => void;
10
+ /** Required — see `commands/start.ts`'s identical `StartDeps.signal` doc comment for why there's no sane default. */
11
+ signal: AbortSignal;
12
+ pollIntervalMs?: number;
13
+ /** DI for tests: substitute the real control-socket connection attempt. */
14
+ connectControl?: typeof connectControlClient;
15
+ }
16
+ /**
17
+ * `byok-agent tasks --follow`: M4 Phase 2 — prefers a LIVE stream over the
18
+ * control socket's `tasks.subscribe` method (see `control-client.ts`),
19
+ * which taps the exact same `DaemonObserver` events `start`'s own stdout
20
+ * already shows, full fidelity, as they happen. Falls back to tailing the
21
+ * SAME `audit.jsonl` a running `start` appends to (like `tail -f`, not a
22
+ * full replay — run plain `byok-agent tasks` first for history) whenever
23
+ * the control socket isn't reachable (daemon not running, or an older
24
+ * daemon build with no control socket at all) — see `byok-agent.ts`'s
25
+ * header comment for the historical rationale this fallback preserves.
26
+ */
27
+ export declare function runTasksFollowCommand(config: DaemonConfig, deps: TasksFollowDeps): Promise<void>;
@@ -0,0 +1,124 @@
1
+ import type { Daemon, ServiceLifecycle } from '../../index';
2
+ import { connectControlClient, isControlDaemonGone } from '../control-client';
3
+ /** Thrown when `unpair` didn't get a `--yes`/confirmed and either declined interactively or has no TTY to prompt on. Caught by `byok-agent.ts`'s top-level handler like any other error (clean message, exit 1) — never left to hang. */
4
+ export declare class UnpairNotConfirmedError extends Error {
5
+ constructor();
6
+ }
7
+ /**
8
+ * Finding P1 #2: thrown instead of clearing the store when the OS
9
+ * background service (`byok-agent install`) is currently installed AND
10
+ * running — see this file's own module doc comment for the full "why".
11
+ * Unconditional: unlike {@link UnpairUnknownDaemonStateError}, `--force`
12
+ * never bypasses this one — it exists for a KNOWN-unsafe state, not an
13
+ * uncertain one.
14
+ */
15
+ export declare class UnpairBlockedByRunningServiceError extends Error {
16
+ constructor(detail: string);
17
+ }
18
+ /**
19
+ * Finding P1 #2 (residual, now fixed across two rounds): thrown instead of
20
+ * clearing the store when unpair could NOT positively confirm that no
21
+ * background service/daemon is running. Three shapes all collapse to
22
+ * "unknown", unsafe BY DEFAULT — exactly like a confirmed-running service —
23
+ * unless the caller opts in via `--force`:
24
+ *
25
+ * - Round 1: a `deps.lifecycle` that could not even be constructed
26
+ * (unsupported platform, win32 without `--winsw-bin`, or no config to
27
+ * build one from at all).
28
+ * - Round 1: a `status()` call that itself rejected (the query to the
29
+ * platform's service manager failed outright).
30
+ * - Round 3: a `status()` call that RESOLVED but reported
31
+ * `determinate: false` — the manager query itself could not be answered
32
+ * (no reachable systemd `--user` D-Bus session, no launchd GUI domain for
33
+ * this uid, permission denied), so `running: false` in that response is a
34
+ * fallback, not a confirmed fact; see `lifecycle/service-types.ts`'s
35
+ * `ServiceStatusResult.determinate`. Every one of `systemd.ts`/
36
+ * `launchd.ts`/`winsw.ts` used to collapse exactly this case into a plain
37
+ * `running: false`, which this file's `checkServiceState` then trusted at
38
+ * face value — reopening the identical fail-open gap round 1 fixed for
39
+ * the other two shapes.
40
+ *
41
+ * Bypassable by `--force`, deliberately unlike
42
+ * {@link UnpairBlockedByRunningServiceError}: this error means "can't
43
+ * tell", not "confirmed unsafe".
44
+ */
45
+ export declare class UnpairUnknownDaemonStateError extends Error {
46
+ constructor(reason: string);
47
+ }
48
+ /**
49
+ * Finding F6 (cross-model adversarial review): thrown instead of clearing
50
+ * the store on the LIVE (control-socket) unpair path when the daemon's
51
+ * actual exit could NOT be confirmed — the shutdown RPC request itself
52
+ * failed/never got through, teardown hung past `waitForControlExit`'s
53
+ * deadline, or the exit-poll otherwise never observed `isControlDaemonGone`
54
+ * before timing out. All three collapse to the same observable signal
55
+ * (`exited === false`): a daemon that never actually received/finished the
56
+ * shutdown is presumably STILL RUNNING, with its `AuthManager`'s proactive
57
+ * renewal timer ready to write a fresh `device.json` right back over
58
+ * whatever `unpair()` is about to clear — clearing anyway (the pre-fix
59
+ * behavior) meant the log line had to openly admit "did not confirm exit...
60
+ * local device identity has still been cleared" as if that were routine,
61
+ * when it is exactly the silently-self-reverting hazard finding P1 #2
62
+ * originally set out to close. Bypassable via `--force`, same shape as
63
+ * {@link UnpairUnknownDaemonStateError} — this means "can't confirm it
64
+ * actually stopped", an explicit, logged-as-unsafe override, never a
65
+ * silent default.
66
+ */
67
+ export declare class UnpairExitUnconfirmedError extends Error {
68
+ constructor();
69
+ }
70
+ export interface UnpairDeps {
71
+ log?: (line: string) => void;
72
+ /** `--yes`: skip the confirmation prompt entirely. */
73
+ confirmed?: boolean;
74
+ /** Defaults to `process.stdin.isTTY` — overridable so a test never depends on the real test-runner's stdin. */
75
+ isTTY?: boolean;
76
+ input?: NodeJS.ReadableStream;
77
+ output?: NodeJS.WritableStream;
78
+ /**
79
+ * Finding P1 #2: an already-constructed lifecycle for the OS background
80
+ * service this product MIGHT be installed as — `byok-agent.ts`'s `unpair`
81
+ * dispatch builds this the same way `bin/commands/service.ts` does
82
+ * (`buildServiceDefinition` + `createServiceLifecycle`) and passes it
83
+ * through. `undefined` means "could not even check" (unsupported
84
+ * platform, or Windows without `--winsw-bin`) — this is NOT the same as
85
+ * "confirmed not running": see {@link UnpairUnknownDaemonStateError} and
86
+ * the module doc comment for what that residual gap means and why it's
87
+ * refused by default (bypassable only via `--force`).
88
+ */
89
+ lifecycle?: Pick<ServiceLifecycle, 'status'>;
90
+ /**
91
+ * Finding P1 #2 (residual): explicit opt-in to proceed when the service
92
+ * check could NOT positively confirm "not running" (lifecycle
93
+ * unavailable, or its `status()` call itself failed) — see
94
+ * {@link UnpairUnknownDaemonStateError}. Does NOT bypass a lifecycle that
95
+ * actively confirmed the service IS running
96
+ * ({@link UnpairBlockedByRunningServiceError} is unconditional either
97
+ * way) — `--force` overrides uncertainty, never a known-unsafe result.
98
+ *
99
+ * Finding F6: this SAME flag also overrides the LIVE (control-socket)
100
+ * path's own exit-confirmation gate — see
101
+ * {@link UnpairExitUnconfirmedError}. One flag, two independent unsafe
102
+ * overrides (whichever path this invocation actually took), each logged
103
+ * as an explicit WARNING rather than silently accepted.
104
+ */
105
+ force?: boolean;
106
+ /**
107
+ * M4 Phase 2: `storeDir`/`productId` to attempt a LIVE control-socket
108
+ * unpair before ever falling back to the service-state-based flow below
109
+ * — see this file's own module doc comment. Omitting either (e.g. an
110
+ * older test exercising only the fallback path) skips straight to that
111
+ * flow, exactly as before this feature existed.
112
+ */
113
+ storeDir?: string;
114
+ productId?: string;
115
+ /** DI for tests: substitute the real control-socket connection attempt. */
116
+ connectControl?: typeof connectControlClient;
117
+ /** DI for tests: substitute the real "has the daemon actually exited" probe. */
118
+ isControlDaemonGone?: typeof isControlDaemonGone;
119
+ /** How long to wait for the daemon to actually exit after `shutdown` is sent, before giving up (the store is still cleared either way — see the live-path log message). Default 15000ms. */
120
+ controlExitTimeoutMs?: number;
121
+ /** Poll interval while waiting for exit. Default 300ms. */
122
+ controlExitPollIntervalMs?: number;
123
+ }
124
+ export declare function runUnpairCommand(daemon: Pick<Daemon, 'unpair'>, deps?: UnpairDeps): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import type { DaemonConfig } from '../../index';
2
+ import { GitWorkspaceStore, type GitWorkspaceLedgerRecord } from '../../daemon/git-workspace-store';
3
+ export interface WorkspacesCommandDeps {
4
+ log?: (line: string) => void;
5
+ store?: Pick<GitWorkspaceStore, 'list'>;
6
+ showPaths?: boolean;
7
+ }
8
+ declare function abbreviateCommit(value: string | undefined): string;
9
+ declare function formatWorkspaceLine(record: GitWorkspaceLedgerRecord, showPaths: boolean): string;
10
+ /**
11
+ * Lists the private Git workspace ledger without probing or changing any
12
+ * workspace. The ledger store already defines missing, corrupt, and future
13
+ * version behavior; this command deliberately preserves those outcomes.
14
+ */
15
+ export declare function runWorkspacesCommand(config: DaemonConfig, deps?: WorkspacesCommandDeps): Promise<void>;
16
+ export { abbreviateCommit, formatWorkspaceLine };
@@ -0,0 +1,41 @@
1
+ import type { DaemonConfig } from '../daemon/create-daemon';
2
+ /**
3
+ * Thrown by {@link loadConfig} on a missing/unreadable/invalid config.
4
+ * Never calls `process.exit` itself (unlike the pre-M3-2b bin, which did) —
5
+ * every caller, including tests, controls its own exit behavior; only
6
+ * `byok-agent.ts`'s top-level `main().catch(...)` turns this into a clean
7
+ * stderr message + `exit(1)`.
8
+ */
9
+ export declare class ConfigError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * Loads a `DaemonConfig` from a JSON file, if given, merged with explicit
14
+ * overrides (e.g. `pair`'s `--server`). Every subcommand in this bin —
15
+ * including the read-only ones (`status`/`runtimes`/`tasks`) — loads
16
+ * config the same way: they all need `productId`/`storeDir`/`branding`/
17
+ * `runtimeAllowlist` from the same one product install, so there is only
18
+ * ever one config shape to reason about.
19
+ */
20
+ export declare function loadConfig(configPath: string | undefined, overrides?: Partial<DaemonConfig>): DaemonConfig;
21
+ /**
22
+ * Same `storeDir` resolution `create-daemon.ts` uses internally
23
+ * (`config.storeDir ?? DeviceStore.defaultDir(config.productId)`) — reused
24
+ * here via `DeviceStore`'s own static helper (not reimplemented) so a query
25
+ * subcommand always looks in the exact same place `createDaemon`/
26
+ * `createDaemonWithAdapters` would persist to.
27
+ */
28
+ export declare function resolveStoreDir(config: Pick<DaemonConfig, 'storeDir' | 'productId'>): string;
29
+ /** Value following `flag` in `args` (e.g. `--config <path>`), or `undefined`. */
30
+ export declare function argValue(args: string[], flag: string): string | undefined;
31
+ /** Whether a bare boolean flag (e.g. `--follow`, `--yes`) is present anywhere in `args`. */
32
+ export declare function hasFlag(args: string[], flag: string): boolean;
33
+ /**
34
+ * Positional (non-flag) arguments, in order, with a fixed set of known
35
+ * `--flag <value>` pairs removed (flag AND its value). Bare boolean flags
36
+ * (e.g. `--follow`/`--yes`) are handled separately via {@link hasFlag} and
37
+ * don't need to be named here unless they'd otherwise be mistaken for a
38
+ * positional. Order-independent: `pair CODE --server URL` and
39
+ * `pair --server URL CODE` both yield `["CODE"]`.
40
+ */
41
+ export declare function positionalArgs(args: string[], valueFlags?: string[]): string[];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * M4 Phase 2: the CLI-side half of the control socket — connects, performs
3
+ * the mutual HMAC handshake (`../daemon/control-protocol.ts`), and exposes a
4
+ * small `request()`/`subscribe()` surface every rewired command
5
+ * (`status`/`tasks --follow`/`unpair`/`approve`/`reject`) builds on.
6
+ *
7
+ * `connectControlClient` never throws for the ordinary "daemon isn't
8
+ * running" case — it returns a typed `{ok:false, reason}` result instead
9
+ * (see {@link ConnectControlResult}), so a caller can render a clean
10
+ * fallback message rather than catching an exception. Missing
11
+ * `control.token` (the daemon was never started, or was stopped) is by far
12
+ * the most common reason; any other connect/handshake failure collapses
13
+ * into the same shape — a CLI user doesn't need to know WHY the control
14
+ * socket isn't reachable, only that it isn't and a fallback is being used.
15
+ */
16
+ export interface ControlClientOptions {
17
+ storeDir: string;
18
+ productId: string;
19
+ /** Default: `HANDSHAKE_TIMEOUT_MS` (3000ms) — matches the server's own handshake timeout. */
20
+ handshakeTimeoutMs?: number;
21
+ /** Default 10000ms — applied per `request()` call; never applied to `subscribe()`, which is expected to stay open indefinitely. */
22
+ requestTimeoutMs?: number;
23
+ }
24
+ export interface ControlClient {
25
+ /** Sends `{method, params}`, resolves with the server's `result`, or rejects with a {@link ControlError} (or a plain `Error` for a connection-level failure/timeout). */
26
+ request<T = unknown>(method: string, params?: unknown): Promise<T>;
27
+ /** Sends a streaming request; `onEvent` fires for each `event` frame. Returns a handle whose `close()` ends the WHOLE connection (per the protocol: "client may just close the connection to unsubscribe") — don't share a client between a `subscribe()` and other concurrent `request()` calls if you need them to outlive each other. */
28
+ subscribe(method: string, params: unknown, onEvent: (event: unknown) => void): {
29
+ close: () => void;
30
+ };
31
+ /** Closes the underlying connection. Safe to call more than once. */
32
+ close(): void;
33
+ }
34
+ export type ConnectControlResult = {
35
+ ok: true;
36
+ client: ControlClient;
37
+ } | {
38
+ ok: false;
39
+ reason: string;
40
+ };
41
+ /** Reads the control token, connects, and performs the handshake — see the module doc comment for why failures here collapse into `{ok:false, reason}` rather than throwing. */
42
+ export declare function connectControlClient(opts: ControlClientOptions): Promise<ConnectControlResult>;
43
+ /**
44
+ * Unpair's own poll for "has the daemon actually exited yet" (see
45
+ * `bin/commands/unpair.ts`): both the control token file being gone AND a
46
+ * fresh connect attempt being refused, checked directly rather than through
47
+ * a full `connectControlClient` handshake — cheaper per poll, and matches
48
+ * the exact two-condition check the M4 design calls for.
49
+ */
50
+ export declare function isControlDaemonGone(storeDir: string, productId: string): Promise<boolean>;
@@ -0,0 +1,67 @@
1
+ import type { AgentEvent } from '@byok-sdk/protocol';
2
+ import type { ConnectionState, DaemonBranding, DaemonEvent, DaemonTaskInfo } from '../index';
3
+ import type { ControlStatusResult, PendingApproval } from '../daemon/control-protocol';
4
+ import type { ProbedRuntime } from './runtime-probe';
5
+ import type { TaskCounts, DerivedTaskInfo } from './tasks-view';
6
+ /** Renders one `task.progress`-derived `AgentEvent` compactly — the inner payload of a `progress`-kind `DaemonEvent`. */
7
+ export declare function formatAgentEvent(event: AgentEvent): string;
8
+ export interface FormatDaemonEventLineOptions {
9
+ /**
10
+ * Finding F8 (cross-model adversarial review): redact an
11
+ * `awaiting-approval` event's `summary` to a `[redacted: N bytes]`
12
+ * placeholder instead of the raw text. `summary` can carry the exact
13
+ * tool-call text (a shell command, a file's contents) a `confirm`-mode
14
+ * policy is gating — `start.ts`'s stdout is captured verbatim by
15
+ * launchd/systemd/WinSW service logs, which have no business holding
16
+ * that. Default `false` (full fidelity) — every OTHER caller
17
+ * (`tasks --follow`'s control-socket path, which is authenticated;
18
+ * and its audit-log-tailing fallback, whose events are ALREADY redacted
19
+ * at the source by `audit-log.ts` regardless of this flag) keeps showing
20
+ * whatever it already legitimately can. Only `bin/commands/start.ts`
21
+ * sets this to `true`.
22
+ */
23
+ redactApprovalSummary?: boolean;
24
+ }
25
+ /**
26
+ * One line per `DaemonEvent`. Shared by `start` (printed to stdout live
27
+ * while it runs) and `tasks --follow` (tailing the identical shape back out
28
+ * of the audit log) so the two ways of watching the same feed read
29
+ * identically.
30
+ */
31
+ export declare function formatDaemonEventLine(event: DaemonEvent, options?: FormatDaemonEventLineOptions): string;
32
+ export declare function formatTaskLine(task: DaemonTaskInfo | DerivedTaskInfo): string;
33
+ export declare function formatTaskListLines(tasks: readonly (DaemonTaskInfo | DerivedTaskInfo)[]): string[];
34
+ export declare function formatRuntimeLines(runtimes: readonly ProbedRuntime[]): string[];
35
+ export interface StatusView {
36
+ productName: string;
37
+ productId: string;
38
+ branding?: DaemonBranding;
39
+ paired: boolean;
40
+ deviceId?: string;
41
+ connection?: {
42
+ state: ConnectionState;
43
+ ts: string;
44
+ };
45
+ runtimes: readonly ProbedRuntime[];
46
+ taskCounts: TaskCounts;
47
+ auditLogPath: string;
48
+ auditLogLineCount: number;
49
+ }
50
+ export declare function formatStatusLines(view: StatusView): string[];
51
+ /**
52
+ * M4 Phase 2: renders the control socket's live `status` result — appended
53
+ * after {@link formatStatusLines}' persisted-state view when a running
54
+ * daemon is actually reachable (see `bin/commands/status.ts`). Every line
55
+ * is prefixed `live-` (or is the single `live:` summary line) so it's
56
+ * unambiguous which lines are a live snapshot of a running process versus
57
+ * the historical/persisted view above them.
58
+ */
59
+ export declare function formatLiveStatusLines(live: ControlStatusResult): string[];
60
+ /**
61
+ * Finding F4: renders `approvals.list`'s registry entries for the new
62
+ * `byok-agent approvals` command (`bin/commands/approvals.ts`) — columns
63
+ * approvalId, taskId, age, summary excerpt, per that finding's own spec.
64
+ * `nowMs` is injected (rather than read via `Date.now()` here) so tests get
65
+ * deterministic age rendering; the real caller passes `Date.now()`.
66
+ */
67
+ export declare function formatApprovalsListLines(approvals: readonly PendingApproval[], nowMs: number): string[];
@@ -0,0 +1,34 @@
1
+ import { type RuntimeAdapter } from '../index';
2
+ export declare const RUNTIME_PROBE_TIMEOUT_MS = 5000;
3
+ /** The bundled adapter set `byok-agent status`/`byok-agent runtimes` probe by default — same unset-vs-set allowlist contract as `createDaemon` itself. */
4
+ export declare function defaultRuntimeAdapters(runtimeAllowlist: string[] | undefined): RuntimeAdapter[];
5
+ /**
6
+ * What `byok-agent status`/`byok-agent runtimes` show per runtime — a
7
+ * flattened, display-ready merge of `RuntimeDetectResult` and
8
+ * `RuntimeCapabilities` (see `../types.ts`). Always probed fresh (this
9
+ * module never reads from the daemon or the audit log) — see
10
+ * `byok-agent.ts`'s header comment for why a live, standalone probe is the
11
+ * honest choice for "what's on this machine right now" instead of a
12
+ * historical snapshot.
13
+ */
14
+ export interface ProbedRuntime {
15
+ id: string;
16
+ present: boolean;
17
+ version?: string;
18
+ authPresent?: boolean;
19
+ steer: boolean;
20
+ resume: boolean;
21
+ permissionModes: string[];
22
+ }
23
+ /**
24
+ * Runs `detect()`/`capabilities()` on each adapter, in parallel. Every
25
+ * bundled adapter's own `detect()` already catches its own failures (e.g.
26
+ * `pi-adapter.ts`'s `detect()` wraps its version probe in try/catch and
27
+ * resolves `{present: false}` rather than rejecting) — the catch here is a
28
+ * defensive backstop for a `RuntimeAdapter` that doesn't hold that
29
+ * convention, not a workaround for an observed failure in the bundled
30
+ * three.
31
+ */
32
+ export declare function probeRuntimes(adapters: readonly RuntimeAdapter[], options?: {
33
+ timeoutMs?: number;
34
+ }): Promise<ProbedRuntime[]>;
@@ -0,0 +1,49 @@
1
+ import { type TaskState } from '@byok-sdk/protocol';
2
+ import { type ConnectionState, type DaemonEvent, type DaemonTaskInfo } from '../index';
3
+ export interface TaskGitStatus {
4
+ /** Opaque local correlation ID; never a path or commit ID. */
5
+ workspaceId: string;
6
+ phase: string;
7
+ headChanged?: boolean;
8
+ commitsSinceBaseline?: number;
9
+ dirty?: {
10
+ staged: number;
11
+ unstaged: number;
12
+ untracked: number;
13
+ conflicted: number;
14
+ };
15
+ errorCategory?: string;
16
+ }
17
+ export type DerivedTaskInfo = DaemonTaskInfo & {
18
+ git?: TaskGitStatus;
19
+ };
20
+ /**
21
+ * Reconstructs the same shape `DaemonObserver.tasks()` exposes to a LIVE
22
+ * in-process daemon, but from a REPLAYED sequence of already-logged
23
+ * `DaemonEvent`s instead — this is what lets a separate, short-lived
24
+ * `byok-agent tasks`/`status` invocation show task state without attaching
25
+ * to the `start` process that actually observed it (see `byok-agent.ts`'s
26
+ * header comment). Deliberately a standalone reducer, not a call into
27
+ * `DaemonObserver` itself: `DaemonObserver`'s own public methods
28
+ * (`handleInboundEnvelope`/`handleOutboundEnvelope`) take a raw protocol
29
+ * `Envelope`, not the already-normalized `DaemonEvent` this module receives
30
+ * from the audit log — the two shapes don't line up, so this mirrors that
31
+ * reducer's logic operating on `DaemonEvent.kind` directly instead.
32
+ */
33
+ export declare function deriveTasksFromEvents(events: readonly DaemonEvent[]): DerivedTaskInfo[];
34
+ /**
35
+ * Most recent `connection` DaemonEvent, if the log has any — used by
36
+ * `status` to show a best-effort "last known" transport state. Never a
37
+ * live/current guarantee: if `start` isn't running right now, this is
38
+ * simply whatever it last reported before exiting (or nothing, if it never
39
+ * ran at all) — see `byok-agent.ts`'s header comment.
40
+ */
41
+ export declare function lastConnectionState(events: readonly DaemonEvent[]): {
42
+ state: ConnectionState;
43
+ ts: string;
44
+ } | undefined;
45
+ export type TaskCounts = Record<TaskState, number> & {
46
+ total: number;
47
+ };
48
+ /** Zero-filled over every `TaskState` (via `TASK_STATES`, the single authority — not a hand-maintained list) so a state with zero current tasks still renders instead of being silently absent. */
49
+ export declare function tallyTaskStates(tasks: readonly DaemonTaskInfo[]): TaskCounts;