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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/usage.mjs ADDED
@@ -0,0 +1,47 @@
1
+ // ours-install v3 — the help text.
2
+ //
3
+ // It lives here rather than in the bin because `--help` is a behaviour with a
4
+ // contract (the flags it names must be the flags target.mjs accepts), and a bin
5
+ // that is three lines long cannot be the place a contract is asserted.
6
+
7
+ export const USAGE = `ours-install — the unified ours.network stack installer.
8
+
9
+ Install: npm i -g @ours.network/install && ours-install (recommended)
10
+ npx @ours.network/install (one-off)
11
+
12
+ ours-install [--state-dir PATH] [--port N] [--dry-run] [--help] [--version]
13
+
14
+ Guided setup for the whole stack: the ours daemon, its components (the MCP
15
+ server, the Telegram connector, cowork), your harness plugins (Claude Code /
16
+ Codex / Hermes), ours-fleet and voice transcription — then one copy-paste
17
+ hand-off prompt. You approve each step; re-run any time to add a piece or update.
18
+
19
+ --state-dir the daemon's STATE DIRECTORY, which is what identifies a daemon
20
+ (default ~/.ours). A second state directory is a second daemon.
21
+ --port used only when CREATING a daemon. For a daemon that already owns
22
+ the state directory the port comes from its own record, and a
23
+ --port that disagrees with it is refused rather than corrected.
24
+ --dry-run walk the whole flow and print what it WOULD do — change nothing
25
+ --help show this help and exit
26
+ --version print the installer version and exit
27
+
28
+ Env: OURS_ASSUME_YES=1 (accept defaults, no prompts) · OURS_INSTALL_DRY_RUN=1 ·
29
+ OURS_CHANNEL=nightly · OURS_BROKER_URL · OURS_NPM. Docs: https://ours.network`;
30
+
31
+ export const UNINSTALL_USAGE = `ours-uninstall — remove one ours daemon and what attaches to it.
32
+
33
+ ours-uninstall [--state-dir PATH] [--purge] [--dry-run] [--help] [--version]
34
+
35
+ Removes the boot service, stops the daemon, and removes the global packages —
36
+ but ONLY when no other daemon on this machine still needs them. A component
37
+ still pointing at this daemon stops the run before anything is removed, so a
38
+ run that refuses leaves the daemon whole rather than half-dismantled.
39
+
40
+ --state-dir which daemon to remove (default ~/.ours). A daemon IS its state
41
+ directory, so this is the only thing that names one.
42
+ --purge also delete the state directory itself. Never the default, never
43
+ done non-interactively, and it asks you to type the full path —
44
+ identity keys exist nowhere else and no peer can give them back.
45
+ --dry-run print what it WOULD remove and remove nothing
46
+ --help show this help and exit
47
+ --version print the version and exit`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/install",
3
- "version": "0.17.0",
3
+ "version": "0.18.0-nightly.1",
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",
package/uninstall.mjs CHANGED
@@ -1,198 +1,27 @@
1
1
  #!/usr/bin/env node
