@ours.network/install 0.17.0-nightly.1 → 0.17.0-nightly.3

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 (4) hide show
  1. package/README.md +152 -24
  2. package/install.mjs +425 -75
  3. package/lib/logic.mjs +419 -21
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -43,37 +43,154 @@ dependency on the things it installs): an ASCII banner, tasteful colour (degrade
43
43
  answers `--version` promptly. A shell alias / hanging wrapper is **never called** (that would
44
44
  hang the run) — it's reported plainly with a fix, and a manual-install path is always offered.
45
45
  If neither harness exists it says so and exits.
46
- 2. **Config-first** (first install only) the daemon's two base settings, up front:
47
- the **broker** (end-to-end encrypted; the broker never sees message content — almost everyone
48
- just presses Enter) and the **port** (probes `3050`; only asks if it's busy; never hands out
49
- `3051`, reserved for the Telegram connector). Applied once, then the stack is built with it.
50
- 3. **Four consent gates**, each paced with a clean `✓ … No problems.` line + an explicit
46
+ 2. **Five consent gates**, each paced with a clean `✓ No problems.` line + an explicit
51
47
  **Continue?** — never a start-twice-then-ask, never a silent failure:
52
- - **1/4 ours core (the daemon)** — write config optional voice setup install/start ONCE
53
- boot service. On a re-run it reuses the running config (no re-ask) and only updates when
54
- you say yes. Complete voice setup is kept without prompting. Missing/incomplete setup is
55
- offered before the first start or pending update restart, then delegated to the canonical
56
- `ours-mcp voice-setup` provider selector and hidden API-key prompt. Accepted setup owns the
57
- one restart/readiness transaction; declining or already-ready setup preserves the normal
58
- core lifecycle. The secret is written atomically to mode-`0600` config; a failed daemon
59
- reload rolls back.
60
- - **2/4 harness plugins** the installer **drives the plugin CLIs itself**
48
+ - **1/5 the shared ours daemon** — its own step, before every consumer, and it owns its own
49
+ configuration: the **broker** (end-to-end encrypted; the broker never sees message content
50
+ almost everyone just presses Enter) and the **listen port**, which is now an explicit
51
+ question rather than one you only hear about when `3050` is busy. The default is still
52
+ `3050` (the next free port when that is taken), so **Enter and every non-interactive run
53
+ land exactly where they always did**. The answer is validated (a real port, not reserved by
54
+ another component, not already in use, not already claimed by another daemon in this run),
55
+ persisted to `~/.ours/config.json`, and it is the endpoint everything below is wired to.
56
+ Then: write config optional voice setup install/start ONCE boot service. On a re-run
57
+ it reuses the running config (no re-ask) and only updates when you say yes. Complete voice
58
+ setup is kept without prompting. Missing/incomplete setup is offered before the first start
59
+ or pending update restart, then delegated to the canonical `ours-mcp voice-setup` provider
60
+ selector and hidden API-key prompt. Accepted setup owns the one restart/readiness
61
+ transaction; declining or already-ready setup preserves the normal core lifecycle. The
62
+ secret is written atomically to mode-`0600` config; a failed daemon reload rolls back.
63
+ - **2/5 harness plugins** — the installer **drives the plugin CLIs itself**
61
64
  (`claude plugin marketplace add …` + `claude plugin install ours@ours.network`;
62
65
  `codex plugin marketplace add …` + `codex plugin add ours@ours-codex-marketplace`). Choosing
63
66
  Codex also installs the `ours-codex` live launcher in the same step. Any failure / alias
64
67
  prints the exact manual commands and continues — it **never dead-ends**.
65
- - **3/4 ours-fleet** — makes your harnesses persistent, always-online agent teams that survive
68
+ - **3/5 ours-fleet** — makes your harnesses persistent, always-online agent teams that survive
66
69
  a reboot; runs `ours-fleet init`. Default **Yes**.
67
- - **4/4 Telegram connector** — install-only (no bot tokens here), then optionally as a
68
- boot service.
69
- 4. **Summary + hand-off** a recap (skipped/failed rows call out the fix), then a **literal
70
- copy-paste prompt** (root identity + fleet + Telegram) with the steps for any skipped/failed
71
- component dropped out. Copied to the clipboard where supported.
70
+ - **4/5 Telegram connector** — install-only (no bot tokens here), then a question asked
71
+ **independently of every other consumer**: use the shared daemon from step 1, or run against
72
+ its **own dedicated daemon**? Default (and Enter, and non-interactive) is the shared one.
73
+ A dedicated daemon is provisioned with its own port, its own state directory
74
+ (`~/.ours-tg`) and its own boot unit (`ours-tg.service`), and the connector is wired to
75
+ that endpoint. Then optionally installed as a boot service itself.
76
+ - **5/5 Rooms (ours-cowork)** — durable mission rooms. Default **No**. Configures its own
77
+ surface (the deployment's broker, its state directory `~/.ours-cowork`, and its loopback
78
+ **console port** — default `3052`, validated the same way), then asks the same
79
+ shared-vs-dedicated daemon question, defaulting to shared. A dedicated Rooms daemon gets
80
+ `~/.ours-rooms` and `ours-rooms.service`. Then `ours-cowork install-service`. See
81
+ **Daemon topology** for when the question is asked at all.
82
+ 3. **Summary + hand-off** — a recap (skipped/failed rows call out the fix), then a **literal
83
+ copy-paste prompt** (human identity + fleet + Telegram + Rooms) with the steps for any
84
+ skipped/failed component dropped out. Copied to the clipboard where supported.
72
85
 
73
86
  The human identity is created idempotently after the daemon becomes reachable. Because
74
87
  `curl … | bash` gives the script its input over the pipe, every prompt is read from the
75
88
  controlling terminal (`/dev/tty`), so the flow still works piped.
76
89
 
90
+ ## Daemon topology
91
+
92
+ A clean deployment installs, configures and starts **one shared** ours daemon first, then wires
93
+ every client to that same daemon. That is the default and the backward-compatible answer.
94
+
95
+ **Optional isolation.** The Telegram connector and Rooms may each instead be given their own
96
+ daemon, chosen independently of one another. Isolation is only real when all three of these are
97
+ separate, which is what the installer provisions:
98
+
99
+ | | shared | dedicated (Telegram) | dedicated (Rooms) |
100
+ |---|---|---|---|
101
+ | listen port | your step-1 answer | its own, validated against every other daemon in the run | likewise |
102
+ | state directory | `~/.ours` | `~/.ours-tg` — the daemon's API token lives here | `~/.ours-rooms` |
103
+ | boot unit | `ours.service` | `ours-tg.service` (via `OURS_SERVICE_NAME`) | `ours-rooms.service` |
104
+ | config file | `~/.ours/config.json` | `~/.ours-tg/config.json` | `~/.ours-rooms/config.json` |
105
+
106
+ Without a distinct service name, `ours-mcp install-service` would write the **same** unit for
107
+ both and the second daemon would silently overwrite the first's port and state directory. See
108
+ `packages/core/src/service-instance.ts`; a daemon with no instance name keeps exactly the
109
+ historical `ours.service` / `solutions.adaptframework.ours`.
110
+
111
+ **Rooms has a third answer.** `ours-cowork` used to host its own daemon, always. It now also
112
+ supports an **external** one, so Rooms answers the same shared-vs-dedicated question — plus
113
+ `embedded`, cowork's own, which is what every install predating that support runs.
114
+
115
+ Its config carries an optional `daemon` block. Absent means embedded; external is
116
+ `{ mode: 'external', endpoint, stateDir }` and **requires both halves**, because cowork stores no
117
+ token and its SDK reads `<stateDir>/daemon-token`. Env equivalents are `OURS_COWORK_DAEMON_MODE`
118
+ / `_ENDPOINT` / `_STATE_DIR`, and the service unit carries only those — never a token.
119
+
120
+ Two things follow from cowork's boot being **fail-closed** (an unreachable endpoint, a non-ours
121
+ daemon, or a mismatched state directory aborts startup, with no embedded fallback):
122
+
123
+ - A build that **predates** the external mode is never handed a block. The check is on the
124
+ **installed version**, not the channel: `COWORK_EXTERNAL_MIN_VERSION` in `lib/logic.mjs` is
125
+ pinned to `0.4.1-nightly.20260816.4aaf940`, the first published cowork that implements it
126
+ (verified against the registry *and* the tarball's contents). A channel-only gate would have
127
+ accepted an earlier nightly of the same core version. `cowork@latest` (0.4.0) predates the
128
+ mode, so the stable installer keeps Rooms embedded, names the build it found and the version it
129
+ needed, and points at `OURS_CHANNEL=nightly`. An unreadable version keeps Rooms embedded too —
130
+ it never guesses.
131
+ - An install **already running embedded** is never migrated behind the user's back — headless
132
+ runs leave it exactly as it is, and interactive runs ask first.
133
+
134
+ Note the two different `stateDir` keys: the top-level one is cowork's own private state;
135
+ `daemon.stateDir` is the **ours daemon's** state directory. Confusing them fails closed at boot.
136
+
137
+ What wiring a client to a daemon takes differs per client:
138
+
139
+ - **The harness plugins** need nothing extra: each is `ours-mcp proxy`, which reads the daemon's
140
+ own config (`OURS_CONFIG`, else `~/.ours/config.json`). `autoStart` is off by default, so a proxy
141
+ whose daemon is down reports it rather than quietly starting a second one.
142
+ - **The Telegram connector keeps its own config file** and never inherits the daemon's, so the
143
+ installer writes `~/.ours-telegram/config.json` (honouring `OURS_TG_CONFIG`) **before** the
144
+ connector is started or installed as a service — `install-service` bakes whatever it resolves
145
+ into the service unit as environment variables, and those outrank the file from then on.
146
+ Three keys are written, so whichever connector generation is installed finds what it reads:
147
+ - `daemonUrl` + `daemonStateDir` — for `>=0.3.3-nightly.1`, which attaches to the running daemon
148
+ over `/api/v1`. **Both** are required: with neither, its SDK never reads `~/.ours/config.json`
149
+ and falls back to the built-in `127.0.0.1:3050`, missing a daemon on any other port; with the
150
+ endpoint alone it refuses outright (`INCOHERENT_SELECTION` — the daemon's API token belongs to
151
+ a state directory, so selecting an endpoint without one would disclose that token).
152
+ - `brokerUrl` — for `<=0.3.2`, which hosts its own ADAPT wrapper and meets the daemon at a broker
153
+ instead. It must match the daemon's or the two can never see each other.
154
+
155
+ A re-run that changes nothing rewrites nothing, and keys the installer does not own are preserved.
156
+
157
+ If `ours-mcp install-service` fails (no systemd user bus, no linger, a container, WSL without
158
+ systemd) it has already **stopped** the daemon it was about to supervise. The installer restarts it
159
+ and says plainly that the boot service is missing — a clean deployment never ends with no daemon
160
+ while the summary claims success.
161
+
162
+ ## Release channel
163
+
164
+ `OURS_CHANNEL=nightly` (or `OURS_INSTALL_CHANNEL`) installs each package's **own** prerelease
165
+ dist-tag. The tag is not the same string everywhere, so the mapping is per package:
166
+
167
+ | package | stable channel | nightly channel |
168
+ |---|---|---|
169
+ | `mcp`, `tg-connector`, `claude-code`, `codex`, `hermes` | `latest` | `nightly` |
170
+ | `fleet` | `latest` | `nightly` |
171
+ | `cowork` (Rooms) | `latest` | `nightly` |
172
+
173
+ `fleet` follows the channel: it publishes its own `nightly` dist-tag, and the nightly stack needs
174
+ the fleet build carrying the SDK integration. A nightly installer that quietly installed stable
175
+ fleet is the same split-brain deployment the channel exists to prevent.
176
+
177
+ `cowork` publishes `nightly` alongside every other service (it previously used `next`). The
178
+ nightly channel must reach that line, because the external-daemon mode the Rooms step configures
179
+ ships there; taking `latest` would pair a config carrying a `daemon` block with a build that
180
+ predates it.
181
+
182
+ A package with no mapping for the selected channel installs `@latest` rather than a guessed tag,
183
+ because a 404 fails the *whole* install — which is also why the nightly installer must not be
184
+ published before every package it names actually has the tag it will ask for.
185
+
186
+ **With no explicit selection the installer follows its own version.** A published nightly build
187
+ carries the `-nightly.N` suffix the release bump stamps, so `npm i -g @ours.network/install@nightly`
188
+ builds a nightly stack, and a stable installer can never consume a nightly. This is load-bearing
189
+ rather than cosmetic: across `tg-connector` 0.3.2 → 0.3.3-nightly.1 the connector stopped hosting
190
+ its own ADAPT wrapper and became a client of the shared daemon, so mixing tags across that boundary
191
+ pairs a connector that needs `/api/v1` with a daemon that does not serve it. `OURS_CHANNEL` still
192
+ overrides in both directions.
193
+
77
194
  ## Non-interactive / CI / safe dry-run
78
195
 
79
196
  ```sh
@@ -96,6 +213,15 @@ is reported and left unchanged.
96
213
  | `OURS_INSTALL_DRY_RUN` | walk the flow without installing or changing anything |
97
214
  | `OURS_NPM` | npm binary to use (default `npm`) |
98
215
  | `OURS_CONFIG` | daemon config file location (default `~/.ours/config.json`) |
216
+ | `OURS_STATE_DIR` | daemon state directory (default `~/.ours`) — also what the Telegram connector is told to expect |
217
+ | `OURS_TG_CONFIG` | Telegram connector config file location (default `~/.ours-telegram/config.json`) |
218
+ | `OURS_COWORK_CONFIG` | Rooms config file location (default `~/.ours-cowork/config.json`) |
219
+ | `OURS_COWORK_DAEMON_MODE` / `_ENDPOINT` / `_STATE_DIR` | cowork's own env equivalents of its `daemon` block (read by cowork, not set by the installer) |
220
+ | `OURS_CHANNEL` | `nightly` or `latest`; unset follows the installer's own version |
221
+
222
+ A non-interactive run takes the shared daemon on its existing default port and installs no
223
+ dedicated daemon and no Rooms — the topology is unchanged from before this flow existed. It also
224
+ never converts an existing embedded Rooms install to an external daemon.
99
225
 
100
226
  ## Uninstall
101
227
 
@@ -138,9 +264,11 @@ OURS_UNINSTALL_DAEMON=yes \
138
264
  - This package is **not published to npm** (`private: true`); it ships as the hosted
139
265
  `install.sh` bootstrap plus the `install.mjs` Node installer (and its `lib/`), and exposes the
140
266
  `ours-install` bin. The pieces it installs — the daemon (`@ours.network/mcp`), the harness
141
- plugins via each marketplace, `@ours.network/fleet`, and `@ours.network/tg-connector` — are the
142
- published components.
267
+ plugins via each marketplace, `@ours.network/fleet`, `@ours.network/tg-connector`, and
268
+ `@ours.network/cowork` — are the published components.
143
269
  - **Idempotent + safe to re-run.** A re-run adds a skipped piece, re-points the plugins, or (only
144
270
  when you say yes) updates a component; an already-current daemon is left untouched, its running
145
- port and complete voice setup are reused everywhere. Bot tokens and fleet roles remain in the
146
- copy-paste hand-off; provider keys never enter that prompt or agent chat.
271
+ port and complete voice setup are reused everywhere and the Telegram connector's daemon
272
+ selection, a dedicated daemon's config, and the Rooms config are each rewritten only if they
273
+ actually changed. Bot tokens and fleet roles remain in the copy-paste hand-off; provider keys
274
+ never enter that prompt or agent chat.