leopold-driver 0.1.1 → 0.1.3

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.
Files changed (48) hide show
  1. package/README.md +19 -5
  2. package/assets/VERSION +1 -0
  3. package/assets/extensions/README.md +52 -0
  4. package/assets/extensions/gstack/extension.json +8 -0
  5. package/assets/extensions/gstack/manage.sh +68 -0
  6. package/assets/extensions/leopold/extension.json +8 -0
  7. package/assets/extensions/leopold/manage.sh +59 -0
  8. package/assets/extensions/ovmem/README.md +101 -0
  9. package/assets/extensions/ovmem/extension.json +8 -0
  10. package/assets/extensions/ovmem/install.sh +330 -0
  11. package/assets/extensions/ovmem/manage.sh +87 -0
  12. package/assets/extensions/ovmem/models.json +24 -0
  13. package/assets/extensions/ovmem/payload/RUNTIME.md +121 -0
  14. package/assets/extensions/ovmem/payload/ovmem-cleanup.py +148 -0
  15. package/assets/extensions/ovmem/payload/ovmem.py +421 -0
  16. package/assets/extensions/serena/README.md +50 -0
  17. package/assets/extensions/serena/extension.json +8 -0
  18. package/assets/extensions/serena/manage.sh +119 -0
  19. package/assets/hooks/guard-irreversible.sh +185 -0
  20. package/assets/hooks/hooks.json +20 -0
  21. package/assets/hooks/stop-continuity.sh +132 -0
  22. package/assets/install.sh +184 -0
  23. package/assets/scripts/__pycache__/leopold-watch.cpython-312.pyc +0 -0
  24. package/assets/scripts/leopold-doctor.sh +53 -0
  25. package/assets/scripts/leopold-menu.sh +132 -0
  26. package/assets/scripts/leopold-update-check.sh +23 -0
  27. package/assets/scripts/leopold-update.sh +13 -0
  28. package/assets/scripts/leopold-watch.py +585 -0
  29. package/assets/scripts/record-demo.sh +61 -0
  30. package/assets/scripts/test-guard.sh +76 -0
  31. package/assets/scripts/test-hooks.sh +121 -0
  32. package/assets/settings.template.json +23 -0
  33. package/assets/skills/leopold-brief/SKILL.md +121 -0
  34. package/assets/skills/leopold-doctor/SKILL.md +23 -0
  35. package/assets/skills/leopold-run/SKILL.md +171 -0
  36. package/assets/skills/leopold-status/SKILL.md +34 -0
  37. package/assets/skills/leopold-stop/SKILL.md +36 -0
  38. package/assets/skills/leopold-update/SKILL.md +27 -0
  39. package/assets/skills/leopold-watch/SKILL.md +48 -0
  40. package/assets/templates/CHARTER.md +32 -0
  41. package/assets/templates/DECISIONS.md +15 -0
  42. package/assets/templates/GUARDRAILS.md +38 -0
  43. package/assets/templates/MISSION.md +22 -0
  44. package/assets/templates/PLAN.md +9 -0
  45. package/dist/guard.js +82 -23
  46. package/dist/harness.js +71 -0
  47. package/dist/index.js +53 -23
  48. package/package.json +6 -3
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: leopold-watch
3
+ version: 0.1.0
4
+ description: "Launch a local live web dashboard to watch the current project's Leopold run — status, cost meters (context/subagents/forks), event feed, decisions, and a Stop button. Local-only (127.0.0.1), zero dependencies."
5
+ allowed-tools:
6
+ - Bash
7
+ triggers:
8
+ - leopold watch
9
+ - watch the run
10
+ - open the dashboard
11
+ ---
12
+
13
+ # /leopold-watch
14
+
15
+ Start the local live dashboard for this project's Leopold run and give the user the URL.
16
+ It only **reads** `.leopold/` (state, plan, decisions, events) and serves on loopback;
17
+ nothing leaves the machine. The one action it offers is a Stop button, which touches
18
+ `.leopold/STOP` — the same kill switch as `/leopold-stop`.
19
+
20
+ Run this (idempotent — if it is already up, just report the URL):
21
+
22
+ ```bash
23
+ WATCH="$HOME/.claude/leopold/scripts/leopold-watch.py"; [ -f "$WATCH" ] || WATCH="scripts/leopold-watch.py"
24
+ PORT="${LEOPOLD_WATCH_PORT:-4179}"
25
+ if curl -sf "http://127.0.0.1:$PORT/api/state" >/dev/null 2>&1; then
26
+ echo "Dashboard already running -> http://127.0.0.1:$PORT"
27
+ else
28
+ nohup python3 "$WATCH" --project "$(pwd)" --port "$PORT" >/tmp/leopold-watch.log 2>&1 &
29
+ sleep 1
30
+ if curl -sf "http://127.0.0.1:$PORT/api/state" >/dev/null 2>&1; then
31
+ echo "Dashboard started -> http://127.0.0.1:$PORT (pid $!)"
32
+ else
33
+ echo "Failed to start; last log lines:"; tail -3 /tmp/leopold-watch.log
34
+ fi
35
+ fi
36
+ ```
37
+
38
+ Then tell the user, briefly:
39
+
40
+ - Open **http://127.0.0.1:4179** (or the port shown) in a browser to watch the run live:
41
+ run status, cost meters (context MB, subagents, forks, iterations, failures vs their
42
+ budgets), the live event feed, the decisions log, and a **Stop** button.
43
+ - It shows real data only while a run is active (`/leopold-run`); otherwise it says
44
+ "no active run" and updates the moment one starts.
45
+ - To stop the dashboard itself: `pkill -f leopold-watch.py`. It also runs via `make watch`
46
+ or, from the npm package, `npx leopold-driver watch`.
47
+
48
+ Do not do anything else. Start it, report the URL, stop.
@@ -0,0 +1,32 @@
1
+ # Charter
2
+
3
+ > This is the decider. The run consults it on every fork so it chooses the way
4
+ > you would. Be concrete; concrete rules resolve real decisions, vague ones do
5
+ > not. This is the part of Leopold that "becomes you."
6
+
7
+ ## What I optimize for
8
+ > Ranked. Example: 1) shipping working software, 2) simplicity, 3) speed.
9
+ 1. ...
10
+
11
+ ## Technology and style preferences
12
+ > Languages, frameworks, patterns you favor or avoid.
13
+ - Prefer: ...
14
+ - Avoid: ...
15
+
16
+ ## Always
17
+ > Things the run should always do.
18
+ - ...
19
+
20
+ ## Never
21
+ > Hard lines the run must never cross.
22
+ - ...
23
+
24
+ ## Tie-breakers
25
+ > When principles conflict, how do you choose? Example: "When robustness and
26
+ > speed conflict on user-facing paths, robustness wins; on internal tooling,
27
+ > speed wins."
28
+ - ...
29
+
30
+ ## Examples of decisions I would make
31
+ > 2-4 worked examples. These calibrate the run better than abstract rules.
32
+ - Situation: ... -> I would: ... because ...
@@ -0,0 +1,15 @@
1
+ # Decisions
2
+
3
+ > Append-only audit trail of decisions the run made on your behalf, newest last.
4
+ > Each non-mechanical decision is one block. The Reversal line is your escape
5
+ > hatch: it tells you how to undo a call you disagree with.
6
+
7
+ <!-- Example:
8
+ ## D1 — Cache layer for the MVP (turn 3, 2026-06-17T15:00:00Z)
9
+ Fork: in-memory cache vs Redis
10
+ Class: reversible
11
+ Charter: "no new infrastructure for the MVP"
12
+ Decision: in-memory map with a TTL
13
+ Why: charter rule + principle 5 (explicit over clever)
14
+ Reversal: swap the cache module for a Redis client; interface is unchanged
15
+ -->
@@ -0,0 +1,38 @@
1
+ # Guardrails
2
+
3
+ > The autonomy boundary for this run. Defaults are the recommended posture.
4
+
5
+ ## Action classes
6
+ - Autonomous: reversible work in the repo (edit, build, lint, test, stage).
7
+ - Gated (locked): git commit, git push, PR create/merge, publish, deploy.
8
+ - Forbidden: rm -rf outside scratch, edits outside the project root, editing
9
+ this file / the hooks / Claude Code settings.
10
+
11
+ ## Git posture
12
+ > Default: LOCKED. The run stages and reports; the human commits and pushes.
13
+ > To allow commits this session: `touch .leopold/ALLOW_GIT` (push stays locked).
14
+ - Commit: locked
15
+ - Push / PR / publish: locked
16
+
17
+ ## Stop conditions
18
+ - max_iterations: 50
19
+ - max_failures: 3 # consecutive failures of the same kind
20
+ - max_no_progress: 6 # turns with the open PLAN set unchanged -> loop, stop
21
+ - max_subagents: 8 # total Task/subagent spawns per run; past it the guard
22
+ # denies more (each subagent re-loads the full context =
23
+ # the #1 cost multiplier). Raise only if you must.
24
+ - max_forks: 0 # forks clone the WHOLE session context (the leak) — forbidden
25
+ # by default; raise only if a sub-task needs the full convo
26
+ - max_context_mb: 5 # stop when the transcript passes this; a long run re-bills
27
+ # its growing context every turn. Resume with a fresh run.
28
+ - token/time budget: none # set if you want a hard ceiling
29
+
30
+ ## On finish
31
+ - on_finish: keep # keep | archive
32
+ > keep: brief, DECISIONS, and events stay in place. archive: on a clean finish,
33
+ > DECISIONS.md and events.jsonl move to .leopold/runs/<timestamp>/. Either way,
34
+ > the kill switch and git opt-in tokens are always cleared when a run stops.
35
+
36
+ ## Kill switch
37
+ - `/leopold-stop` (clean) or `touch .leopold/STOP` (blunt). Takes effect at the
38
+ next turn boundary; nothing is interrupted mid-turn.
@@ -0,0 +1,22 @@
1
+ # Mission
2
+
3
+ > What we are building and why. The autonomous run executes this; it does not
4
+ > invent it. Fill every section. Delete the guidance quotes when done.
5
+
6
+ ## Problem
7
+ > What hurts today, and why it matters now.
8
+
9
+ ## Goal
10
+ > The outcome we want. One or two sentences.
11
+
12
+ ## Non-goals
13
+ > What we are deliberately NOT doing this run. This prevents scope creep.
14
+ - ...
15
+
16
+ ## Definition of done
17
+ > Concrete, checkable. How we will know the mission succeeded.
18
+ - [ ] ...
19
+
20
+ ## Constraints
21
+ > Stack, deadlines, things that must not change, environments.
22
+ - ...
@@ -0,0 +1,9 @@
1
+ # Plan
2
+
3
+ > Ordered, checkbox backlog. Each item should be independently completable and
4
+ > verifiable. The run takes the next unchecked item each turn and marks it [x]
5
+ > when done. Order by dependency, then by value. Keep items small.
6
+
7
+ - [ ] First item
8
+ - [ ] Second item
9
+ - [ ] Third item
package/dist/guard.js CHANGED
@@ -1,16 +1,57 @@
1
1
  // The git lock, in driver form. Used as the worker's canUseTool callback so the
