@ours.network/install 0.17.0-nightly.9 → 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/uninstall.mjs CHANGED
@@ -1,230 +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, resolveChannel } from './lib/logic.mjs';
23
- import { realEffects } from './lib/effects.mjs';
24
- import { runUninstall as runUninstallV3 } from './lib/orchestrate-uninstall.mjs';
25
-
26
- const NPM = process.env.OURS_NPM || 'npm';
27
- const HOME = homedir();
28
- const HERMES_DIR = process.env.HERMES_DIR || join(HOME, '.hermes');
29
- const CODEX_DIR = process.env.CODEX_DIR || join(HOME, '.codex');
30
- const SKILLS_DIR = process.env.SKILLS_DIR || join(HOME, '.agents', 'skills');
31
- const OURS_STATE_DIR = process.env.OURS_STATE_DIR || join(HOME, '.ours');
32
- const ASSUME_YES = !!process.env.OURS_ASSUME_YES;
33
- const packageVersion = (() => {
34
- try { return JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version || ''; }
35
- catch { return ''; }
36
- })();
37
- const CHANNEL = resolveChannel(process.env.OURS_CHANNEL || process.env.OURS_INSTALL_CHANNEL, packageVersion);
38
-
39
- const say = (s) => process.stdout.write(`ours: ${s}\n`);
40
- const line = (s = '') => process.stdout.write(`${s}\n`);
41
-
42
- // Sentinel markers stamped by the installers' config helpers (must match them verbatim).
43
- const YAML_START = '# >>> ours.network plugin (managed block)';
44
- const YAML_END = '# <<< ours.network plugin';
45
- const MD_START = '<!-- >>> ours.network plugin (managed block) -->';
46
- const MD_END = '<!-- <<< ours.network plugin -->';
47
-
48
- const run = (bin, args, { env } = {}) => spawnSync(bin, args, {
49
- encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
50
- env: env ? { ...process.env, ...env } : process.env,
51
- });
52
-
53
- // --- safe removal helpers ----------------------------------------------------------------------
54
- function rmDir(d) { if (d && existsSync(d)) { rmSync(d, { recursive: true, force: true }); say(` removed ${d}`); } }
55
- function rmFile(f) { if (f && existsSync(f)) { rmSync(f, { force: true }); say(` removed ${f}`); } }
56
-
57
- // stripBlock: delete the sentinel-delimited managed block (inclusive) if present; leave the file
58
- // untouched otherwise. Only ever removes OUR block.
59
- function stripBlock(file, start, end) {
60
- if (!existsSync(file)) return;
61
- const text = readFileSync(file, 'utf8');
62
- if (!text.includes(start)) return;
63
- const lines = text.split('\n');
64
- const out = [];
65
- let skip = false;
66
- for (const ln of lines) {
67
- if (!skip && ln.includes(start)) { skip = true; continue; }
68
- if (skip) { if (ln.includes(end)) skip = false; continue; }
69
- out.push(ln);
70
- }
71
- writeFileSync(file, out.join('\n'));
72
- say(` removed the ours managed block from ${file}`);
73
- }
74
-
75
- function npmRm(pkg) {
76
- const r = run(NPM, ['rm', '-g', pkg]);
77
- if (r.status === 0) say(` npm: removed ${pkg}`);
78
- else say(` npm: ${pkg} not installed globally (skipped)`);
79
- }
80
-
81
- // --- per-harness removal -----------------------------------------------------------------------
82
- function removeHermes() {
83
- line(heading('→ removing hermes plugin'));
84
- stripBlock(join(HERMES_DIR, 'config.yaml'), YAML_START, YAML_END);
85
- rmDir(join(HERMES_DIR, 'skills', 'communication', 'ours'));
86
- rmDir(join(HERMES_DIR, 'skills', 'communication', 'writing-agent-bios'));
87
- rmFile(join(HERMES_DIR, 'ours-connector.env'));
88
- rmFile(join(HERMES_DIR, 'ours-connector.log'));
89
- npmRm('@ours.network/hermes');
90
- }
91
-
92
- function removeCodex() {
93
- line(heading('→ removing codex plugin'));
94
- stripBlock(join(CODEX_DIR, 'config.toml'), YAML_START, YAML_END);
95
- stripBlock(join(CODEX_DIR, 'AGENTS.md'), MD_START, MD_END);
96
- rmDir(join(SKILLS_DIR, 'ours'));
97
- rmDir(join(SKILLS_DIR, 'writing-agent-bios'));
98
- npmRm('@ours.network/codex');
99
- }
100
-
101
- function removeClaudeCode() {
102
- line(heading('→ claude-code'));
103
- say("Claude Code's plugin lives in its in-app marketplace — the uninstaller can't remove it.");
104
- say('Inside your Claude Code session, run:');
105
- line('');
106
- line(' /plugin uninstall ours');
107
- line(' /plugin marketplace remove adapt-toolkit/ours-claude-marketplace');
108
- line('');
109
- }
110
-
111
- // Typed-'yes' gate for destructive removals. assume-yes bypasses; no-tty (without assume-yes)
112
- // declines rather than guesses.
113
- function confirmDestructive(write, fd, what) {
114
- if (ASSUME_YES) return true;
115
- if (fd == null) return false;
116
- return askLine(write, fd, ` Type 'yes' to permanently remove ${what}: `, 'no') === 'yes';
117
- }
118
2
 
