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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "great-cto",
3
- "version": "2.9.3",
3
+ "version": "2.9.5",
4
4
  "description": "One command install for the great_cto Claude Code plugin. Auto-detects your stack, picks the right archetype, bootstraps PROJECT.md.",
5
5
  "keywords": [
6
6
  "claude-code",