2
2
  // same policy as the in-session hook holds when the worker runs under the SDK.
3
- // It only adds denials; it never loosens anything.
3
+ // It only adds denials; it never loosens anything. Detection mirrors the hardened
4
+ // bash guard (hooks/guard-irreversible.sh) and is covered by test/guard.test.ts.
4
5
  import fs from "node:fs";
5
6
  import path from "node:path";
6
- const DESTRUCTIVE_RM = /rm\s+-[a-z]*(rf|fr)/i;
7
- const DESTRUCTIVE_GIT = /git\s+(reset\s+--hard|clean\s+-[a-z]*f|branch\s+-D)/;
8
- const FORCE_PUSH = /git\s+push\s+.*(--force|--force-with-lease|-f(\s|$))/;
9
- const GIT_COMMIT = /git\s+(-[^\s]+\s+)*commit/;
10
- const GIT_PUSH = /git\s+push/;
11
- const GH_PR = /gh\s+(pr\s+(create|merge)|release\s+create)/;
12
- const PUBLISH = /(npm|pnpm|yarn)\s+publish|cargo\s+publish|twine\s+upload|pip\s+.*upload/;
13
- const PROTECTED_PATH = /(GUARDRAILS\.md|settings\.json|settings\.local\.json|leopold\/hooks\/)/;
7
+ const GH_PR = /(^|[^\w-])gh(\s.*)?\s(pr\s+(create|merge)|release\s+create)/i;
8
+ const PUBLISH = /(npm|pnpm|yarn)\s+publish|cargo\s+publish|twine\s+upload|pip\s+.*upload/i;
9
+ const PROTECTED_PATH = /(GUARDRAILS\.md|settings\.json|settings\.local\.json|leopold\/hooks\/|\.leopold\/state\.json)/;
10
+ // git global options that consume the following token as their value.
11
+ const GIT_VALUE_OPTS = new Set([
12
+ "-c", "-C", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env",
13
+ ]);
14
+ /** Collapse whitespace so tab/space evasion does not change matching. */
15
+ function norm(s) {
16
+ return s.replace(/[\t\n]+/g, " ").replace(/ +/g, " ").trim();
17
+ }
18
+ /** Resolve the git subcommand, skipping global options + their values, by basename. */
19
+ export function gitSubcommand(cmd) {
20
+ const toks = norm(cmd).split(" ");
21
+ let i = toks.findIndex((t) => t.split("/").pop() === "git");
22
+ if (i === -1)
23
+ return "";
24
+ i += 1;
25
+ while (i < toks.length) {
26
+ const t = toks[i];
27
+ if (GIT_VALUE_OPTS.has(t)) {
28
+ i += 2;
29
+ continue;
30
+ }
31
+ if (t.startsWith("-")) {
32
+ i += 1;
33
+ continue;
34
+ }
35
+ return t;
36
+ }
37
+ return "";
38
+ }
39
+ /** Recursive AND forced rm, in any spelling (--recursive --force, -r -f, -rf, /bin/rm -rf). */
40
+ export function isRecursiveForceRm(cmd) {
41
+ const c = norm(cmd);
42
+ if (!/(^|[^\w-])rm(\s|$)/i.test(c))
43
+ return false;
44
+ const recursive = /(--recursive|(^|\s)-[a-z]*r)/i.test(c);
45
+ const force = /(--force|(^|\s)-[a-z]*f)/i.test(c);
46
+ return recursive && force;
47
+ }
48
+ /** find ... -delete or find ... -exec rm. */
49
+ export function isFindDelete(cmd) {
50
+ const c = norm(cmd);
51
+ if (!/(^|[^\w-])find(\s|$)/i.test(c))
52
+ return false;
53
+ return /\s-delete(\s|$)/i.test(c) || /-exec\s+rm(\s|$)/i.test(c);
54
+ }
14
55
  function hasToken(leoDir, name) {
15
56
  return fs.existsSync(path.join(leoDir, name));
16
57
  }
