@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/README.md +37 -122
- package/install.mjs +23 -790
- package/lib/components.mjs +360 -0
- package/lib/detect.mjs +169 -0
- package/lib/effects.mjs +349 -0
- package/lib/extras.mjs +320 -0
- package/lib/journal.mjs +158 -0
- package/lib/logic.mjs +351 -25
- package/lib/orchestrate-uninstall.mjs +379 -0
- package/lib/orchestrate.mjs +1051 -0
- package/lib/plan.mjs +270 -0
- package/lib/rerun.mjs +119 -0
- package/lib/target.mjs +390 -0
- package/lib/uninstall.mjs +736 -0
- package/lib/usage.mjs +47 -0
- package/package.json +1 -1
- package/uninstall.mjs +23 -194
- package/uninstall.sh +13 -4
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.
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
#
|
|
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)
|