great-cto 2.9.3 → 2.9.5
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/dist/bootstrap.js +30 -0
- package/dist/leash.js +63 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -59,6 +59,19 @@ compliance: [${complianceLine}]
|
|
|
59
59
|
> \`compliance:\` list drives which checklists security-officer runs.
|
|
60
60
|
> See ARCHETYPES.md "Parameter Values" for supported keys.
|
|
61
61
|
|
|
62
|
+
## Leash
|
|
63
|
+
|
|
64
|
+
leash:
|
|
65
|
+
tenant_id: ${slugifyTenant(title)}
|
|
66
|
+
daily_cap_usd: 10
|
|
67
|
+
session_prefix: gcto
|
|
68
|
+
|
|
69
|
+
> \`leash.tenant_id\` is sent as \`X-LLM-Leash-Tenant-Id\` on every LLM call
|
|
70
|
+
> through the proxy. Board's Security tab scopes stats to the active project
|
|
71
|
+
> via this id. Change here if multiple repos share the same logical project.
|
|
72
|
+
> \`session_prefix\` is prepended to auto-generated session ids so logs are
|
|
73
|
+
> easy to filter across machines.
|
|
74
|
+
|
|
62
75
|
## Memory & Query Rule
|
|
63
76
|
|
|
64
77
|
> Before reading source files, agents should query memory layers in this order:
|
|
@@ -101,6 +114,23 @@ when you actually start work:
|
|
|
101
114
|
success(`created .great_cto/PROJECT.md ${dim(`(archetype: ${archetype})`)}`);
|
|
102
115
|
return { projectMdPath: projectMd, created: true, skippedReason: null };
|
|
103
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Slugify a project title into a tenant-id safe for HTTP headers and audit
|
|
119
|
+
* logs. Rules: lowercase, ASCII alnum + dash, collapse, trim, max 48 chars.
|
|
120
|
+
*
|
|
121
|
+
* Examples:
|
|
122
|
+
* "billing-api" → "billing-api"
|
|
123
|
+
* "@acme/web-app" → "acme-web-app"
|
|
124
|
+
* " Foo Bar 2026" → "foo-bar-2026"
|
|
125
|
+
*/
|
|
126
|
+
export function slugifyTenant(title) {
|
|
127
|
+
return title
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.normalize("NFKD")
|
|
130
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
131
|
+
.replace(/^-+|-+$/g, "")
|
|
132
|
+
.slice(0, 48) || "default";
|
|
133
|
+
}
|
|
104
134
|
function inferProjectTitle(dir) {
|
|
105
135
|
// Prefer package.json name, then directory basename.
|
|
106
136
|
try {
|
package/dist/leash.js
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
import { spawnSync } from "node:child_process";
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
18
|
+
import { join, dirname } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
19
20
|
import { log, success, warn, error, cyan, dim, bold } from "./ui.js";
|
|
20
21
|
const REPO_URL = "https://github.com/avelikiy/llm-leash.git";
|
|
21
22
|
const REPO_API = "https://api.github.com/repos/avelikiy/llm-leash";
|
|
@@ -43,6 +44,8 @@ export async function runLeash(argv) {
|
|
|
43
44
|
return killAll();
|
|
44
45
|
case "uninstall":
|
|
45
46
|
return uninstall();
|
|
47
|
+
case "wire":
|
|
48
|
+
return wire(argv.includes("--unwire"));
|
|
46
49
|
default:
|
|
47
50
|
error(`great-cto leash: unknown subcommand '${sub}'`);
|
|
48
51
|
printHelp();
|
|
@@ -58,6 +61,8 @@ function printHelp() {
|
|
|
58
61
|
log(" " + cyan("status") + " installed version vs GitHub latest, last audit-log entry");
|
|
59
62
|
log(" " + cyan("start") + " start the HTTP proxy on :8765 (env-var deployment)");
|
|
60
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");
|
|
61
66
|
log(" " + cyan("uninstall") + " remove ~/.great_cto/llm-leash (config left intact)");
|
|
62
67
|
log("");
|
|
63
68
|
log(dim(" Config: " + CONFIG_PATH));
|
|
@@ -227,6 +232,63 @@ function uninstall() {
|
|
|
227
232
|
log(dim(`config left intact at ${CONFIG_PATH}`));
|
|
228
233
|
return { exitCode: r.status ?? 0 };
|
|
229
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
|
+
}
|
|
230
292
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
231
293
|
function hasGit() {
|
|
232
294
|
return spawnSync("git", ["--version"], { stdio: "ignore", timeout: 3000 }).status === 0;
|
package/package.json
CHANGED