@@ -21,26 +62,44 @@ export function makeGuard(leoDir, onBlock) {
21
62
  return { behavior: "deny", message };
22
63
  };
23
64
  if (toolName === "Bash") {
24
- const cmd = String(input.command ?? "");
25
- if (DESTRUCTIVE_RM.test(cmd))
26
- return deny("Leopold guard: 'rm -rf' style deletion is forbidden in autonomous mode.");
27
- if (DESTRUCTIVE_GIT.test(cmd))
28
- return deny("Leopold guard: destructive git op (reset --hard / clean -f / branch -D) is forbidden.");
29
- if (FORCE_PUSH.test(cmd))
30
- return deny("Leopold guard: force-push is forbidden in autonomous mode.");
31
- if (GIT_COMMIT.test(cmd) && !hasToken(leoDir, "ALLOW_GIT"))
32
- return deny("Leopold guard: git commit is locked. Stage and report; the user commits. (touch .leopold/ALLOW_GIT to allow commits this run.)");
33
- if (GIT_PUSH.test(cmd) && !hasToken(leoDir, "ALLOW_PUSH"))
34
- return deny("Leopold guard: git push is locked. Report readiness instead.");
35
- if (GH_PR.test(cmd) && !hasToken(leoDir, "ALLOW_PUSH"))
65
+ const c = norm(String(input.command ?? ""));
66
+ if (isRecursiveForceRm(c))
67
+ return deny("Leopold guard: recursive+forced rm is forbidden in autonomous mode.");
68
+ if (isFindDelete(c))
69
+ return deny("Leopold guard: 'find -delete' / 'find -exec rm' is forbidden in autonomous mode.");
70
+ switch (gitSubcommand(c)) {
71
+ case "reset":
72
+ if (/(^|\s)--hard(\s|$)/.test(c))
73
+ return deny("Leopold guard: 'git reset --hard' is forbidden in autonomous mode.");
74
+ break;
75
+ case "clean":
76
+ if (/(--force|(^|\s)-[a-z]*f)/i.test(c))
77
+ return deny("Leopold guard: 'git clean -f' is forbidden in autonomous mode.");
78
+ break;
79
+ case "branch":
80
+ if (/(^|\s)-D(\s|$)/.test(c))
81
+ return deny("Leopold guard: 'git branch -D' is forbidden in autonomous mode.");
82
+ break;
83
+ case "push":
84
+ if (/(--force|--force-with-lease|(^|\s)-f(\s|$))/.test(c))
85
+ return deny("Leopold guard: force-push is forbidden in autonomous mode.");
86
+ if (!hasToken(leoDir, "ALLOW_PUSH"))
87
+ return deny("Leopold guard: git push is locked. Report readiness instead.");
88
+ break;
89
+ case "commit":
90
+ if (!hasToken(leoDir, "ALLOW_GIT"))
91
+ return deny("Leopold guard: git commit is locked. Stage and report; the user commits. (touch .leopold/ALLOW_GIT to allow commits this run.)");
92
+ break;
93
+ }
94
+ if (GH_PR.test(c) && !hasToken(leoDir, "ALLOW_PUSH"))
36
95
  return deny("Leopold guard: opening or merging PRs and creating releases is locked.");
37
- if (PUBLISH.test(cmd) && !hasToken(leoDir, "ALLOW_PUBLISH"))
96
+ if (PUBLISH.test(c) && !hasToken(leoDir, "ALLOW_PUBLISH"))
38
97
  return deny("Leopold guard: publishing packages is locked in autonomous mode.");
39
98
  }
