@ours.network/install 0.17.0-nightly.10 → 0.17.0-nightly.11

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 (2) hide show
  1. package/lib/effects.mjs +32 -14
  2. package/package.json +1 -1
package/lib/effects.mjs CHANGED
@@ -18,6 +18,7 @@ import { dirname, join, resolve } from 'node:path';
18
18
  import { atomicWriteConfig, snapshotConfig, restoreConfig } from './config.mjs';
19
19
  import { askYesNo, askLine as askLineOnTty } from './prompt.mjs';
20
20
  import { classifyHarnessProbe } from './logic.mjs';
21
+ import { classifyStateDir } from './detect.mjs';
21
22
 
22
23
  /** GET http://127.0.0.1:<port>/state-dir — the unauthenticated identity probe. */
23
24
  async function probePort(port, { timeoutMs = 1500 } = {}) {
@@ -156,24 +157,41 @@ function copyToClipboard(text) {
156
157
  }
157
158
 
158
159
  /**
159
- * Every state directory on this machine that still has a daemon config.
160
+ * Every DAEMON state directory on this machine.
160
161
  *
161
- * `ours-uninstall` asks this exactly once, to answer one question: are the
162
- * GLOBAL packages still needed by somebody else? Getting it wrong the optimistic
163
- * way (reporting none) uninstalls the CLI out from under a second daemon that is
164
- * still running, so the search is deliberately conservative — it looks only where
165
- * a state directory can actually be, and an unreadable home means "there might be
166
- * others", not "there are none".
162
+ * ONE DEFINITION OF WHAT A DAEMON IS, and this function is why that matters.
167
163
  *
168
- * Where they can be: the default `~/.ours`, plus any `~/.ours*` sibling, which is
169
- * the shape every other part of this installer uses for a second daemon. A state
170
- * directory somewhere else entirely will not be found, and that is a KNOWN limit
171
- * rather than a claim — the failure is keeping a global package that could have
172
- * been removed, which is the harmless direction.
164
+ * It used to count any `~/.ours*` directory containing a config.json. Two of those
165
+ * are not daemons on a perfectly normal machine: `~/.ours-telegram/config.json` is
166
+ * the Telegram connector's and `~/.ours-cowork/config.json` is cowork's. So the
167
+ * uninstaller reported "@ours.network/cli kept — still used by the daemon at
168
+ * ~/.ours-telegram", and two things followed silently:
169
+ *
170
+ * · planGlobalPackages kept cli, mcp and the plugin packages FOREVER on any
171
+ * machine with the connector installed, naming a connector's config directory
172
+ * as a daemon;
173
+ * · worse, planPluginRemoval's `lastDaemon` went false, so the whole harness
174
+ * plugin phase was skipped — with a reason that was not true. A plain
175
+ * interactive `ours-uninstall` on a machine with the connector removed no
176
+ * plugins at all.
177
+ *
178
+ * The selection screen had already closed exactly this: config.json is the one
179
+ * piece of evidence that is AMBIGUOUS, so it cannot be the test. That predicate
180
+ * lives in lib/detect.mjs and this now calls it rather than keeping a second,
181
+ * naive copy that drifted. A daemon is identified by an artefact only a daemon
182
+ * writes, or by a config whose SHAPE is a daemon's.
183
+ *
184
+ * Still deliberately conservative about WHERE it looks: only `~/.ours` and its
185
+ * `~/.ours*` siblings. A state directory somewhere else is not found, and an
186
+ * unreadable home means "there might be others", not "there are none" — because
187
+ * the caller uses this to decide whether a GLOBAL package is still needed, and
188
+ * being wrong the optimistic way uninstalls the CLI out from under a running
189
+ * daemon.
173
190
  */
174
191
  function knownStateDirsIn(home) {
175
192
  const found = [];
176
- const consider = (dir) => { if (existsSync(join(dir, 'config.json'))) found.push(dir); };
193
+ const io = { exists: existsSync, readJson: readJsonFile };
194
+ const consider = (dir) => { if (classifyStateDir(dir, io).isDaemon) found.push(dir); };
177
195
  consider(join(home, '.ours'));
178
196
  try {
179
197
  for (const entry of readdirSync(home, { withFileTypes: true })) {
@@ -278,7 +296,7 @@ export function realEffects({ write, ttyFd, env = process.env, home = homedir(),
278
296
  };
279
297
  }
280
298
 
281
- export const __testables = { probePort, portTakenSync, readJsonFile, readTextFile, installedVersionOf };
299
+ export const __testables = { probePort, portTakenSync, readJsonFile, readTextFile, installedVersionOf, knownStateDirsIn };
282
300
 
283
301
  // -----------------------------------------------------------------------------
284
302
  // THE PAIR
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/install",
3
- "version": "0.17.0-nightly.10",
3
+ "version": "0.17.0-nightly.11",
4
4
  "private": false,
5
5
  "description": "The unified ours.network stack installer (ours-install): one guided ~3-minute flow for ours core (the daemon) + the harness plugins (Claude Code / Codex) + ours-fleet + the Telegram connector, then a single copy-paste hand-off prompt. Self-contained (Node built-ins only); run as `ours-install` or via curl|bash (install.sh).",
6
6
  "type": "module",