@ours.network/install 0.11.0
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/LICENSE +98 -0
- package/README.md +136 -0
- package/install.mjs +604 -0
- package/install.sh +92 -0
- package/lib/logic.mjs +192 -0
- package/lib/prompt.mjs +139 -0
- package/lib/ui.mjs +126 -0
- package/package.json +33 -0
- package/uninstall.mjs +198 -0
- package/uninstall.sh +71 -0
package/uninstall.mjs
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
|
|
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.');
|
|
185
|
+
}
|
|
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
|
+
}
|
|
195
|
+
|
|
196
|
+
function finish(ttyFd) { if (ttyFd != null) { try { closeSync(ttyFd); } catch { /* ignore */ } } }
|
|
197
|
+
|
|
198
|
+
main();
|
package/uninstall.sh
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ours.network — uninstaller bootstrap. The symmetric partner to install.sh. Run from a clone:
|
|
3
|
+
#
|
|
4
|
+
# bash packages/installer/uninstall.sh
|
|
5
|
+
#
|
|
6
|
+
# (or piped: curl -fsSL https://raw.githubusercontent.com/adapt-toolkit/ours-mcp/main/packages/installer/uninstall.sh | bash)
|
|
7
|
+
#
|
|
8
|
+
# Like install.sh this is a THIN bootstrap: it checks Node.js is present (friendly per-OS guidance
|
|
9
|
+
# if not), then hands off to the Node uninstaller (uninstall.mjs) — banner + colours + a clear
|
|
10
|
+
# explanation of what will be removed. It removes ONLY what the ours installers created; the two
|
|
11
|
+
# destructive items (data dir, daemon) require an explicit typed 'yes'.
|
|
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
|
|
17
|
+
# OURS_ASSUME_YES=1 accept defaults; skip the typed confirmations (implies no tty)
|
|
18
|
+
# OURS_NPM="npm" npm binary to use
|
|
19
|
+
# OURS_UNINSTALLER_MJS / OURS_INSTALLER_BASE run/fetch overrides (dev/testing)
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
say(){ printf 'ours: %s\n' "$1"; }
|
|
23
|
+
|
|
24
|
+
# --- 1) Node.js check + friendly guidance ------------------------------------------------------
|
|
25
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
26
|
+
os="$(uname -s 2>/dev/null || echo unknown)"
|
|
27
|
+
printf '\n'
|
|
28
|
+
say "ours needs Node.js (version 20 or newer) to run its uninstaller — it isn't installed."
|
|
29
|
+
case "$os" in
|
|
30
|
+
Darwin) say " • macOS (Homebrew): brew install node (or nvm: https://github.com/nvm-sh/nvm)";;
|
|
31
|
+
Linux) say " • Linux: https://github.com/nodesource/distributions (or nvm)";;
|
|
32
|
+
*) say " • Windows/WSL: install Node.js in WSL, or from https://nodejs.org";;
|
|
33
|
+
esac
|
|
34
|
+
say " • Any OS: https://nodejs.org — then re-run this command."
|
|
35
|
+
printf '\n'
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# --- 2) locate the Node uninstaller ------------------------------------------------------------
|
|
40
|
+
MJS=""
|
|
41
|
+
if [ -n "${OURS_UNINSTALLER_MJS:-}" ] && [ -f "${OURS_UNINSTALLER_MJS}" ]; then
|
|
42
|
+
MJS="${OURS_UNINSTALLER_MJS}"
|
|
43
|
+
else
|
|
44
|
+
SELF="${BASH_SOURCE[0]:-$0}"
|
|
45
|
+
DIR="$(cd "$(dirname "$SELF")" 2>/dev/null && pwd || true)"
|
|
46
|
+
if [ -n "$DIR" ] && [ -f "$DIR/uninstall.mjs" ]; then MJS="$DIR/uninstall.mjs"; fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
CLEANUP=""
|
|
50
|
+
if [ -z "$MJS" ]; then
|
|
51
|
+
BASE="${OURS_INSTALLER_BASE:-https://raw.githubusercontent.com/adapt-toolkit/ours-mcp/main/packages/installer}"
|
|
52
|
+
fetch(){ if command -v curl >/dev/null 2>&1; then curl -fsSL "$1"; else wget -qO- "$1"; fi; }
|
|
53
|
+
TMP="$(mktemp -d)"; CLEANUP="$TMP"
|
|
54
|
+
mkdir -p "$TMP/lib"
|
|
55
|
+
say "fetching the ours uninstaller…"
|
|
56
|
+
for f in uninstall.mjs lib/ui.mjs lib/logic.mjs lib/prompt.mjs; do
|
|
57
|
+
if ! fetch "$BASE/$f" > "$TMP/$f" 2>/dev/null; then
|
|
58
|
+
say "could not download the uninstaller ($BASE/$f). Check your connection and retry."
|
|
59
|
+
rm -rf "${TMP:?}"; exit 1
|
|
60
|
+
fi
|
|
61
|
+
done
|
|
62
|
+
MJS="$TMP/uninstall.mjs"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# --- 3) run the Node uninstaller ---------------------------------------------------------------
|
|
66
|
+
set +e
|
|
67
|
+
node "$MJS"
|
|
68
|
+
rc=$?
|
|
69
|
+
set -e
|
|
70
|
+
[ -n "$CLEANUP" ] && rm -rf "${CLEANUP:?}"
|
|
71
|
+
exit "$rc"
|