@geohar/un-bien 0.7.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 (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Model registry projection + the narrow SDK/ctx views the rpc command
3
+ * dispatch (`index.ts`) uses for `set_model` / `get_available_models` /
4
+ * `compact` / `new_session`.
5
+ *
6
+ * The former stock action handlers (session_compact/new/model_set/thinking_set/
7
+ * list_models) were retired when those app commands migrated to the rpc plane
8
+ * (dispatchRpcCommand). Only the shared model types + `wireFromModel` + the
9
+ * narrow ctx/pi/registry views the rpc handlers still take remain.
10
+ */
11
+ import type { WireModel } from "../protocol/types.js";
12
+ /**
13
+ * Structural subset of the SDK's `Model<Api>` interface (defined in
14
+ * `@earendil-works/pi-ai`, a transitive dep not re-exported by
15
+ * `@earendil-works/pi-coding-agent`'s main entry). Capturing just the fields we
16
+ * touch keeps this decoupled from the SDK's full Model surface.
17
+ */
18
+ export interface SdkModelLike {
19
+ id: string;
20
+ name: string;
21
+ provider: string;
22
+ reasoning: boolean;
23
+ contextWindow: number;
24
+ /** Plan/30: accepted input modalities. The SDK's `Model.input` is
25
+ * `("text" | "image")[]`; we read `includes("image")` for the `vision`
26
+ * flag. Optional here so tests can omit it (treated as text-only). */
27
+ input?: ("text" | "image")[];
28
+ }
29
+ type Model<_TApi = unknown> = SdkModelLike;
30
+ /**
31
+ * Narrow shape of the `ExtensionAPI` surface the rpc dispatch calls (setModel /
32
+ * setThinkingLevel). Lets the test layer stub just these.
33
+ */
34
+ export interface ActionPi {
35
+ setModel(model: Model<any>): Promise<boolean>;
36
+ setThinkingLevel(level: import("../protocol/types.js").ThinkingLevel): void;
37
+ }
38
+ /**
39
+ * Narrow shape of the per-call context. Drawn from the union of
40
+ * `ExtensionContextActions` (compact, getModel) and
41
+ * `ExtensionCommandContextActions` (newSession). All fields optional so a
42
+ * missing method becomes a typed error instead of a runtime TypeError.
43
+ */
44
+ export interface ActionCtx {
45
+ compact?: (options?: Record<string, unknown>) => void;
46
+ /**
47
+ * Starts a new session. `withSession` is the SDK's blessed hook for
48
+ * post-replacement work: it receives a FRESH, command-capable ctx bound to
49
+ * the new session. The SDK marks any ctx captured BEFORE this call stale, so
50
+ * callers must re-capture via `withSession` rather than reuse the old ctx.
51
+ */
52
+ newSession?: (options?: {
53
+ withSession?: (ctx: ActionCtx) => Promise<void>;
54
+ }) => Promise<{
55
+ cancelled: boolean;
56
+ }>;
57
+ getModel?: () => Model<any> | undefined;
58
+ /**
59
+ * Live session registry from Pi's extension ctx. Includes providers/models
60
+ * registered dynamically via `pi.registerProvider(...)`, unlike the fallback
61
+ * disk-backed registry un-bien can build on its own.
62
+ */
63
+ modelRegistry?: ActionModelRegistry;
64
+ }
65
+ /**
66
+ * Minimal shape of the registry surface. Maps 1:1 onto `ModelRegistry` but lets
67
+ * tests fake catalogs without instantiating the real one.
68
+ */
69
+ export interface ActionModelRegistry {
70
+ refresh(): void;
71
+ getAvailable(): Model<any>[];
72
+ find(provider: string, modelId: string): Model<any> | undefined;
73
+ }
74
+ /** Project a SDK `Model<Api>` onto the wire schema. Shared by the rpc
75
+ * `get_available_models` / `set_model` dispatch so both stay in lockstep. */
76
+ export declare function wireFromModel(model: Model<any>): WireModel;
77
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Model registry projection + the narrow SDK/ctx views the rpc command
3
+ * dispatch (`index.ts`) uses for `set_model` / `get_available_models` /
4
+ * `compact` / `new_session`.
5
+ *
6
+ * The former stock action handlers (session_compact/new/model_set/thinking_set/
7
+ * list_models) were retired when those app commands migrated to the rpc plane
8
+ * (dispatchRpcCommand). Only the shared model types + `wireFromModel` + the
9
+ * narrow ctx/pi/registry views the rpc handlers still take remain.
10
+ */
11
+ /** Project a SDK `Model<Api>` onto the wire schema. Shared by the rpc
12
+ * `get_available_models` / `set_model` dispatch so both stay in lockstep. */
13
+ export function wireFromModel(model) {
14
+ return {
15
+ id: model.id,
16
+ name: model.name,
17
+ provider: model.provider,
18
+ reasoning: model.reasoning,
19
+ context_window: model.contextWindow,
20
+ // Plan/30: vision = model accepts image input. `Model.input` is
21
+ // `("text" | "image")[]` at runtime (confirmed against pi-ai). `?.` guards
22
+ // a fake/partial model in tests → treated as text-only.
23
+ vision: model.input?.includes("image") ?? false,
24
+ };
25
+ }
26
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/actions/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuEH;8EAC8E;AAC9E,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,KAAK,CAAC,aAAa;QACnC,gEAAgE;QAChE,2EAA2E;QAC3E,wDAAwD;QACxD,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK;KAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Plan/28 — ModelRegistry access for the action handlers.
3
+ *
4
+ * The host hands every extension a ready `ModelRegistry` on
5
+ * `ExtensionContext.modelRegistry` — the same instance `AgentSession` uses
6
+ * internally. That is the ONLY source we read.
7
+ *
8
+ * History (issue #112): this module used to build its own registry via
9
+ * `ModelRegistry.create(AuthStorage.create())`. Both factories were removed
10
+ * from `@earendil-works/pi-coding-agent` in pi 0.83, so the import crashed the
11
+ * whole pi process on the first `model_set`/`list_models` from the app
12
+ * (`undefined is not an object (evaluating 'AuthStorage.create')`, thrown from
13
+ * the WebSocket line handler). Reading the registry off the live ctx drops the
14
+ * dependency on that package's factory surface entirely, so a future breaking
15
+ * change there cannot crash the extension the same way again.
16
+ *
17
+ * Two hazards this handles:
18
+ *
19
+ * 1. A ctx captured before a session replacement/reload is **stale**, and even
20
+ * *reading* `.modelRegistry` on it throws (`assertActive`). Every read is
21
+ * wrapped.
22
+ * 2. There may be no live ctx at all (daemon boot, control channel). Rather
23
+ * than throw from a WS callback — an uncaught exception that exits pi — we
24
+ * fall back to the last registry that worked, then to a stub whose methods
25
+ * never throw and report an empty catalog.
26
+ */
27
+ import type { ActionModelRegistry } from "./handlers.js";
28
+ /** Anything carrying the host registry — `ExtensionContext` and friends. */
29
+ interface RegistryBearingCtx {
30
+ modelRegistry?: ActionModelRegistry;
31
+ }
32
+ /**
33
+ * Resolve the live host `ModelRegistry` from the most recent extension ctx.
34
+ * Falls back to the last one that worked, then to an inert stub.
35
+ */
36
+ export declare function ensureModelRegistry(ctx?: RegistryBearingCtx | null): ActionModelRegistry;
37
+ /** Test seam — drop the cached registry so tests can rebuild with fakes. */
38
+ export declare function _resetModelRegistryForTests(): void;
39
+ export {};
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Plan/28 — ModelRegistry access for the action handlers.
3
+ *
4
+ * The host hands every extension a ready `ModelRegistry` on
5
+ * `ExtensionContext.modelRegistry` — the same instance `AgentSession` uses
6
+ * internally. That is the ONLY source we read.
7
+ *
8
+ * History (issue #112): this module used to build its own registry via
9
+ * `ModelRegistry.create(AuthStorage.create())`. Both factories were removed
10
+ * from `@earendil-works/pi-coding-agent` in pi 0.83, so the import crashed the
11
+ * whole pi process on the first `model_set`/`list_models` from the app
12
+ * (`undefined is not an object (evaluating 'AuthStorage.create')`, thrown from
13
+ * the WebSocket line handler). Reading the registry off the live ctx drops the
14
+ * dependency on that package's factory surface entirely, so a future breaking
15
+ * change there cannot crash the extension the same way again.
16
+ *
17
+ * Two hazards this handles:
18
+ *
19
+ * 1. A ctx captured before a session replacement/reload is **stale**, and even
20
+ * *reading* `.modelRegistry` on it throws (`assertActive`). Every read is
21
+ * wrapped.
22
+ * 2. There may be no live ctx at all (daemon boot, control channel). Rather
23
+ * than throw from a WS callback — an uncaught exception that exits pi — we
24
+ * fall back to the last registry that worked, then to a stub whose methods
25
+ * never throw and report an empty catalog.
26
+ */
27
+ let _lastGoodRegistry = null;
28
+ /**
29
+ * Never-throwing empty catalog. Used only when no ctx has ever been seen —
30
+ * callers get an empty model list instead of a crashed agent.
31
+ */
32
+ const _stubRegistry = {
33
+ refresh() { },
34
+ getAvailable() {
35
+ return [];
36
+ },
37
+ find() {
38
+ return undefined;
39
+ },
40
+ };
41
+ /**
42
+ * Resolve the live host `ModelRegistry` from the most recent extension ctx.
43
+ * Falls back to the last one that worked, then to an inert stub.
44
+ */
45
+ export function ensureModelRegistry(ctx) {
46
+ try {
47
+ const live = ctx?.modelRegistry;
48
+ if (live) {
49
+ _lastGoodRegistry = live;
50
+ return live;
51
+ }
52
+ }
53
+ catch {
54
+ // stale ctx after session replacement — reading the property throws.
55
+ }
56
+ return _lastGoodRegistry ?? _stubRegistry;
57
+ }
58
+ /** Test seam — drop the cached registry so tests can rebuild with fakes. */
59
+ export function _resetModelRegistryForTests() {
60
+ _lastGoodRegistry = null;
61
+ }
62
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AASH,IAAI,iBAAiB,GAA+B,IAAI,CAAC;AAEzD;;;GAGG;AACH,MAAM,aAAa,GAAwB;IACzC,OAAO,KAAI,CAAC;IACZ,YAAY;QACV,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAA+B;IAE/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,EAAE,aAAa,CAAC;QAChC,IAAI,IAAI,EAAE,CAAC;YACT,iBAAiB,GAAG,IAAI,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;IACvE,CAAC;IACD,OAAO,iBAAiB,IAAI,aAAa,CAAC;AAC5C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,2BAA2B;IACzC,iBAAiB,GAAG,IAAI,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pi-supervisord` — long-running daemon supervisor.
4
+ *
5
+ * Entry point of the `pi-supervisord` binary (plan/26 W2). Run by
6
+ * systemd/launchd in production, or directly during dev:
7
+ *
8
+ * pnpm build
9
+ * node dist/bin/supervisord.js
10
+ *
11
+ * Once running, it:
12
+ * - Reads `~/.pi/un-bien/daemons.json`
13
+ * - Spawns `pi --mode rpc -e <un-bien/dist/index.js>` per entry
14
+ * - Listens on `~/.pi/un-bien/supervisor.sock` for CLI control requests
15
+ * - Restarts crashed children with exponential backoff
16
+ *
17
+ * Exits cleanly on SIGTERM/SIGINT (used by `unbien uninstall`).
18
+ */
19
+ import { fileURLToPath } from "node:url";
20
+ import { dirname, join } from "node:path";
21
+ import { Supervisor, SupervisorAlreadyRunningError, } from "../daemon/supervisor.js";
22
+ const HELP_TEXT = `pi-supervisord — Remote Pi daemon supervisor
23
+
24
+ Usage: pi-supervisord
25
+
26
+ Runs the long-lived supervisor: reads ~/.pi/un-bien/daemons.json, spawns one
27
+ \`pi --mode rpc\` child per entry, and listens on ~/.pi/un-bien/supervisor.sock
28
+ for control requests from the \`un-bien\` CLI.
29
+
30
+ This binary takes NO arguments — it is normally launched by systemd/launchd
31
+ (via \`unbien install\`), not by hand. Manage the fleet with:
32
+ unbien daemon start | stop | restart | status
33
+ unbien daemons
34
+
35
+ Options:
36
+ -h, --help Show this help and exit
37
+ -v, --version Print version and exit
38
+ `;
39
+ async function main() {
40
+ // Guard: any stray argument used to fall through and start a FULL
41
+ // supervisor (a `pi-supervisord --help` once ran for days). Handle the
42
+ // conventional flags explicitly and reject unknown args instead of
43
+ // silently spawning the daemon fleet.
44
+ const args = process.argv.slice(2);
45
+ if (args.includes("-h") || args.includes("--help")) {
46
+ process.stdout.write(HELP_TEXT);
47
+ return;
48
+ }
49
+ if (args.includes("-v") || args.includes("--version")) {
50
+ process.stdout.write("pi-supervisord (un-bien)\n");
51
+ return;
52
+ }
53
+ if (args.length > 0) {
54
+ process.stderr.write(`pi-supervisord: unexpected argument(s): ${args.join(" ")}\n` +
55
+ "This binary takes no arguments. Run `pi-supervisord --help`.\n");
56
+ process.exit(2);
57
+ }
58
+ // The supervisor needs to point each spawned Pi at the extension
59
+ // entry it's bundled with. We're at `dist/bin/supervisord.js` after
60
+ // build; the extension is the sibling `dist/index.js`.
61
+ const here = fileURLToPath(import.meta.url);
62
+ const distRoot = dirname(dirname(here)); // dist/bin → dist
63
+ const extensionPath = join(distRoot, "index.js");
64
+ const supervisor = new Supervisor({ extensionPath });
65
+ await supervisor.start();
66
+ process.stderr.write(`[pi-supervisord] up — UDS: ~/.pi/un-bien/supervisor.sock, extension: ${extensionPath}\n`);
67
+ const shutdown = async (signal) => {
68
+ process.stderr.write(`[pi-supervisord] received ${signal}, shutting down\n`);
69
+ await supervisor.stop();
70
+ process.exit(0);
71
+ };
72
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
73
+ process.on("SIGINT", () => void shutdown("SIGINT"));
74
+ }
75
+ main().catch((err) => {
76
+ // "Already running" is a normal, expected condition (systemd/launchd may
77
+ // race a manual start, or the user double-launches) — not a crash. Report
78
+ // it calmly and exit 0 so service managers don't flag a failure loop.
79
+ if (err instanceof SupervisorAlreadyRunningError) {
80
+ process.stderr.write(`[pi-supervisord] ${err.message}\n`);
81
+ process.exit(0);
82
+ }
83
+ process.stderr.write(`[pi-supervisord] fatal: ${String(err)}\n`);
84
+ process.exit(1);
85
+ });
86
+ //# sourceMappingURL=supervisord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisord.js","sourceRoot":"","sources":["../../src/bin/supervisord.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ export type UnBienConfig = {
2
+ relay?: string;
3
+ /**
4
+ * Debug prefs. Fields here gate the file-based diagnostic logs (see
5
+ * `session/debug_log.ts` and `panel_bridge.ts`). Read from config — NOT env —
6
+ * because the fork usually runs detached and doesn't inherit a shell's env, so
7
+ * a persisted config field is the only reliable enable switch. Absent/false by
8
+ * default: no logging.
9
+ */
10
+ debug?: {
11
+ envelope?: boolean;
12
+ panels?: boolean;
13
+ };
14
+ /**
15
+ * Machine-wide fallback defaults for a session's LOCAL config (the per-cwd
16
+ * `.pi/un-bien/config.json`). A field here applies to every cwd that does
17
+ * not set it via the inline `UNBIEN_DIRECT_CONFIG` env or its own file, so
18
+ * you can pin `auto_start_relay` once — beside `relay` — instead of dropping a
19
+ * file into every repo. See `session/local_config.ts`. Absent by default, so
20
+ * omitting it preserves the historical per-cwd-only behaviour exactly.
21
+ */
22
+ defaults?: {
23
+ auto_start_relay?: boolean;
24
+ };
25
+ };
26
+ export declare function loadConfig(): UnBienConfig;
27
+ export declare function saveConfig(patch: Partial<UnBienConfig>): void;
28
+ export type RelayResolution = {
29
+ url: string;
30
+ source: "env" | "config";
31
+ } | {
32
+ url: null;
33
+ source: "unset";
34
+ };
35
+ /**
36
+ * Resolves the effective relay URL in **canonical http(s):// form**.
37
+ *
38
+ * Precedence:
39
+ * 1. `UNBIEN_RELAY` env var (ops/CI escape hatch)
40
+ * 2. `relay` field in the global config
41
+ * (`<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`, set via
42
+ * `/unbien set-relay`)
43
+ *
44
+ * There is NO built-in default. When neither is set, `url` is null and
45
+ * `source` is `"unset"` — callers MUST refuse to connect and prompt the user
46
+ * to configure a relay (un-bien ships pointing at nobody's infrastructure).
47
+ *
48
+ * Any ws(s):// values found (legacy configs or env overrides) are coerced
49
+ * to http(s):// defensively — the canonical form across the codebase is
50
+ * http(s)://, and the transport layer converts to ws(s):// at WS-open time.
51
+ */
52
+ export declare function resolveRelayUrl(): RelayResolution;
53
+ /**
54
+ * Strict validator for **user-provided** relay URLs (via `/unbien
55
+ * set-relay` or `/unbien relay url`).
56
+ *
57
+ * Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
58
+ * **rejected** — the canonical form stored in config is http(s):// and the
59
+ * extension converts to ws(s):// internally when opening the WebSocket.
60
+ * Forcing a single scheme at the user boundary avoids two-form drift.
61
+ */
62
+ export declare function isValidRelayUrl(url: string): boolean;
63
+ /**
64
+ * Returns true if the URL uses ws:// or wss:// scheme — for emitting a
65
+ * targeted error message when the user pastes a WebSocket URL by mistake.
66
+ */
67
+ export declare function isWebSocketScheme(url: string): boolean;
68
+ /**
69
+ * Converts an http(s):// URL to the corresponding ws(s):// form. Used by
70
+ * the transport layer right before opening the WebSocket — config storage
71
+ * and the mesh HTTP client both stay on http(s)://.
72
+ *
73
+ * https://host → wss://host
74
+ * http://host → ws://host
75
+ * ws(s)://host → pass-through (defensive — env overrides or legacy
76
+ * configs may still carry ws(s)://)
77
+ */
78
+ export declare function toWebSocketUrl(url: string): string;
79
+ /**
80
+ * Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
81
+ * ws(s):// values back to canonical http(s):// before returning them to
82
+ * the rest of the codebase.
83
+ */
84
+ export declare function toHttpUrl(url: string): string;
package/dist/config.js ADDED
@@ -0,0 +1,115 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { unbienConfigHome } from "./paths.js";
4
+ // Resolved at call time via `unbienConfigHome()` — the global config is a pi
5
+ // extension config at `<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`.
6
+ // See paths.ts.
7
+ const configDir = () => unbienConfigHome();
8
+ const configFile = () => path.join(unbienConfigHome(), "un-bien.json");
9
+ export function loadConfig() {
10
+ try {
11
+ const raw = fs.readFileSync(configFile(), "utf8");
12
+ const parsed = JSON.parse(raw);
13
+ if (!parsed || typeof parsed !== "object")
14
+ return {};
15
+ return parsed;
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ export function saveConfig(patch) {
22
+ fs.mkdirSync(configDir(), { recursive: true });
23
+ const current = loadConfig();
24
+ const next = { ...current, ...patch };
25
+ fs.writeFileSync(configFile(), JSON.stringify(next, null, 2));
26
+ }
27
+ /**
28
+ * Resolves the effective relay URL in **canonical http(s):// form**.
29
+ *
30
+ * Precedence:
31
+ * 1. `UNBIEN_RELAY` env var (ops/CI escape hatch)
32
+ * 2. `relay` field in the global config
33
+ * (`<PI_CODING_AGENT_DIR|~/.pi>/extensions/un-bien.json`, set via
34
+ * `/unbien set-relay`)
35
+ *
36
+ * There is NO built-in default. When neither is set, `url` is null and
37
+ * `source` is `"unset"` — callers MUST refuse to connect and prompt the user
38
+ * to configure a relay (un-bien ships pointing at nobody's infrastructure).
39
+ *
40
+ * Any ws(s):// values found (legacy configs or env overrides) are coerced
41
+ * to http(s):// defensively — the canonical form across the codebase is
42
+ * http(s)://, and the transport layer converts to ws(s):// at WS-open time.
43
+ */
44
+ export function resolveRelayUrl() {
45
+ const env = process.env["UNBIEN_RELAY"];
46
+ if (env && env.length > 0)
47
+ return { url: toHttpUrl(env), source: "env" };
48
+ const cfg = loadConfig();
49
+ if (cfg.relay && cfg.relay.length > 0)
50
+ return { url: toHttpUrl(cfg.relay), source: "config" };
51
+ return { url: null, source: "unset" };
52
+ }
53
+ /**
54
+ * Strict validator for **user-provided** relay URLs (via `/unbien
55
+ * set-relay` or `/unbien relay url`).
56
+ *
57
+ * Only accepts `http://` and `https://`. `ws://`/`wss://` are deliberately
58
+ * **rejected** — the canonical form stored in config is http(s):// and the
59
+ * extension converts to ws(s):// internally when opening the WebSocket.
60
+ * Forcing a single scheme at the user boundary avoids two-form drift.
61
+ */
62
+ export function isValidRelayUrl(url) {
63
+ if (!url)
64
+ return false;
65
+ const lower = url.toLowerCase();
66
+ if (!lower.startsWith("http://") && !lower.startsWith("https://"))
67
+ return false;
68
+ try {
69
+ new URL(url);
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Returns true if the URL uses ws:// or wss:// scheme — for emitting a
78
+ * targeted error message when the user pastes a WebSocket URL by mistake.
79
+ */
80
+ export function isWebSocketScheme(url) {
81
+ const lower = url.toLowerCase();
82
+ return lower.startsWith("ws://") || lower.startsWith("wss://");
83
+ }
84
+ /**
85
+ * Converts an http(s):// URL to the corresponding ws(s):// form. Used by
86
+ * the transport layer right before opening the WebSocket — config storage
87
+ * and the mesh HTTP client both stay on http(s)://.
88
+ *
89
+ * https://host → wss://host
90
+ * http://host → ws://host
91
+ * ws(s)://host → pass-through (defensive — env overrides or legacy
92
+ * configs may still carry ws(s)://)
93
+ */
94
+ export function toWebSocketUrl(url) {
95
+ const lower = url.toLowerCase();
96
+ if (lower.startsWith("https://"))
97
+ return "wss://" + url.slice("https://".length);
98
+ if (lower.startsWith("http://"))
99
+ return "ws://" + url.slice("http://".length);
100
+ return url;
101
+ }
102
+ /**
103
+ * Inverse of `toWebSocketUrl`. Used by `resolveRelayUrl` to coerce any
104
+ * ws(s):// values back to canonical http(s):// before returning them to
105
+ * the rest of the codebase.
106
+ */
107
+ export function toHttpUrl(url) {
108
+ const lower = url.toLowerCase();
109
+ if (lower.startsWith("wss://"))
110
+ return "https://" + url.slice("wss://".length);
111
+ if (lower.startsWith("ws://"))
112
+ return "http://" + url.slice("ws://".length);
113
+ return url;
114
+ }
115
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type ControlReplyFor, type ControlRequest } from "./control_protocol.js";
2
+ export declare class SupervisorOfflineError extends Error {
3
+ readonly sockPath: string;
4
+ constructor(sockPath: string);
5
+ }
6
+ /**
7
+ * Sends a single request and returns the typed reply data.
8
+ *
9
+ * Throws:
10
+ * - `SupervisorOfflineError` when the supervisor isn't reachable.
11
+ * - `Error` from `parseReply` when the reply line is malformed.
12
+ * - The supervisor's own error string when `ok: false`.
13
+ */
14
+ export declare function callSupervisor<Op extends ControlRequest["op"]>(req: Extract<ControlRequest, {
15
+ op: Op;
16
+ }>): Promise<ControlReplyFor<Op>>;
17
+ /** Returns true when the supervisor is reachable. Used by `/unbien
18
+ * daemons` to decide whether to query runtime state or fall back to
19
+ * registry-only listing. */
20
+ export declare function supervisorOnline(): Promise<boolean>;
@@ -0,0 +1,131 @@
1
+ import { existsSync } from "node:fs";
2
+ import { createConnection } from "node:net";
3
+ import { encodeRequest, parseReply, } from "./control_protocol.js";
4
+ import { getSupervisorSockPath } from "./supervisor.js";
5
+ import { usesNamedPipe } from "../session/ipc.js";
6
+ /**
7
+ * Tiny client for the `un-bien` CLI to call the supervisor over the
8
+ * `~/.pi/un-bien/supervisor.sock` UDS.
9
+ *
10
+ * Each call opens a fresh connection, sends one request line, reads one
11
+ * reply line, closes. No connection pooling — the CLI is short-lived,
12
+ * latency is dominated by the socket round-trip (<1ms).
13
+ *
14
+ * `SupervisorOfflineError` is the common error: thrown when the socket
15
+ * file is missing OR the connect fails (no listener). The CLI handler
16
+ * formats it as a friendly "Run `unbien install` first" message.
17
+ */
18
+ const CONNECT_TIMEOUT_MS = 1000;
19
+ const REPLY_TIMEOUT_MS = 5000;
20
+ export class SupervisorOfflineError extends Error {
21
+ sockPath;
22
+ constructor(sockPath) {
23
+ super(`Supervisor is not running. UDS not responding at ${sockPath}.\n` +
24
+ "Run `unbien install` to set it up, or start it manually with `pi-supervisord`.");
25
+ this.sockPath = sockPath;
26
+ this.name = "SupervisorOfflineError";
27
+ }
28
+ }
29
+ /**
30
+ * Sends a single request and returns the typed reply data.
31
+ *
32
+ * Throws:
33
+ * - `SupervisorOfflineError` when the supervisor isn't reachable.
34
+ * - `Error` from `parseReply` when the reply line is malformed.
35
+ * - The supervisor's own error string when `ok: false`.
36
+ */
37
+ export async function callSupervisor(req) {
38
+ const sockPath = getSupervisorSockPath();
39
+ // POSIX fast-fail on a missing socket file. Windows pipes have no file —
40
+ // skip the check and let `_connect` fail fast if the pipe isn't there.
41
+ if (!usesNamedPipe() && !existsSync(sockPath))
42
+ throw new SupervisorOfflineError(sockPath);
43
+ const sock = await _connect(sockPath);
44
+ try {
45
+ sock.write(encodeRequest(req));
46
+ const line = await _readLine(sock);
47
+ const reply = parseReply(line);
48
+ if (!reply.ok)
49
+ throw new Error(reply.error);
50
+ return reply.data;
51
+ }
52
+ finally {
53
+ sock.destroy();
54
+ }
55
+ }
56
+ /** Returns true when the supervisor is reachable. Used by `/unbien
57
+ * daemons` to decide whether to query runtime state or fall back to
58
+ * registry-only listing. */
59
+ export async function supervisorOnline() {
60
+ const sockPath = getSupervisorSockPath();
61
+ if (!usesNamedPipe() && !existsSync(sockPath))
62
+ return false;
63
+ try {
64
+ const sock = await _connect(sockPath);
65
+ sock.destroy();
66
+ return true;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ // ── internals ───────────────────────────────────────────────────────────────
73
+ function _connect(sockPath) {
74
+ return new Promise((resolve, reject) => {
75
+ const sock = createConnection({ path: sockPath });
76
+ let settled = false;
77
+ const timer = setTimeout(() => {
78
+ if (settled)
79
+ return;
80
+ settled = true;
81
+ sock.destroy();
82
+ reject(new SupervisorOfflineError(sockPath));
83
+ }, CONNECT_TIMEOUT_MS);
84
+ sock.once("connect", () => {
85
+ if (settled)
86
+ return;
87
+ settled = true;
88
+ clearTimeout(timer);
89
+ sock.setEncoding("utf8");
90
+ resolve(sock);
91
+ });
92
+ sock.once("error", () => {
93
+ if (settled)
94
+ return;
95
+ settled = true;
96
+ clearTimeout(timer);
97
+ reject(new SupervisorOfflineError(sockPath));
98
+ });
99
+ });
100
+ }
101
+ function _readLine(sock) {
102
+ return new Promise((resolve, reject) => {
103
+ let buf = "";
104
+ const timer = setTimeout(() => {
105
+ reject(new Error("supervisor reply timeout"));
106
+ sock.destroy();
107
+ }, REPLY_TIMEOUT_MS);
108
+ sock.on("data", (chunk) => {
109
+ buf += chunk;
110
+ const nl = buf.indexOf("\n");
111
+ if (nl >= 0) {
112
+ clearTimeout(timer);
113
+ resolve(buf.slice(0, nl));
114
+ }
115
+ });
116
+ sock.on("end", () => {
117
+ clearTimeout(timer);
118
+ const nl = buf.indexOf("\n");
119
+ if (nl >= 0)
120
+ return resolve(buf.slice(0, nl));
121
+ if (buf.length > 0)
122
+ return resolve(buf);
123
+ reject(new Error("supervisor closed connection without replying"));
124
+ });
125
+ sock.on("error", (err) => {
126
+ clearTimeout(timer);
127
+ reject(err);
128
+ });
129
+ });
130
+ }
131
+ //# sourceMappingURL=client.js.map