2
- // ours.network — the Node uninstaller (the real UX behind uninstall.sh's thin bootstrap). The
3
- // symmetric partner to install.mjs: same banner + colours + plain-language explanations, but for
4
- // REMOVAL. It removes ONLY what the ours installers created — sentinel-delimited config blocks,
5
- // the ours skill dirs, the npm globals, plus cleanup of any LEGACY connector env/log leftovers —
6
- // never unrelated files. The two destructive items (data dir, daemon) require an explicit typed
7
- // 'yes'. Everything uses safe, quoted, explicit paths — no wildcards on $HOME.
8
- //
9
- // Non-interactive env overrides (all optional):
10
- // OURS_UNINSTALL="hermes codex" which harness plugins to remove (space/comma; names or "all")
11
- // OURS_UNINSTALL_DATA=yes also remove the ours data dir (~/.ours) [destructive]
12
- // OURS_UNINSTALL_DAEMON=yes also stop + remove the ours-mcp daemon + its service
13
- // OURS_ASSUME_YES=1 accept defaults; skip the typed confirmations (implies no tty)
14
- // OURS_NPM="npm" npm binary to use
15
- // HERMES_DIR / CODEX_DIR / SKILLS_DIR / OURS_STATE_DIR path overrides
16
- import { spawnSync } from 'node:child_process';
17
- import { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';
18
- import { homedir } from 'node:os';
19
- import { join } from 'node:path';
20
- import { banner, heading, c, openTty, makeWriter, closeSync } from './lib/ui.mjs';
21
- import { askLine, checkboxSelect } from './lib/prompt.mjs';
22
- import { canonHarnesses } from './lib/logic.mjs';
23
2
 
24
- const NPM = process.env.OURS_NPM || 'npm';
25
- const HOME = homedir();
26
- const HERMES_DIR = process.env.HERMES_DIR || join(HOME, '.hermes');
27
- const CODEX_DIR = process.env.CODEX_DIR || join(HOME, '.codex');
28
- const SKILLS_DIR = process.env.SKILLS_DIR || join(HOME, '.agents', 'skills');
29
- const OURS_STATE_DIR = process.env.OURS_STATE_DIR || join(HOME, '.ours');
30
- const ASSUME_YES = !!process.env.OURS_ASSUME_YES;
31
-
32
- const say = (s) => process.stdout.write(`ours: ${s}\n`);
33
- const line = (s = '') => process.stdout.write(`${s}\n`);
34
-
35
- // Sentinel markers stamped by the installers' config helpers (must match them verbatim).
36
- const YAML_START = '# >>> ours.network plugin (managed block)';
37
- const YAML_END = '# <<< ours.network plugin';
38
- const MD_START = '<!-- >>> ours.network plugin (managed block) -->';
39
- const MD_END = '<!-- <<< ours.network plugin -->';
40
-
41
- const run = (bin, args) => spawnSync(bin, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
42
-
43
- // --- safe removal helpers ----------------------------------------------------------------------
44
- function rmDir(d) { if (d && existsSync(d)) { rmSync(d, { recursive: true, force: true }); say(` removed ${d}`); } }
45
- function rmFile(f) { if (f && existsSync(f)) { rmSync(f, { force: true }); say(` removed ${f}`); } }
46
-
47
- // stripBlock: delete the sentinel-delimited managed block (inclusive) if present; leave the file
48
- // untouched otherwise. Only ever removes OUR block.
49
- function stripBlock(file, start, end) {
50
- if (!existsSync(file)) return;
51
- const text = readFileSync(file, 'utf8');
52
- if (!text.includes(start)) return;
53
- const lines = text.split('\n');
54
- const out = [];
55
- let skip = false;
56
- for (const ln of lines) {
57
- if (!skip && ln.includes(start)) { skip = true; continue; }
58
- if (skip) { if (ln.includes(end)) skip = false; continue; }
59
- out.push(ln);
60
- }
61
- writeFileSync(file, out.join('\n'));
62
- say(` removed the ours managed block from ${file}`);
63
- }
64
-
65
- function npmRm(pkg) {
66
- const r = run(NPM, ['rm', '-g', pkg]);
67
- if (r.status === 0) say(` npm: removed ${pkg}`);
68
- else say(` npm: ${pkg} not installed globally (skipped)`);
69
- }
70
-
71
- // --- per-harness removal -----------------------------------------------------------------------
72
- function removeHermes() {
73
- line(heading('→ removing hermes plugin'));
74
- stripBlock(join(HERMES_DIR, 'config.yaml'), YAML_START, YAML_END);
75
- rmDir(join(HERMES_DIR, 'skills', 'communication', 'ours'));
76
- rmDir(join(HERMES_DIR, 'skills', 'communication', 'writing-agent-bios'));
77
- rmFile(join(HERMES_DIR, 'ours-connector.env'));
78
- rmFile(join(HERMES_DIR, 'ours-connector.log'));
79
- npmRm('@ours.network/hermes');
80
- }
81
-
82
- function removeCodex() {
83
- line(heading('→ removing codex plugin'));
84
- stripBlock(join(CODEX_DIR, 'config.toml'), YAML_START, YAML_END);
85
- stripBlock(join(CODEX_DIR, 'AGENTS.md'), MD_START, MD_END);
86
- rmDir(join(SKILLS_DIR, 'ours'));
87
- rmDir(join(SKILLS_DIR, 'writing-agent-bios'));
88
- npmRm('@ours.network/codex');
89
- }
90
-
91
- function removeClaudeCode() {
92
- line(heading('→ claude-code'));
93
- say("Claude Code's plugin lives in its in-app marketplace — the uninstaller can't remove it.");
94
- say('Inside your Claude Code session, run:');
95
- line('');
96
- line(' /plugin uninstall ours');
97
- line(' /plugin marketplace remove adapt-toolkit/ours-claude-marketplace');
98
- line('');
99
- }
100
-
101
- // Typed-'yes' gate for destructive removals. assume-yes bypasses; no-tty (without assume-yes)
102
- // declines rather than guesses.
103
- function confirmDestructive(write, fd, what) {
104
- if (ASSUME_YES) return true;
105
- if (fd == null) return false;
106
- return askLine(write, fd, ` Type 'yes' to permanently remove ${what}: `, 'no') === 'yes';
107
- }
108
-
109
- // ===============================================================================================
110
- function main() {
111
- const ttyFd = openTty();
112
- const write = makeWriter(ttyFd);
113
-
114
- line(banner());
115
- line(c.bold(' ours.network uninstaller'));
116
- say('Removes only what the ours installers created. Nothing is removed until you confirm.');
117
-
118
- // --- 1) choose what to remove ----------------------------------------------------------------
119
- let selected = [];
120
- let wantData = false;
121
- let wantDaemon = false;
122
-
123
- const envUninstall = process.env.OURS_UNINSTALL;
124
- if (envUninstall != null || process.env.OURS_UNINSTALL_DATA || process.env.OURS_UNINSTALL_DAEMON) {
125
- selected = canonHarnesses(envUninstall || '').names;
126
- wantData = process.env.OURS_UNINSTALL_DATA === 'yes';
127
- wantDaemon = process.env.OURS_UNINSTALL_DAEMON === 'yes';
128
- } else if (ttyFd != null) {
129
- line(heading('1) what to remove'));
130
- const picked = checkboxSelect(write, ttyFd, [
131
- { name: 'claude-code', label: 'Claude Code plugin (prints manual removal commands)' },
132
- { name: 'codex', label: 'Codex plugin' },
133
- { name: 'hermes', label: 'Hermes plugin' },
134
- { name: 'data', label: `ours data directory (${OURS_STATE_DIR} — identities + keys) [destructive]` },
135
- { name: 'daemon', label: 'ours-mcp daemon (stop + remove service + npm global) [destructive]' },
136
- ], { title: `Choose what to remove — ${c.bold('↑/↓')} move, ${c.bold('Space')} toggle, ${c.bold('Enter')} confirm (a=all, n=none)` });
137
- wantData = picked.includes('data');
138
- wantDaemon = picked.includes('daemon');
139
- selected = canonHarnesses(picked.filter((p) => p !== 'data' && p !== 'daemon').join(' ')).names;
140
- } else {
141
- say('no terminal and no OURS_UNINSTALL* set — nothing to do. Re-run with a terminal, or set');
142
- say(' OURS_UNINSTALL="hermes codex" [OURS_UNINSTALL_DATA=yes] [OURS_UNINSTALL_DAEMON=yes]');
143
- finish(ttyFd);
144
- return;
145
- }
146
-
147
- if (selected.length === 0 && !wantData && !wantDaemon) {
148
- line(heading('Done'));
149
- say('Nothing selected — nothing removed.');
150
- finish(ttyFd);
151
- return;
152
- }
153
-
154
- // --- 2) remove selected harness plugins ------------------------------------------------------
155
- const removed = [];
156
- let killWatcher = false;
157
- for (const h of selected) {
158
- if (h === 'hermes') { removeHermes(); killWatcher = true; removed.push('hermes'); }
159
- else if (h === 'codex') { removeCodex(); removed.push('codex'); }
160
- else if (h === 'claude-code') { removeClaudeCode(); removed.push('claude-code'); }
161
- }
162
- // Stop any reactivity watcher we started (only when a reactive harness was removed).
163
- if (killWatcher) { if (run('pkill', ['-f', 'connector-watch.sh']).status === 0) say('stopped the ours reactivity watcher(s).'); }
164
-
165
- // --- 3) data directory (destructive, guarded) ------------------------------------------------
166
- if (wantData) {
167
- line(heading('→ ours data directory'));
168
- say(`This holds your Ours IDENTITIES and private KEYS (${OURS_STATE_DIR}). Removing it is`);
169
- say('permanent and cannot be undone — you would lose those identities.');
170
- if (confirmDestructive(write, ttyFd, `the ours data directory (${OURS_STATE_DIR})`)) { rmDir(OURS_STATE_DIR); removed.push('data-dir'); }
171
- else say(' kept the data directory.');
172
- }
173
-
174
- // --- 4) daemon (destructive, guarded) --------------------------------------------------------
175
- if (wantDaemon) {
176
- line(heading('→ ours-mcp daemon'));
177
- if (confirmDestructive(write, ttyFd, 'the ours-mcp daemon (stops it + removes its service + npm global)')) {
178
- if (run('sh', ['-c', 'command -v ours-mcp']).status === 0) {
179
- run('ours-mcp', ['stop']); say(' stopped the daemon (if it was running).');
180
- run('ours-mcp', ['uninstall-service']); say(' removed the persistent service (if present).');
181
- }
182
- npmRm('@ours.network/mcp');
183
- removed.push('daemon');
184
- } else say(' kept the daemon.');
3
+ import { readFileSync } from 'node:fs';
4
+
5
+ import { realEffects } from './lib/effects.mjs';
6
+ import { runUninstall } from './lib/orchestrate-uninstall.mjs';
7
+ import { closeSync, makeWriter, openTty } from './lib/ui.mjs';
8
+
9
+ const version = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version;
10
+ const ttyFd = openTty();
11
+
12
+ try {
13
+ const code = await runUninstall(process.argv.slice(2), realEffects({
14
+ write: makeWriter(ttyFd),
15
+ ttyFd,
16
+ env: process.env,
17
+ version,
18
+ }));
19
+ process.exitCode = code;
20
+ } catch (error) {
21
+ process.stderr.write(`ours-uninstall: ${error instanceof Error ? error.message : String(error)}\n`);
22
+ process.exitCode = 1;
23
+ } finally {
24
+ if (ttyFd != null) {
25
+ try { closeSync(ttyFd); } catch { /* already closed */ }
185
26
  }
186
-
187
- // --- done ------------------------------------------------------------------------------------
188
- line(heading('Done'));
189
- if (removed.length) { say('removed:'); for (const r of removed) say(` ${c.green('✓')} ${r}`); }
190
- else say('nothing was removed.');
191
- if (!wantData) say(`Your Ours identities/keys in ${OURS_STATE_DIR} were left in place.`);
192
- say("Reload each harness to drop the ours tools (Hermes: '/reload-mcp'; Codex: next session).");
193
- finish(ttyFd);
194
27
  }
195
-
196
- function finish(ttyFd) { if (ttyFd != null) { try { closeSync(ttyFd); } catch { /* ignore */ } } }
197
-
198
- main();
package/uninstall.sh CHANGED
@@ -10,10 +10,19 @@
10
10
  # explanation of what will be removed. It removes ONLY what the ours installers created; the two
11
11
  # destructive items (data dir, daemon) require an explicit typed 'yes'.
12
12
  #
13
- # Non-interactive env overrides (all optional) — consumed by the Node uninstaller:
14
- # OURS_UNINSTALL="hermes codex" which harness plugins to remove (space/comma; names or "all")
15
- # OURS_UNINSTALL_DATA=yes also remove the ours data dir (~/.ours) [destructive]
16
- # OURS_UNINSTALL_DAEMON=yes also stop + remove the ours-mcp daemon + its service
13
+ # Non-interactive env overrides (all optional) — consumed by the Node uninstaller. Setting ANY of
14
+ # the OURS_UNINSTALL* variables makes the run non-interactive; setting none leaves it unchanged.
15
+ # Four documented variables describe things this uninstaller cannot do, and it REFUSES (exit 2,
16
+ # nothing removed) rather than ignoring them — see the table in README.md. Full list there.
17
+ # OURS_UNINSTALL="hermes codex" which harness plugins to remove (space/comma; names, "all", "none")
18
+ # OURS_UNINSTALL_DAEMON=yes also stop + remove the daemon + its service + the global packages.
19
+ # Unset (or any other value) removes ONLY the plugins named above
20
+ # and leaves the daemon running.
21
+ # OURS_UNINSTALL_TELEGRAM=detach detach the Telegram connector from this daemon (same for
22
+ # OURS_UNINSTALL_ROOMS=detach Rooms). Without it, a run a connector still points at refuses.
23
+ # OURS_UNINSTALL_DATA=yes REFUSED. State is never deleted unattended — it holds identity
24
+ # private keys. Use 'ours-uninstall --state-dir <dir> --purge'
25
+ # from a terminal and type the full path.
17
26
  # OURS_ASSUME_YES=1 accept defaults; skip the typed confirmations (implies no tty)
18
27
  # OURS_NPM="npm" npm binary to use
19
28
  # OURS_UNINSTALLER_MJS / OURS_INSTALLER_BASE run/fetch overrides (dev/testing)