40
99
  if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit" || toolName === "NotebookEdit") {
41
100
  const p = String(input.file_path ?? input.path ?? "");
42
101
  if (PROTECTED_PATH.test(p))
43
- return deny("Leopold guard: editing guardrails, settings, or hooks is forbidden in autonomous mode.");
102
+ return deny("Leopold guard: editing guardrails, settings, hooks, or run state is forbidden in autonomous mode.");
44
103
  }
45
104
  return { behavior: "allow", updatedInput: input };
46
105
  };
@@ -0,0 +1,71 @@
1
+ // Harness commands for the leopold-driver CLI: install / menu / watch / extensions /
2
+ // doctor / update. They run the bundled harness assets (skills, hooks, installer,
3
+ // extensions) that `npm run build` vendors into ../assets, so everything works from the
4
+ // npm package alone — no repo clone, no `make`.
5
+ import { spawnSync } from "node:child_process";
6
+ import { existsSync, readdirSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ /** Locate the vendored harness root (assets/ in the package, or the repo root in dev). */
10
+ export function assetRoot() {
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const bundled = join(here, "..", "assets");
13
+ if (existsSync(join(bundled, "install.sh")))
14
+ return bundled;
15
+ let dir = here; // dev: walk up to the repo root (has install.sh + skills/)
16
+ for (let i = 0; i < 6; i++) {
17
+ if (existsSync(join(dir, "install.sh")) && existsSync(join(dir, "skills")))
18
+ return dir;
19
+ const parent = dirname(dir);
20
+ if (parent === dir)
21
+ break;
22
+ dir = parent;
23
+ }
24
+ return bundled;
25
+ }
26
+ function run(cmd, args) {
27
+ const r = spawnSync(cmd, args, { stdio: "inherit" });
28
+ if (r.error) {
29
+ const e = r.error;
30
+ const hint = e.code === "ENOENT" ? ` (is "${cmd}" installed and on PATH?)` : "";
31
+ console.error(`leopold-driver: could not run ${cmd}${hint}: ${e.message}`);
32
+ return 1;
33
+ }
34
+ return r.status ?? 0;
35
+ }
36
+ export function runInstall(args) {
37
+ return run("bash", [join(assetRoot(), "install.sh"), ...args]);
38
+ }
39
+ export function runMenu() {
40
+ return run("bash", [join(assetRoot(), "scripts", "leopold-menu.sh")]);
41
+ }
42
+ export function runWatch(args) {
43
+ const script = join(assetRoot(), "scripts", "leopold-watch.py");
44
+ if (!existsSync(script)) {
45
+ console.error("leopold-driver watch: dashboard script (leopold-watch.py) not found.");
46
+ return 1;
47
+ }
48
+ const py = process.env.LEOPOLD_PYTHON ?? "python3";
49
+ return run(py, [script, "--project", process.cwd(), ...args]);
50
+ }
51
+ export function runExt(name, args) {
52
+ const mgr = join(assetRoot(), "extensions", name, "manage.sh");
53
+ if (!existsSync(mgr)) {
54
+ console.error(`leopold-driver: unknown extension "${name}". Try: serena, gstack, ovmem.`);
55
+ return 2;
56
+ }
57
+ return run("bash", [mgr, ...(args.length ? args : ["status"])]);
58
+ }
59
+ export function runDoctor() {
60
+ const extDir = join(assetRoot(), "extensions");
61
+ let rc = 0;
62
+ for (const name of existsSync(extDir) ? readdirSync(extDir) : []) {
63
+ if (!existsSync(join(extDir, name, "manage.sh")))
64
+ continue;
65
+ process.stdout.write(`\n[${name}]\n`);
66
+ const r = run("bash", [join(extDir, name, "manage.sh"), "doctor"]);
67
+ if (r !== 0)
68
+ rc = r;
69
+ }
70
+ return rc;
71
+ }
package/dist/index.js CHANGED
@@ -1,31 +1,61 @@
1
1
  #!/usr/bin/env node
2
- // leopold-driver: the external conductor. Reads the .leopold brief from the cwd
3
- // and orchestrates Claude Code workers through the plan, git locked.
2
+ // leopold-driver: Leopold from npm. Install + manage the harness, conduct runs, and
3
+ // watch without cloning the repo or running `make`. The harness assets are bundled
4
+ // into the package at build time; subcommands run them.
4
5
  import { runDriver } from "./loop.js";
5
- const arg = process.argv[2] ?? "run";
6
- if (arg === "--help" || arg === "-h" || arg === "help") {
7
- process.stdout.write(`leopold-driver - conduct Claude Code through the .leopold brief.
6
+ import { runInstall, runMenu, runWatch, runExt, runDoctor } from "./harness.js";
7
+ const sub = process.argv[2];
8
+ const rest = process.argv.slice(3);
9
+ function help() {
10
+ process.stdout.write(`leopold-driver — Leopold from npm. Manage the harness, conduct runs, watch.
8
11
 
9
12
  Usage:
10
- leopold-driver [run] [--dry-run]
13
+ leopold-driver install [--with-gstack] install skills + hooks into ~/.claude
14
+ leopold-driver menu toolchain manager (serena / gstack / ovmem)
15
+ leopold-driver watch [--port N] live dashboard (http://127.0.0.1:4179)
16
+ leopold-driver serena [install|doctor] manage an extension (also: gstack, ovmem)
17
+ leopold-driver doctor run every extension's doctor
18
+ leopold-driver update reinstall from this package
19
+ leopold-driver run [--dry-run] conduct the .leopold run (the SDK driver)
11
20
 
12
- Reads .leopold/ (MISSION, CHARTER, GUARDRAILS, PLAN) from the current project.
21
+ Most commands run the bundled harness — no repo clone, no make. 'watch' needs Python 3.
22
+ Newer version: npm i -g leopold-driver@latest.
13
23
 
14
- Auth:
15
- Uses your existing Claude Code login (your subscription) for BOTH the worker
16
- and the conductor. No API key needed. ANTHROPIC_API_KEY is only required in a
17
- headless environment that has no Claude Code auth configured.
18
-
19
- Env:
20
- LEOPOLD_CONDUCTOR_MODEL conductor model (default: your Claude Code default)
21
- LEOPOLD_WORKER_MODEL worker model (default: your Claude Code default)
22
- LEOPOLD_MAX_TURNS_PER_ITEM max worker turns per item (default: 40)
23
- LEOPOLD_WEBHOOK optional URL for JSON POST notifications
24
+ Conducting a run uses your existing Claude Code login (ANTHROPIC_API_KEY only in headless).
25
+ Env: LEOPOLD_CONDUCTOR_MODEL, LEOPOLD_WORKER_MODEL, LEOPOLD_MAX_TURNS_PER_ITEM, LEOPOLD_WEBHOOK
24
26
  `);
25
- process.exit(0);
26
27
  }
27
- runDriver(process.cwd(), process.argv.slice(2)).catch((err) => {
28
- const msg = err instanceof Error ? err.message : String(err);
29
- console.error("leopold-driver error:", msg);
30
- process.exit(1);
31
- });
28
+ function conduct() {
29
+ runDriver(process.cwd(), process.argv.slice(2)).catch((err) => {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ console.error("leopold-driver error:", msg);
32
+ process.exit(1);
33
+ });
34
+ }
35
+ switch (sub) {
36
+ case "install":
37
+ case "update":
38
+ process.exit(runInstall(rest));
39
+ case "menu":
40
+ process.exit(runMenu());
41
+ case "watch":
42
+ case "watch-web":
43
+ process.exit(runWatch(rest));
44
+ case "serena":
45
+ case "gstack":
46
+ case "ovmem":
47
+ process.exit(runExt(sub, rest));
48
+ case "doctor":
49
+ process.exit(runDoctor());
50
+ case "--help":
51
+ case "-h":
52
+ case "help":
53
+ help();
54
+ process.exit(0);
55
+ default:
56
+ if (sub && sub !== "run" && !sub.startsWith("-")) {
57
+ console.error(`leopold-driver: unknown command "${sub}". Try: leopold-driver --help`);
58
+ process.exit(2);
59
+ }
60
+ conduct();
61
+ }
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "leopold-driver",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Leopold SDK driver: a persistent conductor that orchestrates fresh Claude Code workers per task, decides from your charter, and notifies you. Uses your Claude Code auth. Git stays locked.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "leopold-driver": "dist/index.js"
7
+ "leopold-driver": "dist/index.js",
8
+ "leopold": "dist/index.js"
8
9
  },
9
10
  "main": "dist/index.js",
10
11
  "files": [
11
12
  "dist",
13
+ "assets",
12
14
  "README.md"
13
15
  ],
14
16
  "engines": {
@@ -33,8 +35,9 @@
33
35
  "anthropic"
34
36
  ],
35
37
  "scripts": {
36
- "build": "tsc -p tsconfig.json",
38
+ "build": "tsc -p tsconfig.json && node scripts/copy-runtime.mjs",
37
39
  "typecheck": "tsc -p tsconfig.json --noEmit",
40
+ "test": "node --experimental-strip-types --test test/protocol.test.ts test/guard.test.ts",
38
41
  "dev": "tsx src/index.ts",
39
42
  "start": "node dist/index.js",
40
43
  "prepublishOnly": "npm run build"