privateer-agent 0.5.0 → 0.6.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/bin/privateer-launch.mjs +218 -0
- package/bin/privateer-tui +8 -139
- package/bin/privateer.cmd +6 -0
- package/extensions/privateer-brand.ts +117 -72
- package/extensions/privateer-gate.ts +6 -3
- package/extensions/privateer-models.ts +429 -0
- package/extensions/privateer-posture.ts +11 -14
- package/package.json +6 -3
- package/patches/@earendil-works+pi-coding-agent+0.80.3.patch +23 -0
- package/src/cli/chat.ts +15 -2
- package/src/providers/account.ts +82 -12
- package/src/providers/defaultModel.ts +31 -6
- package/src/ui/palette.ts +135 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cross-platform Privateer launcher — the single source of launch logic for every
|
|
3
|
+
// platform (macOS, Linux, Windows). `bin/privateer-tui` (unix) and the Windows
|
|
4
|
+
// `privateer.cmd` are thin shims that just pick a Node and run THIS file.
|
|
5
|
+
//
|
|
6
|
+
// It boots Pi's full interactive TUI with the Privateer moat + tool packs. The moat
|
|
7
|
+
// is installed as re-export SHIMS in the agent dir's extensions/, so BOTH this TUI
|
|
8
|
+
// and any subagents it spawns (child processes reading the same agent dir) load the
|
|
9
|
+
// identical set — including our permission gate. One source of truth via discovery
|
|
10
|
+
// (no `-e`, which would double-load vs discovery). Runs in the current directory;
|
|
11
|
+
// model via PRIVATEER_MODEL=provider/id.
|
|
12
|
+
//
|
|
13
|
+
// Ported from the original bash launcher; behaviour is intended to match exactly.
|
|
14
|
+
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url)); // bin/
|
|
22
|
+
const REPO = path.resolve(HERE, "..");
|
|
23
|
+
const isWin = process.platform === "win32";
|
|
24
|
+
|
|
25
|
+
const PRIVATEER_HOME = process.env.PRIVATEER_HOME || path.join(os.homedir(), ".privateer");
|
|
26
|
+
const ENV_FILE = path.join(REPO, ".env"); // dev-only; a real install has none
|
|
27
|
+
|
|
28
|
+
// --- bundle detection ------------------------------------------------------
|
|
29
|
+
// A self-contained bundle ships its own pinned Node at "$REPO/node[.exe]" plus a
|
|
30
|
+
// BUNDLE_INFO.json marker (built by scripts/build-bundle.mjs). When present we use
|
|
31
|
+
// that runtime and never touch system node/npm. Putting the bundle dir on PATH also
|
|
32
|
+
// lets any child that boots via `#!/usr/bin/env node` (Pi's cli.js, the subagent
|
|
33
|
+
// wrapper) resolve the bundled node.
|
|
34
|
+
const bundledNode = path.join(REPO, isWin ? "node.exe" : "node");
|
|
35
|
+
const BUNDLED = fs.existsSync(bundledNode) && fs.existsSync(path.join(REPO, "BUNDLE_INFO.json"));
|
|
36
|
+
|
|
37
|
+
// The node used for child processes. When bundled, the bundled runtime; otherwise the
|
|
38
|
+
// very node already running this script (a suitable >=22, since we booted under it).
|
|
39
|
+
const NODE_BIN = BUNDLED ? bundledNode : process.execPath;
|
|
40
|
+
// Make sure the chosen node's directory is on PATH for shebang-spawned grandchildren.
|
|
41
|
+
process.env.PATH = path.dirname(NODE_BIN) + path.delimiter + (process.env.PATH || "");
|
|
42
|
+
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
const sub = args[0];
|
|
45
|
+
|
|
46
|
+
// `privateer --version` — report OUR version, not Pi's. Left to Pi's cli.js it would
|
|
47
|
+
// print the pi-coding-agent version (e.g. 0.80.3); intercept so users see the
|
|
48
|
+
// Privateer release they installed. (The startup banner already shows this version.)
|
|
49
|
+
if (sub === "--version" || sub === "-V") {
|
|
50
|
+
const ver = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).version; } catch { return null; } };
|
|
51
|
+
const pv = ver(path.join(REPO, "package.json")) || "unknown";
|
|
52
|
+
const pi = ver(path.join(REPO, "node_modules", "@earendil-works", "pi-coding-agent", "package.json"));
|
|
53
|
+
console.log(`privateer ${pv}${pi ? ` (pi ${pi})` : ""}`);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Faithfully propagate a child's exit/signal, mirroring bash `exec`.
|
|
58
|
+
function runToCompletion(cmd, cmdArgs, opts = {}) {
|
|
59
|
+
const child = spawn(cmd, cmdArgs, { stdio: "inherit", env: process.env, ...opts });
|
|
60
|
+
child.on("exit", (code, signal) => {
|
|
61
|
+
if (signal) process.kill(process.pid, signal);
|
|
62
|
+
else process.exit(code ?? 0);
|
|
63
|
+
});
|
|
64
|
+
child.on("error", (e) => {
|
|
65
|
+
console.error(`privateer: failed to launch — ${e.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- `privateer update` ----------------------------------------------------
|
|
71
|
+
// Fetch the latest release and exit. Bundle installs re-run the download+extract
|
|
72
|
+
// installer; npm installs update the global package.
|
|
73
|
+
if (sub === "update") {
|
|
74
|
+
if (BUNDLED) {
|
|
75
|
+
console.log("Updating Privateer to the latest release…");
|
|
76
|
+
if (isWin) {
|
|
77
|
+
runToCompletion("powershell", ["-NoProfile", "-Command", "irm https://privateer.pro/install.ps1 | iex"]);
|
|
78
|
+
} else {
|
|
79
|
+
runToCompletion("sh", ["-c", "curl -fsSL https://privateer.pro/install.sh | sh"]);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
console.log("Updating privateer-agent to the latest release…");
|
|
83
|
+
runToCompletion(isWin ? "npm.cmd" : "npm", ["install", "-g", "privateer-agent@latest"]);
|
|
84
|
+
}
|
|
85
|
+
// runToCompletion exits via the child's exit handler.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// --- `privateer daemon [run|install|uninstall|status]` ---------------------
|
|
89
|
+
// The resident background daemon (routines + app-driven headless task spawns). Boots
|
|
90
|
+
// straight into src/daemon via bin/privateer-daemon.mjs — no moat-shim install (the
|
|
91
|
+
// daemon loads the moat as in-code factories, not interactive extensions).
|
|
92
|
+
else if (sub === "daemon") {
|
|
93
|
+
const nodeArgs = fs.existsSync(ENV_FILE) ? [`--env-file=${ENV_FILE}`] : [];
|
|
94
|
+
runToCompletion(NODE_BIN, [...nodeArgs, path.join(REPO, "bin", "privateer-daemon.mjs"), ...args.slice(1)]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- normal launch: install the moat, then exec Pi's TUI -------------------
|
|
98
|
+
else {
|
|
99
|
+
const AGENT_DIR = path.join(PRIVATEER_HOME, "agent");
|
|
100
|
+
const EXT_DIR = path.join(AGENT_DIR, "extensions");
|
|
101
|
+
fs.mkdirSync(EXT_DIR, { recursive: true });
|
|
102
|
+
|
|
103
|
+
// Install/refresh the moat + tool-pack shims. Each shim re-exports its target by
|
|
104
|
+
// ABSOLUTE path (as a file:// URL, portable across OSes) so the target's own
|
|
105
|
+
// relative imports resolve from the repo. We remove any shim we previously managed
|
|
106
|
+
// first, so a dropped package can't linger and reload.
|
|
107
|
+
const MANAGED = [
|
|
108
|
+
"privateer-brand", "privateer-context", "privateer-gate", "privateer-account",
|
|
109
|
+
"privateer-models", "privateer-posture", "privateer-tools", "privateer-privacy",
|
|
110
|
+
"pi-privacy", "pi-web-access", "rpiv-web-tools", "pi-mcp-adapter", "pi-hypa", "pi-subagents",
|
|
111
|
+
];
|
|
112
|
+
for (const name of MANAGED) fs.rmSync(path.join(EXT_DIR, `${name}.ts`), { force: true });
|
|
113
|
+
|
|
114
|
+
const ext = (...p) => path.join(REPO, "extensions", ...p);
|
|
115
|
+
const dep = (...p) => path.join(REPO, "node_modules", ...p);
|
|
116
|
+
const shim = (name, target) =>
|
|
117
|
+
fs.writeFileSync(path.join(EXT_DIR, `${name}.ts`), `export { default } from ${JSON.stringify(pathToFileURL(target).href)};\n`);
|
|
118
|
+
|
|
119
|
+
shim("privateer-brand", ext("privateer-brand.ts")); // banner, ⚓ badge, /signin /signout
|
|
120
|
+
shim("privateer-context", ext("privateer-context.ts")); // PRIVATEER.md context + /init
|
|
121
|
+
shim("privateer-gate", ext("privateer-gate.ts")); // the permission gate (moat)
|
|
122
|
+
shim("privateer-account", ext("privateer-account.ts"));
|
|
123
|
+
shim("privateer-models", ext("privateer-models.ts")); // /models picker w/ privacy shields
|
|
124
|
+
shim("privateer-posture", ext("privateer-posture.ts"));
|
|
125
|
+
shim("privateer-tools", ext("privateer-tools.ts"));
|
|
126
|
+
shim("privateer-privacy", ext("privateer-privacy.ts")); // pi-privacy + account tier resolver
|
|
127
|
+
shim("rpiv-web-tools", dep("@juicesharp", "rpiv-web-tools", "index.ts")); // private web tools
|
|
128
|
+
shim("pi-mcp-adapter", dep("pi-mcp-adapter", "index.ts"));
|
|
129
|
+
shim("pi-hypa", dep("@hypabolic", "pi-hypa", "extensions", "index.ts"));
|
|
130
|
+
shim("pi-subagents", dep("pi-subagents", "src", "extension", "index.ts"));
|
|
131
|
+
|
|
132
|
+
const CLI = dep("@earendil-works", "pi-coding-agent", "dist", "cli.js");
|
|
133
|
+
process.env.PI_CODING_AGENT_DIR = AGENT_DIR;
|
|
134
|
+
// The binary pi-subagents spawns for each child. Point it at OUR cli.js so the child
|
|
135
|
+
// reads this same PI_CODING_AGENT_DIR and DISCOVERS the moat shims (gated + private,
|
|
136
|
+
// no -e injection). Set only when unset so a power user can override.
|
|
137
|
+
if (!process.env.PI_SUBAGENT_PI_BINARY) process.env.PI_SUBAGENT_PI_BINARY = CLI;
|
|
138
|
+
// Suppress Pi's upstream update banner (our banner is the startup surface). Disables
|
|
139
|
+
// ONLY the version fetch — fd/rg can still download on first run.
|
|
140
|
+
if (!process.env.PI_SKIP_VERSION_CHECK) process.env.PI_SKIP_VERSION_CHECK = "1";
|
|
141
|
+
|
|
142
|
+
// Quiet Pi's built-in startup chatter so our banner is the only greeting. Each key is
|
|
143
|
+
// set only when unset, so a user's own settings.json toggle still wins.
|
|
144
|
+
try {
|
|
145
|
+
const sp = path.join(AGENT_DIR, "settings.json");
|
|
146
|
+
let s = {};
|
|
147
|
+
try { s = JSON.parse(fs.readFileSync(sp, "utf8")); } catch { /* new/absent */ }
|
|
148
|
+
let m = false;
|
|
149
|
+
if (s.quietStartup === undefined) { s.quietStartup = true; m = true; }
|
|
150
|
+
if (s.collapseChangelog === undefined) { s.collapseChangelog = true; m = true; }
|
|
151
|
+
if (s.lastChangelogVersion === undefined) { s.lastChangelogVersion = "9999.0.0"; m = true; }
|
|
152
|
+
if (m) fs.writeFileSync(sp, JSON.stringify(s, null, 2) + "\n");
|
|
153
|
+
} catch { /* best-effort */ }
|
|
154
|
+
|
|
155
|
+
// Passive update check: refresh the cached "latest version" at most ~daily, in the
|
|
156
|
+
// background so it never blocks or breaks launch (offline-safe). The banner reads
|
|
157
|
+
// this cache and shows a "↑ vX available · run privateer update" notice. We never
|
|
158
|
+
// auto-install. Fire-and-forget: the event loop stays alive while the TUI child runs.
|
|
159
|
+
refreshUpdateCache();
|
|
160
|
+
|
|
161
|
+
// Default model. Explicit PRIVATEER_MODEL wins; else Tinfoil GLM 5.2 (client-attested
|
|
162
|
+
// TEE, strongest tier) when a Tinfoil key is present; else the signed-in account's
|
|
163
|
+
// NEAR channel; else a cheap OpenRouter fallback.
|
|
164
|
+
const CRED = path.join(PRIVATEER_HOME, "credentials.json");
|
|
165
|
+
const MODEL = process.env.PRIVATEER_MODEL
|
|
166
|
+
? process.env.PRIVATEER_MODEL
|
|
167
|
+
: haveTinfoilKey()
|
|
168
|
+
? "tinfoil/glm-5-2"
|
|
169
|
+
: fs.existsSync(CRED)
|
|
170
|
+
? "privateer/near/zai-org/GLM-5.1-FP8"
|
|
171
|
+
: "openrouter/openai/gpt-4o-mini";
|
|
172
|
+
|
|
173
|
+
// Dev convenience: load provider keys from the repo's .env if present.
|
|
174
|
+
const nodeArgs = fs.existsSync(ENV_FILE) ? [`--env-file=${ENV_FILE}`] : [];
|
|
175
|
+
runToCompletion(NODE_BIN, [...nodeArgs, CLI, "--model", MODEL, ...args]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --- helpers ---------------------------------------------------------------
|
|
179
|
+
function haveTinfoilKey() {
|
|
180
|
+
if (process.env.TINFOIL_API_KEY) return true;
|
|
181
|
+
try { return /^TINFOIL_API_KEY=.+/m.test(fs.readFileSync(ENV_FILE, "utf8")); }
|
|
182
|
+
catch { return false; }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function refreshUpdateCache() {
|
|
186
|
+
const cache = path.join(PRIVATEER_HOME, "update-check.json");
|
|
187
|
+
try {
|
|
188
|
+
const stat = fs.statSync(cache);
|
|
189
|
+
if (Date.now() - stat.mtimeMs < 24 * 60 * 60 * 1000) return; // fresh (<1 day)
|
|
190
|
+
} catch { /* missing — refresh */ }
|
|
191
|
+
|
|
192
|
+
const write = (latest) => {
|
|
193
|
+
if (!/^[0-9]/.test(latest || "")) return;
|
|
194
|
+
try {
|
|
195
|
+
fs.mkdirSync(PRIVATEER_HOME, { recursive: true });
|
|
196
|
+
fs.writeFileSync(cache, JSON.stringify({ latest }) + "\n");
|
|
197
|
+
} catch { /* best-effort */ }
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (BUNDLED) {
|
|
201
|
+
// No npm in a bundle — read the latest tag off GitHub Releases.
|
|
202
|
+
fetch("https://api.github.com/repos/privateer-agent/privateer-agent/releases/latest", {
|
|
203
|
+
headers: { "User-Agent": "privateer-cli", Accept: "application/vnd.github+json" },
|
|
204
|
+
})
|
|
205
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
206
|
+
.then((j) => write(String(j?.tag_name || "").replace(/^v/, "")))
|
|
207
|
+
.catch(() => { /* offline — keep stale cache */ });
|
|
208
|
+
} else {
|
|
209
|
+
const p = spawn(isWin ? "npm.cmd" : "npm", ["view", "privateer-agent", "version"], {
|
|
210
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
211
|
+
});
|
|
212
|
+
let out = "";
|
|
213
|
+
p.stdout.on("data", (d) => { out += d; });
|
|
214
|
+
p.on("close", () => write(out.trim()));
|
|
215
|
+
p.on("error", () => { /* no npm — ignore */ });
|
|
216
|
+
p.unref();
|
|
217
|
+
}
|
|
218
|
+
}
|
package/bin/privateer-tui
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# gated too (the gate runs bypass-within-restricted-tools headlessly; danger.ts still
|
|
7
|
-
# blocks destructive shell). One source of truth via discovery — no -e (which would
|
|
8
|
-
# double-load vs discovery).
|
|
9
|
-
#
|
|
10
|
-
# Runs in the current directory. Model via PRIVATEER_MODEL=provider/id.
|
|
2
|
+
# Thin unix shim: pick a Node, then hand off to the cross-platform launcher
|
|
3
|
+
# (bin/privateer-launch.mjs), which is the single source of launch logic shared with
|
|
4
|
+
# the Windows privateer.cmd. All the real work — moat-shim install, model pick,
|
|
5
|
+
# update/daemon dispatch, exec into Pi's TUI — lives in the launcher.
|
|
11
6
|
set -euo pipefail
|
|
12
|
-
# Resolve through symlinks so REPO points at the real repo
|
|
7
|
+
# Resolve through symlinks so REPO points at the real bundle/repo dir.
|
|
13
8
|
SOURCE="${BASH_SOURCE[0]}"
|
|
14
9
|
while [ -h "$SOURCE" ]; do
|
|
15
10
|
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
@@ -18,16 +13,9 @@ while [ -h "$SOURCE" ]; do
|
|
|
18
13
|
done
|
|
19
14
|
REPO="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
20
15
|
|
|
21
|
-
#
|
|
22
|
-
# launcher (not the TUI) so it works even when a broken install won't boot. npm rewrites
|
|
23
|
-
# the global bin in place; replacing it while this process runs is safe on unix.
|
|
24
|
-
if [ "${1:-}" = "update" ]; then
|
|
25
|
-
echo "Updating privateer-agent to the latest release…"
|
|
26
|
-
npm install -g privateer-agent@latest
|
|
27
|
-
exit $?
|
|
28
|
-
fi
|
|
29
|
-
|
|
16
|
+
# Bundled runtime wins; else a system node >=22; else an nvm node; else PATH `node`.
|
|
30
17
|
pick_node() {
|
|
18
|
+
if [ -x "$REPO/node" ] && [ -f "$REPO/BUNDLE_INFO.json" ]; then echo "$REPO/node"; return; fi
|
|
31
19
|
if command -v node >/dev/null 2>&1 \
|
|
32
20
|
&& node -e 'process.exit((+process.versions.node.split(".")[0]) >= 22 ? 0 : 1)' 2>/dev/null; then
|
|
33
21
|
command -v node; return
|
|
@@ -38,123 +26,4 @@ pick_node() {
|
|
|
38
26
|
echo "node"
|
|
39
27
|
}
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
# (routines + app-driven headless task spawns). Handled HERE, before the Pi TUI exec
|
|
43
|
-
# and before the moat-shim install (the daemon doesn't need the interactive extension
|
|
44
|
-
# set), so it boots straight into src/daemon via bin/privateer-daemon.mjs.
|
|
45
|
-
if [ "${1:-}" = "daemon" ]; then
|
|
46
|
-
shift
|
|
47
|
-
DAEMON_NODE="$(pick_node)"
|
|
48
|
-
DAEMON_ENV_ARGS=""
|
|
49
|
-
[ -f "$REPO/.env" ] && DAEMON_ENV_ARGS="--env-file=$REPO/.env"
|
|
50
|
-
exec "$DAEMON_NODE" ${DAEMON_ENV_ARGS:+"$DAEMON_ENV_ARGS"} "$REPO/bin/privateer-daemon.mjs" "$@"
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
AGENT_DIR="${PRIVATEER_HOME:-$HOME/.privateer}/agent"
|
|
54
|
-
EXT_DIR="$AGENT_DIR/extensions"
|
|
55
|
-
mkdir -p "$EXT_DIR"
|
|
56
|
-
|
|
57
|
-
# Install/refresh the moat + tool-pack shims. Each shim re-exports its target by
|
|
58
|
-
# ABSOLUTE path so the target's own relative imports resolve from the repo (a plain
|
|
59
|
-
# symlink would resolve them relative to the shim's location and break). We remove
|
|
60
|
-
# any shim we previously managed first, so a dropped package can't linger and reload.
|
|
61
|
-
MANAGED="privateer-brand privateer-context privateer-gate privateer-account privateer-posture privateer-tools privateer-privacy pi-privacy pi-web-access rpiv-web-tools pi-mcp-adapter pi-hypa pi-subagents"
|
|
62
|
-
for name in $MANAGED; do rm -f "$EXT_DIR/$name.ts"; done
|
|
63
|
-
shim() { printf 'export { default } from "%s";\n' "$2" > "$EXT_DIR/$1.ts"; }
|
|
64
|
-
# Branding + the account sign-in surface (banner, ⚓ badge, /signin /signout).
|
|
65
|
-
shim privateer-brand "$REPO/extensions/privateer-brand.ts"
|
|
66
|
-
# PRIVATEER.md project-context loading (like AGENTS.md/CLAUDE.md) + the /init command.
|
|
67
|
-
shim privateer-context "$REPO/extensions/privateer-context.ts"
|
|
68
|
-
shim privateer-gate "$REPO/extensions/privateer-gate.ts"
|
|
69
|
-
shim privateer-account "$REPO/extensions/privateer-account.ts"
|
|
70
|
-
shim privateer-posture "$REPO/extensions/privateer-posture.ts"
|
|
71
|
-
shim privateer-tools "$REPO/extensions/privateer-tools.ts"
|
|
72
|
-
# pi-privacy wrapped with the account-channel tier resolver (privateer/near… = TEE),
|
|
73
|
-
# so it replaces loading pi-privacy's default entry.
|
|
74
|
-
shim privateer-privacy "$REPO/extensions/privateer-privacy.ts"
|
|
75
|
-
# Privacy-aligned web tools: pluggable backends (SearXNG self-hosted = private; no
|
|
76
|
-
# WebView). Pick the backend with /web-tools, or set SEARXNG_URL. Replaces
|
|
77
|
-
# pi-web-access (opened a WebView + defaulted to Exa, a 3rd-party search).
|
|
78
|
-
shim rpiv-web-tools "$REPO/node_modules/@juicesharp/rpiv-web-tools/index.ts"
|
|
79
|
-
shim pi-mcp-adapter "$REPO/node_modules/pi-mcp-adapter/index.ts"
|
|
80
|
-
shim pi-hypa "$REPO/node_modules/@hypabolic/pi-hypa/extensions/index.ts"
|
|
81
|
-
shim pi-subagents "$REPO/node_modules/pi-subagents/src/extension/index.ts"
|
|
82
|
-
|
|
83
|
-
CLI="$REPO/node_modules/@earendil-works/pi-coding-agent/dist/cli.js"
|
|
84
|
-
export PI_CODING_AGENT_DIR="$AGENT_DIR"
|
|
85
|
-
# The binary pi-subagents spawns for each subagent child. Without this it falls back
|
|
86
|
-
# to `pi` on PATH (getPiSpawnCommand) — which a Privateer install does NOT provide, so
|
|
87
|
-
# every subagent spawn would fail with ENOENT. Point it at OUR bundled cli.js (it's
|
|
88
|
-
# executable, `#!/usr/bin/env node`): the child then reads this same PI_CODING_AGENT_DIR
|
|
89
|
-
# and DISCOVERS the moat shims above — so subagents run gated + private, no -e injection
|
|
90
|
-
# (hence no double-load). Set only when unset so a power user can override it.
|
|
91
|
-
export PI_SUBAGENT_PI_BINARY="${PI_SUBAGENT_PI_BINARY:-$CLI}"
|
|
92
|
-
# Suppress Pi's upstream "Update Available — run `pi update`" banner. It's noise on a
|
|
93
|
-
# Privateer install (our banner is the startup surface), it leaks the "pi" name, and
|
|
94
|
-
# `pi update` would fight our npm-managed install. PI_SKIP_VERSION_CHECK disables ONLY
|
|
95
|
-
# the version fetch — unlike PI_OFFLINE it still lets fd/rg download on first run.
|
|
96
|
-
export PI_SKIP_VERSION_CHECK=1
|
|
97
|
-
NODE_BIN="$(pick_node)"
|
|
98
|
-
|
|
99
|
-
# Quiet Pi's built-in startup chatter so our banner is the only greeting. Each key is
|
|
100
|
-
# set only when unset, so a user's own settings.json toggle (or --verbose) still wins:
|
|
101
|
-
# quietStartup — hide the [Extensions]/[Context]/[Skills]/… resource listing
|
|
102
|
-
# collapseChangelog — never dump Pi's "What's New" changelog wall into the chat
|
|
103
|
-
# lastChangelogVersion — sentinel so Pi finds no "new" entries to show on upgrade;
|
|
104
|
-
# release notes live in the npm README / GitHub releases instead
|
|
105
|
-
"$NODE_BIN" -e '
|
|
106
|
-
const fs = require("fs"), p = process.argv[1];
|
|
107
|
-
let s = {}; try { s = JSON.parse(fs.readFileSync(p, "utf8")); } catch {}
|
|
108
|
-
let m = false;
|
|
109
|
-
if (s.quietStartup === undefined) { s.quietStartup = true; m = true; }
|
|
110
|
-
if (s.collapseChangelog === undefined) { s.collapseChangelog = true; m = true; }
|
|
111
|
-
if (s.lastChangelogVersion === undefined) { s.lastChangelogVersion = "9999.0.0"; m = true; }
|
|
112
|
-
if (m) fs.writeFileSync(p, JSON.stringify(s, null, 2) + "\n");
|
|
113
|
-
' "$AGENT_DIR/settings.json" 2>/dev/null || true
|
|
114
|
-
|
|
115
|
-
# Passive update check: refresh the cached "latest npm version" at most ~daily, in the
|
|
116
|
-
# BACKGROUND so it never blocks or breaks launch (offline-safe — a failed fetch just
|
|
117
|
-
# leaves the stale cache in place). The banner (privateer-brand) reads this cache and
|
|
118
|
-
# shows a one-line "↑ vX available · run privateer update" notice when we're behind. We
|
|
119
|
-
# never auto-install — the user stays in control of when new code lands (the whole point
|
|
120
|
-
# of an attestable tool). Detached with </dev/null &, so it outlives our exec into node.
|
|
121
|
-
UPDATE_CACHE="${PRIVATEER_HOME:-$HOME/.privateer}/update-check.json"
|
|
122
|
-
if [ -z "$(find "$UPDATE_CACHE" -mtime -1 2>/dev/null)" ]; then
|
|
123
|
-
(
|
|
124
|
-
latest="$(npm view privateer-agent version 2>/dev/null || true)"
|
|
125
|
-
case "$latest" in
|
|
126
|
-
[0-9]*) printf '{"latest":"%s"}\n' "$latest" > "$UPDATE_CACHE.tmp" 2>/dev/null \
|
|
127
|
-
&& mv -f "$UPDATE_CACHE.tmp" "$UPDATE_CACHE" 2>/dev/null ;;
|
|
128
|
-
esac
|
|
129
|
-
) </dev/null >/dev/null 2>&1 &
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
# Default model. An explicit PRIVATEER_MODEL always wins. Otherwise prefer Tinfoil's
|
|
133
|
-
# GLM 5.2 when a Tinfoil key is available: verifiable TEE inference with CLIENT-side
|
|
134
|
-
# attestation (the live TLS key is bound to the enclave's quote), the strongest privacy
|
|
135
|
-
# tier we offer — stronger than the account's server-proxied NEAR channel. Failing that,
|
|
136
|
-
# use the signed-in Privateer account's NEAR confidential-compute channel; with neither,
|
|
137
|
-
# fall back to a cheap OpenRouter model. The Tinfoil key may sit in the ambient env or in
|
|
138
|
-
# the dev .env the launcher loads below, so check both.
|
|
139
|
-
CRED="${PRIVATEER_HOME:-$HOME/.privateer}/credentials.json"
|
|
140
|
-
have_tinfoil_key() {
|
|
141
|
-
[ -n "${TINFOIL_API_KEY:-}" ] && return 0
|
|
142
|
-
[ -f "$REPO/.env" ] && grep -qE '^TINFOIL_API_KEY=.+' "$REPO/.env"
|
|
143
|
-
}
|
|
144
|
-
if [ -n "${PRIVATEER_MODEL:-}" ]; then
|
|
145
|
-
MODEL="$PRIVATEER_MODEL"
|
|
146
|
-
elif have_tinfoil_key; then
|
|
147
|
-
MODEL="tinfoil/glm-5-2"
|
|
148
|
-
elif [ -f "$CRED" ]; then
|
|
149
|
-
MODEL="privateer/near/zai-org/GLM-5.1-FP8"
|
|
150
|
-
else
|
|
151
|
-
MODEL="openrouter/openai/gpt-4o-mini"
|
|
152
|
-
fi
|
|
153
|
-
|
|
154
|
-
# Dev convenience: load provider keys from the repo's .env if present. A real install
|
|
155
|
-
# has none — so only pass the flag when the file exists (using --env-file-if-exists
|
|
156
|
-
# unconditionally would print "…/.env not found. Continuing without it." on every
|
|
157
|
-
# launch, muddying the startup banner). No .env → rely on the ambient environment.
|
|
158
|
-
ENV_ARGS=""
|
|
159
|
-
[ -f "$REPO/.env" ] && ENV_ARGS="--env-file=$REPO/.env"
|
|
160
|
-
exec "$NODE_BIN" ${ENV_ARGS:+"$ENV_ARGS"} "$CLI" --model "$MODEL" "$@"
|
|
29
|
+
exec "$(pick_node)" "$REPO/bin/privateer-launch.mjs" "$@"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM Windows entry point for a Privateer bundle. Mirrors the unix bin/privateer-tui
|
|
3
|
+
REM shim: run the bundled Node against the shared cross-platform launcher. %~dp0 is
|
|
4
|
+
REM this file's dir (<app>\bin\), so ..\node.exe is the bundled runtime.
|
|
5
|
+
"%~dp0..\node.exe" "%~dp0privateer-launch.mjs" %*
|
|
6
|
+
exit /b %errorlevel%
|