great-cto 2.22.0 → 2.22.2
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/package.json +1 -1
- package/dist/leash.js +0 -351
package/package.json
CHANGED
package/dist/leash.js
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
// `great-cto leash <subcommand>` — install, start, status, kill, update.
|
|
2
|
-
//
|
|
3
|
-
// Distribution model: we track llm-leash by *git repository* (not PyPI) so
|
|
4
|
-
// every push to https://github.com/avelikiy/llm-leash main is one
|
|
5
|
-
// `great-cto leash update` away. The repo is cloned to ~/.great_cto/llm-leash
|
|
6
|
-
// and installed as editable (`pip install -e .`). Updates run `git pull` +
|
|
7
|
-
// `pip install -e . --upgrade`.
|
|
8
|
-
//
|
|
9
|
-
// Three sources of truth:
|
|
10
|
-
// 1. Installed SHA = git rev-parse HEAD in ~/.great_cto/llm-leash
|
|
11
|
-
// 2. Latest SHA = GitHub commits API
|
|
12
|
-
// 3. Pinned SHA = .great_cto/leash.json → "pinned_sha" (optional)
|
|
13
|
-
//
|
|
14
|
-
// If pinned_sha is set, update() refuses to bump past it without --force.
|
|
15
|
-
import { spawnSync } from "node:child_process";
|
|
16
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { homedir } from "node:os";
|
|
18
|
-
import { join, dirname } from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { log, success, warn, error, cyan, dim, bold } from "./ui.js";
|
|
21
|
-
const REPO_URL = "https://github.com/avelikiy/llm-leash.git";
|
|
22
|
-
const REPO_API = "https://api.github.com/repos/avelikiy/llm-leash";
|
|
23
|
-
const INSTALL_ROOT = join(homedir(), ".great_cto", "llm-leash");
|
|
24
|
-
const CONFIG_PATH = join(homedir(), ".great_cto", "leash.json");
|
|
25
|
-
// ── public API ────────────────────────────────────────────────────────────────
|
|
26
|
-
export async function runLeash(argv) {
|
|
27
|
-
const sub = argv[0];
|
|
28
|
-
switch (sub) {
|
|
29
|
-
case undefined:
|
|
30
|
-
case "help":
|
|
31
|
-
case "--help":
|
|
32
|
-
case "-h":
|
|
33
|
-
printHelp();
|
|
34
|
-
return { exitCode: 0 };
|
|
35
|
-
case "install":
|
|
36
|
-
return install();
|
|
37
|
-
case "update":
|
|
38
|
-
return update(argv.includes("--force"));
|
|
39
|
-
case "status":
|
|
40
|
-
return status();
|
|
41
|
-
case "start":
|
|
42
|
-
return startProxy(argv.slice(1));
|
|
43
|
-
case "kill":
|
|
44
|
-
return killAll();
|
|
45
|
-
case "uninstall":
|
|
46
|
-
return uninstall();
|
|
47
|
-
case "wire":
|
|
48
|
-
return wire(argv.includes("--unwire"));
|
|
49
|
-
default:
|
|
50
|
-
error(`great-cto leash: unknown subcommand '${sub}'`);
|
|
51
|
-
printHelp();
|
|
52
|
-
return { exitCode: 2 };
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// ── subcommands ───────────────────────────────────────────────────────────────
|
|
56
|
-
function printHelp() {
|
|
57
|
-
log(bold("great-cto leash") + " — runtime governance for LLM agents (https://github.com/avelikiy/llm-leash)");
|
|
58
|
-
log("");
|
|
59
|
-
log(" " + cyan("install") + " clone the repo, install as editable, write default config");
|
|
60
|
-
log(" " + cyan("update") + " git pull + reinstall (auto-pulls latest commits from main)");
|
|
61
|
-
log(" " + cyan("status") + " installed version vs GitHub latest, last audit-log entry");
|
|
62
|
-
log(" " + cyan("start") + " start the HTTP proxy on :8765 (env-var deployment)");
|
|
63
|
-
log(" " + cyan("kill") + " fire kill switch — stops all in-flight LLM calls (<300 ms)");
|
|
64
|
-
log(" " + cyan("wire") + " install Python sitecustomize + Node --require wrappers");
|
|
65
|
-
log(" " + dim(" ") + "so anthropic/openai SDK clients auto-send tenant + session headers");
|
|
66
|
-
log(" " + cyan("uninstall") + " remove ~/.great_cto/llm-leash (config left intact)");
|
|
67
|
-
log("");
|
|
68
|
-
log(dim(" Config: " + CONFIG_PATH));
|
|
69
|
-
log(dim(" Install dir: " + INSTALL_ROOT));
|
|
70
|
-
}
|
|
71
|
-
function install() {
|
|
72
|
-
if (!hasGit()) {
|
|
73
|
-
error("git is required. Install git first: https://git-scm.com/downloads");
|
|
74
|
-
return { exitCode: 1 };
|
|
75
|
-
}
|
|
76
|
-
if (!hasPython()) {
|
|
77
|
-
error("python3 is required. Install Python 3.10+ first: https://www.python.org/downloads/");
|
|
78
|
-
return { exitCode: 1 };
|
|
79
|
-
}
|
|
80
|
-
mkdirSync(join(homedir(), ".great_cto"), { recursive: true });
|
|
81
|
-
if (existsSync(INSTALL_ROOT)) {
|
|
82
|
-
warn(`llm-leash already cloned at ${INSTALL_ROOT}.`);
|
|
83
|
-
log(` Run ${cyan("great-cto leash update")} to pull latest.`);
|
|
84
|
-
return { exitCode: 0 };
|
|
85
|
-
}
|
|
86
|
-
log(dim(` cloning ${REPO_URL} → ${INSTALL_ROOT}`));
|
|
87
|
-
const cloneResult = spawnSync("git", ["clone", REPO_URL, INSTALL_ROOT], {
|
|
88
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 120_000,
|
|
89
|
-
});
|
|
90
|
-
if (cloneResult.status !== 0) {
|
|
91
|
-
error(`git clone failed: ${cloneResult.stderr?.toString() || "unknown"}`);
|
|
92
|
-
return { exitCode: 1 };
|
|
93
|
-
}
|
|
94
|
-
log(dim(` pip install -e .`));
|
|
95
|
-
const pipResult = spawnSync(pythonCmd(), ["-m", "pip", "install", "-e", INSTALL_ROOT, "--quiet"], {
|
|
96
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 240_000,
|
|
97
|
-
});
|
|
98
|
-
if (pipResult.status !== 0) {
|
|
99
|
-
warn("pip install reported errors — leash CLI may not be on PATH yet:");
|
|
100
|
-
warn(pipResult.stderr?.toString() || "");
|
|
101
|
-
log(` Try: ${cyan(`${pythonCmd()} -m pip install -e ${INSTALL_ROOT}`)}`);
|
|
102
|
-
}
|
|
103
|
-
// Default config (only if absent — never clobber user changes)
|
|
104
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
105
|
-
const defaults = {
|
|
106
|
-
enabled: true,
|
|
107
|
-
install_root: INSTALL_ROOT,
|
|
108
|
-
audit_path: join(homedir(), ".leash", "audit.jsonl"),
|
|
109
|
-
proxy_url: "http://localhost:8765",
|
|
110
|
-
daily_cap_usd: 50,
|
|
111
|
-
monthly_cap_usd: 500,
|
|
112
|
-
};
|
|
113
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(defaults, null, 2));
|
|
114
|
-
success(`wrote ${CONFIG_PATH}`);
|
|
115
|
-
}
|
|
116
|
-
const sha = getInstalledSha();
|
|
117
|
-
success(`llm-leash installed at ${INSTALL_ROOT}${sha ? ` (HEAD: ${sha})` : ""}`);
|
|
118
|
-
log("");
|
|
119
|
-
log("Next: " + cyan("great-cto leash status") + " — verify proxy reachable");
|
|
120
|
-
log(" " + cyan("great-cto leash start") + " — start HTTP proxy on :8765");
|
|
121
|
-
return { exitCode: 0 };
|
|
122
|
-
}
|
|
123
|
-
function update(force) {
|
|
124
|
-
if (!existsSync(INSTALL_ROOT)) {
|
|
125
|
-
warn("llm-leash not installed. Run `great-cto leash install` first.");
|
|
126
|
-
return { exitCode: 1 };
|
|
127
|
-
}
|
|
128
|
-
const cfg = readConfig();
|
|
129
|
-
const beforeSha = getInstalledSha();
|
|
130
|
-
if (cfg.pinned_sha && !force) {
|
|
131
|
-
log(dim(` pinned to ${cfg.pinned_sha} in ${CONFIG_PATH} — checkout pinned commit`));
|
|
132
|
-
const co = spawnSync("git", ["-C", INSTALL_ROOT, "fetch", "--quiet"], { stdio: "ignore", timeout: 60_000 });
|
|
133
|
-
if (co.status !== 0) {
|
|
134
|
-
error("git fetch failed");
|
|
135
|
-
return { exitCode: 1 };
|
|
136
|
-
}
|
|
137
|
-
const reset = spawnSync("git", ["-C", INSTALL_ROOT, "reset", "--hard", cfg.pinned_sha], {
|
|
138
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 30_000,
|
|
139
|
-
});
|
|
140
|
-
if (reset.status !== 0) {
|
|
141
|
-
error(`reset to pinned ${cfg.pinned_sha} failed: ${reset.stderr?.toString()}`);
|
|
142
|
-
return { exitCode: 1 };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
log(dim(` git pull origin main`));
|
|
147
|
-
const pull = spawnSync("git", ["-C", INSTALL_ROOT, "pull", "--ff-only", "origin", "main"], {
|
|
148
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 60_000,
|
|
149
|
-
});
|
|
150
|
-
if (pull.status !== 0) {
|
|
151
|
-
error(`git pull failed: ${pull.stderr?.toString()}`);
|
|
152
|
-
log(` Try: ${cyan(`cd ${INSTALL_ROOT} && git status`)}`);
|
|
153
|
-
return { exitCode: 1 };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const afterSha = getInstalledSha();
|
|
157
|
-
if (beforeSha === afterSha) {
|
|
158
|
-
log(dim(` already at latest (${afterSha})`));
|
|
159
|
-
return { exitCode: 0 };
|
|
160
|
-
}
|
|
161
|
-
log(dim(` pip install -e . --upgrade`));
|
|
162
|
-
const pip = spawnSync(pythonCmd(), ["-m", "pip", "install", "-e", INSTALL_ROOT, "--upgrade", "--quiet"], {
|
|
163
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 240_000,
|
|
164
|
-
});
|
|
165
|
-
if (pip.status !== 0) {
|
|
166
|
-
warn("pip reinstall reported errors:");
|
|
167
|
-
warn(pip.stderr?.toString() || "");
|
|
168
|
-
}
|
|
169
|
-
success(`llm-leash: ${beforeSha} → ${afterSha}`);
|
|
170
|
-
// Persist last-known SHA for the version-check hook
|
|
171
|
-
if (afterSha)
|
|
172
|
-
writeVersionCache(afterSha);
|
|
173
|
-
return { exitCode: 0 };
|
|
174
|
-
}
|
|
175
|
-
async function status() {
|
|
176
|
-
const installed = existsSync(INSTALL_ROOT);
|
|
177
|
-
if (!installed) {
|
|
178
|
-
log(bold("llm-leash:") + " " + dim("not installed"));
|
|
179
|
-
log(` Install: ${cyan("great-cto leash install")}`);
|
|
180
|
-
return { exitCode: 0 };
|
|
181
|
-
}
|
|
182
|
-
const cfg = readConfig();
|
|
183
|
-
const head = getInstalledSha() || "?";
|
|
184
|
-
const latest = await fetchLatestSha();
|
|
185
|
-
log(bold("llm-leash:") + " installed at " + dim(INSTALL_ROOT));
|
|
186
|
-
log(` Installed HEAD : ${head}`);
|
|
187
|
-
log(` GitHub latest : ${latest || dim("unknown (network?)")}`);
|
|
188
|
-
log(` Config : ${CONFIG_PATH}`);
|
|
189
|
-
log(` Audit log : ${cfg.audit_path || dim("default")}`);
|
|
190
|
-
log(` Daily cap : ${cfg.daily_cap_usd ? "$" + cfg.daily_cap_usd : dim("not set")}`);
|
|
191
|
-
log(` Pinned SHA : ${cfg.pinned_sha || dim("none — track main")}`);
|
|
192
|
-
if (latest && latest !== head) {
|
|
193
|
-
log("");
|
|
194
|
-
warn(`Update available. Run ${cyan("great-cto leash update")} to bump.`);
|
|
195
|
-
}
|
|
196
|
-
return { exitCode: 0 };
|
|
197
|
-
}
|
|
198
|
-
function startProxy(extraArgs) {
|
|
199
|
-
if (!existsSync(INSTALL_ROOT)) {
|
|
200
|
-
warn("llm-leash not installed. Run `great-cto leash install` first.");
|
|
201
|
-
return { exitCode: 1 };
|
|
202
|
-
}
|
|
203
|
-
log(`Starting llm-leash proxy on http://localhost:8765 …`);
|
|
204
|
-
log(dim(` set ANTHROPIC_BASE_URL=http://localhost:8765 to route via leash`));
|
|
205
|
-
const r = spawnSync(pythonCmd(), ["-m", "leash.proxy", ...extraArgs], {
|
|
206
|
-
stdio: "inherit",
|
|
207
|
-
});
|
|
208
|
-
return { exitCode: r.status ?? 0 };
|
|
209
|
-
}
|
|
210
|
-
function killAll() {
|
|
211
|
-
const r = spawnSync("leash", ["kill", "--all", "--reason", "cli"], {
|
|
212
|
-
stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
|
|
213
|
-
});
|
|
214
|
-
if (r.status === 0) {
|
|
215
|
-
success("kill switch fired");
|
|
216
|
-
return { exitCode: 0 };
|
|
217
|
-
}
|
|
218
|
-
// Fall back to python -m
|
|
219
|
-
const r2 = spawnSync(pythonCmd(), ["-m", "leash", "kill", "--all", "--reason", "cli"], {
|
|
220
|
-
stdio: "inherit", timeout: 5000,
|
|
221
|
-
});
|
|
222
|
-
return { exitCode: r2.status ?? 1 };
|
|
223
|
-
}
|
|
224
|
-
function uninstall() {
|
|
225
|
-
if (!existsSync(INSTALL_ROOT)) {
|
|
226
|
-
log(dim("nothing to remove"));
|
|
227
|
-
return { exitCode: 0 };
|
|
228
|
-
}
|
|
229
|
-
const r = spawnSync("rm", ["-rf", INSTALL_ROOT], { stdio: "inherit", timeout: 30_000 });
|
|
230
|
-
if (r.status === 0)
|
|
231
|
-
success(`removed ${INSTALL_ROOT}`);
|
|
232
|
-
log(dim(`config left intact at ${CONFIG_PATH}`));
|
|
233
|
-
return { exitCode: r.status ?? 0 };
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* `great-cto leash wire` — install Python sitecustomize + Node --require
|
|
237
|
-
* wrappers into ~/.great_cto/leash-customize/ so every Anthropic / OpenAI
|
|
238
|
-
* client constructed in any child process inherits X-LLM-Leash-Tenant-Id
|
|
239
|
-
* and X-LLM-Leash-Session-Id headers automatically.
|
|
240
|
-
*
|
|
241
|
-
* Idempotent. Pass --unwire to remove. Leaves PROJECT.md untouched —
|
|
242
|
-
* tenant_id is already in PROJECT.md after great-cto init.
|
|
243
|
-
*
|
|
244
|
-
* Mark file ~/.great_cto/leash-customize/.wired so SessionStart hook
|
|
245
|
-
* knows it should expand .great_cto/env.sh with PYTHONSTARTUP / NODE_OPTIONS.
|
|
246
|
-
*/
|
|
247
|
-
async function wire(unwire) {
|
|
248
|
-
const customizeDir = join(homedir(), ".great_cto", "leash-customize");
|
|
249
|
-
const markerFile = join(customizeDir, ".wired");
|
|
250
|
-
if (unwire) {
|
|
251
|
-
try {
|
|
252
|
-
const { rmSync } = await import("node:fs");
|
|
253
|
-
rmSync(customizeDir, { recursive: true, force: true });
|
|
254
|
-
success("removed leash header wrappers");
|
|
255
|
-
log(dim("re-source .great_cto/env.sh or open a new shell to clear PYTHONSTARTUP / NODE_OPTIONS"));
|
|
256
|
-
}
|
|
257
|
-
catch (e) {
|
|
258
|
-
warn(`could not remove: ${e.message}`);
|
|
259
|
-
}
|
|
260
|
-
return { exitCode: 0 };
|
|
261
|
-
}
|
|
262
|
-
// Locate the source files inside the plugin: dist/main.js → ../../scripts/leash-customize/
|
|
263
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
264
|
-
const srcDir = join(here, "..", "..", "scripts", "leash-customize");
|
|
265
|
-
if (!existsSync(srcDir)) {
|
|
266
|
-
error(`source dir not found: ${srcDir}`);
|
|
267
|
-
log("This usually means the plugin install is incomplete. Try `great-cto install` again.");
|
|
268
|
-
return { exitCode: 1 };
|
|
269
|
-
}
|
|
270
|
-
try {
|
|
271
|
-
const { mkdirSync, copyFileSync, writeFileSync, statSync } = await import("node:fs");
|
|
272
|
-
mkdirSync(join(customizeDir, "python"), { recursive: true });
|
|
273
|
-
mkdirSync(join(customizeDir, "node"), { recursive: true });
|
|
274
|
-
copyFileSync(join(srcDir, "python", "sitecustomize.py"), join(customizeDir, "python", "sitecustomize.py"));
|
|
275
|
-
copyFileSync(join(srcDir, "node", "leash-init.cjs"), join(customizeDir, "node", "leash-init.cjs"));
|
|
276
|
-
writeFileSync(markerFile, `wired_at=${new Date().toISOString()}\nplugin_src=${srcDir}\n`);
|
|
277
|
-
// sanity-check
|
|
278
|
-
statSync(join(customizeDir, "python", "sitecustomize.py"));
|
|
279
|
-
statSync(join(customizeDir, "node", "leash-init.cjs"));
|
|
280
|
-
success(`wired leash header wrappers → ${customizeDir}`);
|
|
281
|
-
log("");
|
|
282
|
-
log("Next: in any project where you want auto-tagging,");
|
|
283
|
-
log(` source .great_cto/env.sh ${dim("# sets PYTHONSTARTUP + NODE_OPTIONS + LEASH_TENANT_ID")}`);
|
|
284
|
-
log("Then launch your agent — Anthropic/OpenAI clients pick up the headers automatically.");
|
|
285
|
-
return { exitCode: 0 };
|
|
286
|
-
}
|
|
287
|
-
catch (e) {
|
|
288
|
-
error(`wire failed: ${e.message}`);
|
|
289
|
-
return { exitCode: 1 };
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
293
|
-
function hasGit() {
|
|
294
|
-
return spawnSync("git", ["--version"], { stdio: "ignore", timeout: 3000 }).status === 0;
|
|
295
|
-
}
|
|
296
|
-
function hasPython() {
|
|
297
|
-
return spawnSync(pythonCmd(), ["--version"], { stdio: "ignore", timeout: 3000 }).status === 0;
|
|
298
|
-
}
|
|
299
|
-
function pythonCmd() {
|
|
300
|
-
// Prefer python3, fall back to python
|
|
301
|
-
if (spawnSync("python3", ["--version"], { stdio: "ignore", timeout: 2000 }).status === 0)
|
|
302
|
-
return "python3";
|
|
303
|
-
return "python";
|
|
304
|
-
}
|
|
305
|
-
function getInstalledSha() {
|
|
306
|
-
if (!existsSync(INSTALL_ROOT))
|
|
307
|
-
return null;
|
|
308
|
-
const r = spawnSync("git", ["-C", INSTALL_ROOT, "rev-parse", "--short", "HEAD"], {
|
|
309
|
-
stdio: ["ignore", "pipe", "ignore"], timeout: 3000,
|
|
310
|
-
});
|
|
311
|
-
if (r.status !== 0)
|
|
312
|
-
return null;
|
|
313
|
-
return r.stdout.toString().trim();
|
|
314
|
-
}
|
|
315
|
-
async function fetchLatestSha() {
|
|
316
|
-
try {
|
|
317
|
-
const res = await fetch(`${REPO_API}/commits/main`, {
|
|
318
|
-
headers: {
|
|
319
|
-
"Accept": "application/vnd.github+json",
|
|
320
|
-
"User-Agent": "great-cto-leash",
|
|
321
|
-
},
|
|
322
|
-
// Node 20 has native AbortController + fetch; cap at 6 s.
|
|
323
|
-
signal: AbortSignal.timeout(6000),
|
|
324
|
-
});
|
|
325
|
-
if (!res.ok)
|
|
326
|
-
return null;
|
|
327
|
-
const j = await res.json();
|
|
328
|
-
return j.sha?.slice(0, 7) || null;
|
|
329
|
-
}
|
|
330
|
-
catch {
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
function readConfig() {
|
|
335
|
-
try {
|
|
336
|
-
if (existsSync(CONFIG_PATH))
|
|
337
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
338
|
-
}
|
|
339
|
-
catch { /* ignore */ }
|
|
340
|
-
return {};
|
|
341
|
-
}
|
|
342
|
-
function writeVersionCache(sha) {
|
|
343
|
-
const cache = join(homedir(), ".great_cto", "leash-version.json");
|
|
344
|
-
try {
|
|
345
|
-
writeFileSync(cache, JSON.stringify({
|
|
346
|
-
installed_sha: sha,
|
|
347
|
-
last_checked: new Date().toISOString(),
|
|
348
|
-
}, null, 2));
|
|
349
|
-
}
|
|
350
|
-
catch { /* best-effort */ }
|
|
351
|
-
}
|