119
- // ===============================================================================================
120
- async function main() {
121
- const ttyFd = openTty();
122
- const write = makeWriter(ttyFd);
3
+ import { readFileSync } from 'node:fs';
123
4
 
124
- // ─── THE CHANNEL FORK ───────────────────────────────────────────────────────
125
- // The partner to install.mjs's, and it has to move in the SAME commit. v3
126
- // installing while this body uninstalls would run the old flow against state it
127
- // no longer understands — the kind of half-state that only surfaces when someone
128
- // tries to undo something.
129
- //
130
- // Before the banner, so the screen the operator sees is the one belonging to the
131
- // flow that is about to run.
132
- if (CHANNEL === 'nightly') {
133
- const code = await runUninstallV3(process.argv.slice(2), realEffects({
134
- write, ttyFd, env: process.env, version: packageVersion,
135
- }));
136
- finish(ttyFd);
137
- process.exit(code);
138
- }
139
-
140
- line(banner());
141
- line(c.bold(' ours.network uninstaller'));
142
- say('Removes only what the ours installers created. Nothing is removed until you confirm.');
143
-
144
- // --- 1) choose what to remove ----------------------------------------------------------------
145
- let selected = [];
146
- let wantData = false;
147
- let wantDaemon = false;
148
-
149
- const envUninstall = process.env.OURS_UNINSTALL;
150
- if (envUninstall != null || process.env.OURS_UNINSTALL_DATA || process.env.OURS_UNINSTALL_DAEMON) {
151
- selected = canonHarnesses(envUninstall || '').names;
152
- wantData = process.env.OURS_UNINSTALL_DATA === 'yes';
153
- wantDaemon = process.env.OURS_UNINSTALL_DAEMON === 'yes';
154
- } else if (ttyFd != null) {
155
- line(heading('1) what to remove'));
156
- const picked = checkboxSelect(write, ttyFd, [
157
- { name: 'claude-code', label: 'Claude Code plugin (prints manual removal commands)' },
158
- { name: 'codex', label: 'Codex plugin' },
159
- { name: 'hermes', label: 'Hermes plugin' },
160
- { name: 'data', label: `ours data directory (${OURS_STATE_DIR} — identities + keys) [destructive]` },
161
- { name: 'daemon', label: 'ours-mcp daemon (stop + remove service + npm global) [destructive]' },
162
- ], { title: `Choose what to remove — ${c.bold('↑/↓')} move, ${c.bold('Space')} toggle, ${c.bold('Enter')} confirm (a=all, n=none)` });
163
- wantData = picked.includes('data');
164
- wantDaemon = picked.includes('daemon');
165
- selected = canonHarnesses(picked.filter((p) => p !== 'data' && p !== 'daemon').join(' ')).names;
166
- } else {
167
- say('no terminal and no OURS_UNINSTALL* set — nothing to do. Re-run with a terminal, or set');
168
- say(' OURS_UNINSTALL="hermes codex" [OURS_UNINSTALL_DATA=yes] [OURS_UNINSTALL_DAEMON=yes]');
169
- finish(ttyFd);
170
- return;
171
- }
172
-
173
- if (selected.length === 0 && !wantData && !wantDaemon) {
174
- line(heading('Done'));
175
- say('Nothing selected — nothing removed.');
176
- finish(ttyFd);
177
- return;
178
- }
179
-
180
- // --- 2) remove selected harness plugins ------------------------------------------------------
181
- const removed = [];
182
- let killWatcher = false;
183
- for (const h of selected) {
184
- if (h === 'hermes') { removeHermes(); killWatcher = true; removed.push('hermes'); }
185
- else if (h === 'codex') { removeCodex(); removed.push('codex'); }
186
- else if (h === 'claude-code') { removeClaudeCode(); removed.push('claude-code'); }
187
- }
188
- // Stop any reactivity watcher we started (only when a reactive harness was removed).
189
- if (killWatcher) { if (run('pkill', ['-f', 'connector-watch.sh']).status === 0) say('stopped the ours reactivity watcher(s).'); }
190
-
191
- // --- 3) data directory (destructive, guarded) ------------------------------------------------
192
- if (wantData) {
193
- line(heading('→ ours data directory'));
194
- say(`This holds your Ours IDENTITIES and private KEYS (${OURS_STATE_DIR}). Removing it is`);
195
- say('permanent and cannot be undone — you would lose those identities.');
196
- if (confirmDestructive(write, ttyFd, `the ours data directory (${OURS_STATE_DIR})`)) { rmDir(OURS_STATE_DIR); removed.push('data-dir'); }
197
- else say(' kept the data directory.');
198
- }
199
-
200
- // --- 4) daemon (destructive, guarded) --------------------------------------------------------
201
- if (wantDaemon) {
202
- line(heading('→ ours-mcp daemon'));
203
- if (confirmDestructive(write, ttyFd, 'the ours-mcp daemon (stops it + removes its service + npm global)')) {
204
- if (run('sh', ['-c', 'command -v ours-mcp']).status === 0) {
205
- run('ours-mcp', ['stop']); say(' stopped the daemon (if it was running).');
206
- run('ours-mcp', ['uninstall-service']); say(' removed the persistent service (if present).');
207
- }
208
- npmRm('@ours.network/mcp');
209
- removed.push('daemon');
210
- } else say(' kept the daemon.');
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 */ }
211
26
  }
212
-
213
- // --- done ------------------------------------------------------------------------------------
214
- line(heading('Done'));
215
- if (removed.length) { say('removed:'); for (const r of removed) say(` ${c.green('✓')} ${r}`); }
216
- else say('nothing was removed.');
217
- if (!wantData) say(`Your Ours identities/keys in ${OURS_STATE_DIR} were left in place.`);
218
- say("Reload each harness to drop the ours tools (Hermes: '/reload-mcp'; Codex: next session).");
219
- finish(ttyFd);
220
27
  }
221
-
222
- function finish(ttyFd) { if (ttyFd != null) { try { closeSync(ttyFd); } catch { /* ignore */ } } }
223
-
224
- // `main` became async when the nightly fork moved in: the v3 uninstaller is async
225
- // all the way down. An unhandled rejection here would exit 0 with no output, so the
226
- // failure is caught and reported like the installer's.
227
- main().catch((error) => {
228
- say(`unexpected error: ${String(error)}`);
229
- process.exitCode = 1;
230
- });
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)