@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/README.md +37 -315
- package/install.mjs +22 -1181
- package/lib/components.mjs +18 -9
- package/lib/detect.mjs +8 -21
- package/lib/effects.mjs +32 -14
- package/lib/extras.mjs +5 -85
- package/lib/journal.mjs +46 -1
- package/lib/logic.mjs +13 -85
- package/lib/orchestrate-uninstall.mjs +105 -19
- package/lib/orchestrate.mjs +134 -118
- package/lib/plan.mjs +40 -8
- package/lib/target.mjs +38 -5
- package/lib/uninstall.mjs +285 -16
- package/package.json +1 -1
- package/uninstall.mjs +22 -225
- package/uninstall.sh +13 -4
- package/lib/nightly-install.mjs +0 -739
- package/lib/nightly-uninstall.mjs +0 -396
- package/lib/profiles.mjs +0 -524
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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)
|