agent-lyceum 0.9.0 → 0.9.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/README.md CHANGED
@@ -26,15 +26,17 @@ Requires Node 20+, plus `claude` and/or `codex` on your `PATH` (already logged i
26
26
  |---|---|---|
27
27
  | Project | `init [-y]` | Create the home and the global agent library. [details](docs/commands.md#init) |
28
28
  | Project | `project add <name> --dir <repo>` | Register a project. [details](docs/commands.md#project-add) |
29
- | Project | `project list` / `project remove <name> [--purge]` | List or unregister projects. [details](docs/commands.md#project-list-and-remove) |
29
+ | Project | `project` / `project remove <name> [--purge]` | List or unregister projects. [details](docs/commands.md#project-and-project-remove) |
30
30
  | Diagnose | `doctor [-p name] [--task-file f] [--resolved] [--json]` | Check config (with enforcement levels), git, the lock and runtime CLIs without running agents; `--resolved` prints every effective setting and its source. [details](docs/commands.md#doctor) |
31
31
  | Diagnose | `status [-p name] [--monitor]` | Show agents, mail, the current wake-up and past runs (`--json`, `--monitor`). [details](docs/commands.md#status) |
32
32
  | Run | `run ["task"] [--task-file f] [--agent a] [-p name]` | Give the task to the lead and run until done; with `--agent`, to one member alone. [details](docs/commands.md#run) |
33
33
  | Run | `resume [run-id] [--answer] [-p name]` | Continue an interrupted or failed run; `--answer` first answers the questions it is waiting on. [details](docs/commands.md#resume) |
34
34
  | Run | `clear <run-id> [-p name] [--dry-run] [--keep-worktrees]` / `clear --lock --force` | Delete a run with its task memory and worktrees; or remove the lock a crashed run left behind. [details](docs/commands.md#clear) |
35
35
  | Run | `call <agent> ["task"] [--task-file f] [--dir d]` | Call one global agent on its own, outside any project; prints its answer. [details](docs/commands.md#call) |
36
- | Run | `memory tidy [-p name] [--agent a] [--layer project\|global] [--dry-run]` | Have agents tidy their own memory (manual only; archives, never deletes). [details](docs/commands.md#memory-tidy) |
37
- | Run | `memory restore <timestamp> -p name --agent a` | Undo a tidy: move its archived files back. [details](docs/commands.md#memory-restore) |
36
+ | Run | `tidy [-p name] [--agent a] [--layer project\|global] [--dry-run]` | Have agents tidy their own memory (manual only; archives, never deletes). [details](docs/commands.md#tidy) |
37
+ | Run | `tidy --undo <timestamp> -p name --agent a` | Undo a tidy: move its archived files back. [details](docs/commands.md#tidy) |
38
+
39
+ `agent-lyceum --help` lists each command with a one-line summary; `agent-lyceum <command> --help` prints its full description and options.
38
40
 
39
41
  Ctrl-C cancels a run cleanly and it can be continued with `resume` ([Stopping a run](docs/commands.md#stopping-a-run)). `run` and `resume` exit `0` only when the lead reported `completed`, `2` for partial or blocked, `1` for failed, `3` while waiting for your answers, `130` for cancelled ([Exit codes](docs/commands.md#exit-codes)). Without `-p`, the project is inferred from the current directory ([Choosing the project](docs/commands.md#choosing-the-project)).
40
42
 
package/README.zh-TW.md CHANGED
@@ -26,15 +26,17 @@ npx agent-lyceum status
26
26
  |---|---|---|
27
27
  | 專案 | `init [-y]` | 建立 home 與全域 agent 庫。 [詳細](docs/commands.zh-TW.md#init) |
28
28
  | 專案 | `project add <name> --dir <repo>` | 註冊專案。 [詳細](docs/commands.zh-TW.md#project-add) |
29
- | 專案 | `project list` / `project remove <name> [--purge]` | 列出或取消註冊專案。 [詳細](docs/commands.zh-TW.md#project-list-and-remove) |
29
+ | 專案 | `project` / `project remove <name> [--purge]` | 列出或取消註冊專案。 [詳細](docs/commands.zh-TW.md#project-and-project-remove) |
30
30
  | 診斷 | `doctor [-p name] [--task-file f] [--resolved] [--json]` | 不執行 agent,檢查設定(含防護等級)、git、鎖與 runtime CLI;`--resolved` 印出每項生效設定與其來源。 [詳細](docs/commands.zh-TW.md#doctor) |
31
31
  | 診斷 | `status [-p name] [--monitor]` | 顯示 agent、信件、目前執行與歷次 run(支援 `--json`、`--monitor`)。 [詳細](docs/commands.zh-TW.md#status) |
32
32
  | 執行 | `run ["task"] [--task-file f] [--agent a] [-p name]` | 把任務交給 lead 並執行到結束;加 `--agent` 則只交給某個成員單獨做。 [詳細](docs/commands.zh-TW.md#run) |
33
33
  | 執行 | `resume [run-id] [--answer] [-p name]` | 接續被中斷或失敗的 run;`--answer` 會先回答它等待中的問題。 [詳細](docs/commands.zh-TW.md#resume) |
34
34
  | 執行 | `clear <run-id> [-p name] [--dry-run] [--keep-worktrees]` / `clear --lock --force` | 刪除 run 與其任務記憶、worktree;或移除當機 run 留下的專案鎖。 [詳細](docs/commands.zh-TW.md#clear) |
35
35
  | 執行 | `call <agent> ["task"] [--task-file f] [--dir d]` | 單獨呼叫一個 global agent(不需專案),印出它的回答。 [詳細](docs/commands.zh-TW.md#call) |
36
- | 執行 | `memory tidy [-p name] [--agent a] [--layer project\|global] [--dry-run]` | 讓 agent 整理自己的記憶(只能手動觸發;封存而非刪除)。 [詳細](docs/commands.zh-TW.md#memory-tidy) |
37
- | 執行 | `memory restore <時間戳> -p name --agent a` | 還原一次整理:把封存的檔案搬回。 [詳細](docs/commands.zh-TW.md#memory-restore) |
36
+ | 執行 | `tidy [-p name] [--agent a] [--layer project\|global] [--dry-run]` | 讓 agent 整理自己的記憶(只能手動觸發;封存而非刪除)。 [詳細](docs/commands.zh-TW.md#tidy) |
37
+ | 執行 | `tidy --undo <時間戳> -p name --agent a` | 還原一次整理:把封存的檔案搬回。 [詳細](docs/commands.zh-TW.md#tidy) |
38
+
39
+ `agent-lyceum --help` 只列出每個指令的一行簡述;`agent-lyceum <指令> --help` 會印出該指令的完整說明與選項。
38
40
 
39
41
  Ctrl-C 會乾淨地取消 run,之後可用 `resume` 接續([中止 run](docs/commands.zh-TW.md#中止-run))。`run`/`resume` 只有在 lead 回報 `completed` 時才 exit `0`;`partial`/`blocked` 為 `2`,`failed` 為 `1`,等待你回答時為 `3`,取消為 `130`([退出碼](docs/commands.zh-TW.md#退出碼))。未指定 `-p` 時,專案由目前目錄推斷([選擇專案](docs/commands.zh-TW.md#選擇專案))。
40
42
 
package/dist/cli.js CHANGED
@@ -71,7 +71,17 @@ program
71
71
  console.log(` kept ${path.relative(h, f)}`);
72
72
  console.log(`\nNext: agent-lyceum project add <name> --dir <your-repo>`);
73
73
  });
74
- const project = program.command("project").description("Manage registered projects");
74
+ const project = program
75
+ .command("project")
76
+ .summary("List registered projects, or add / remove one")
77
+ .description("Without a subcommand, list the registered projects.\n\nSubcommands:\n - add <name> --dir <repo>: register a project\n - remove <name> [--purge]: unregister one")
78
+ .action(() => {
79
+ const list = listProjects(home());
80
+ if (!list.length)
81
+ console.log("No projects registered.");
82
+ for (const p of list)
83
+ console.log(`${p.name} -> ${p.dir}`);
84
+ });
75
85
  project
76
86
  .command("add <name>")
77
87
  .description("Register a project (config and context live in the home, not in the repo)")
@@ -85,16 +95,6 @@ project
85
95
  fail(e.message);
86
96
  }
87
97
  });
88
- project
89
- .command("list")
90
- .description("List registered projects")
91
- .action(() => {
92
- const list = listProjects(home());
93
- if (!list.length)
94
- console.log("No projects registered.");
95
- for (const p of list)
96
- console.log(`${p.name} -> ${p.dir}`);
97
- });
98
98
  project
99
99
  .command("remove <name>")
100
100
  .description("Unregister a project (keeps its context unless --purge)")
@@ -222,7 +222,8 @@ async function driveRun(pr, runDir, first, assumeDefaults) {
222
222
  }
223
223
  program
224
224
  .command("run [task]")
225
- .description('Start the dispatcher: give the task to the lead. Use "<task>" or --task-file <path>. With --agent, give it to that one member alone.')
225
+ .summary("Start the dispatcher with a task for the lead")
226
+ .description('Start the dispatcher: give the task to the lead.\n\nGive the task either way:\n - "<task>": as text\n - --task-file <path>: from a file\n\nWho gets it:\n - by default, the lead\n - --agent <name>: that one member alone')
226
227
  .option("-p, --project <name>")
227
228
  .option("--task-file <path>", "read the task from a file")
228
229
  .option("--agent <name>", "run only this member of the project, alone: it cannot mail teammates or ask you, and its own `done` ends the run")
@@ -268,7 +269,8 @@ program
268
269
  });
269
270
  program
270
271
  .command("resume [run-id]")
271
- .description("Continue an interrupted (or failed) run: same run dir, sessions and round count; the task is not re-sent. Without an id, picks the newest run that is not done or running; with an id, continues that run.")
272
+ .summary("Continue an interrupted (or failed) run")
273
+ .description("Continue an interrupted (or failed) run: same run dir, sessions and round count; the task is not re-sent.\n\nWhich run:\n - without an id: the newest run that is not done or running\n - with an id: that run")
272
274
  .option("-p, --project <name>")
273
275
  .option("--answer", "the run is waiting for answers: open ask-reply.md in $EDITOR, check the answers, then continue")
274
276
  .option("--no-edit", "with --answer: do not open the editor; check the file as it is")
@@ -347,9 +349,13 @@ function unlockProject(pr, force) {
347
349
  }
348
350
  program
349
351
  .command("clear [run-id]")
350
- .description("Delete a task (run) by id: its run directory (state, log, result, mailboxes), its task memory and its agent worktrees, branches and snapshots. " +
351
- "Long-term (global and project) memory is never touched. Refuses while the run is running, or while a worktree holds work that is not in the repo. " +
352
- "With --lock (and no run id) it instead removes the run lock a crashed run left behind; that needs --force.")
352
+ .summary("Delete a run by id, or remove a stale run lock")
353
+ .description("Delete a task (run) by id: its run directory (state, log, result, mailboxes), its task memory and its agent worktrees, branches and snapshots.\n\n" +
354
+ "Long-term (global and project) memory is never touched.\n" +
355
+ "Refuses while the run is running, or while a worktree holds work that is not in the repo.\n\n" +
356
+ "With --lock (no run id) it does something else:\n" +
357
+ " - removes the run lock a crashed run left behind\n" +
358
+ " - needs --force to actually remove it")
353
359
  .option("-p, --project <name>")
354
360
  .option("--dry-run", "only list what would be deleted and what would be kept")
355
361
  .option("--lock", "remove the project's run lock left behind by a crashed run (takes no run id; needs --force)")
@@ -412,7 +418,8 @@ program
412
418
  });
413
419
  program
414
420
  .command("doctor")
415
- .description("Check the setup without running any agent: configuration and enforcement levels, git, the project lock, and what each runtime CLI supports (from --version/--help). With --resolved, print every effective setting and where it came from instead.")
421
+ .summary("Check the setup without running any agent")
422
+ .description("Check the setup without running any agent. It checks:\n - configuration and enforcement levels\n - git\n - the project lock\n - what each runtime CLI supports (read from its --version and --help)\n\nWith --resolved it checks nothing and instead prints:\n - every effective setting\n - where each one came from")
416
423
  .option("-p, --project <name>")
417
424
  .option("--task-file <path>", "also check a task file")
418
425
  .option("--resolved", "print the merged settings (global library, project file, defaults) and their sources; runs no other check")
@@ -466,7 +473,8 @@ function printResolved(projectName, json) {
466
473
  }
467
474
  program
468
475
  .command("call <agent> [task]")
469
- .description("Call one agent of the global library on its own, outside any project: it works in --dir (default: the current directory) and its final answer is printed. It cannot mail anyone or ask you.")
476
+ .summary("Call one global-library agent on its own, outside any project")
477
+ .description("Call one agent of the global library on its own, outside any project.\n\n - it works in --dir (default: the current directory)\n - its final answer is printed\n - it cannot mail anyone or ask you")
470
478
  .option("--task-file <path>", "read the task from a file")
471
479
  .option("--dir <path>", "the directory the agent works in (default: the current directory)")
472
480
  .action(async (agentName, task, opts) => {
@@ -499,7 +507,6 @@ program
499
507
  fail(e instanceof ConfigError ? e.message : e.message);
500
508
  }
501
509
  });
502
- const memoryCmd = program.command("memory").description("Look after the agents' long-term memory");
503
510
  function layerOpt(v) {
504
511
  if (v === undefined || v === "project")
505
512
  return "project";
@@ -507,16 +514,44 @@ function layerOpt(v) {
507
514
  return "global";
508
515
  return fail(`--layer must be "project" or "global" (task memory is not tidied: \`clear\` deletes it).`);
509
516
  }
510
- memoryCmd
517
+ program
511
518
  .command("tidy")
512
- .description("Have agents tidy their own memory: merge duplicates, fix stale entries, archive what is obsolete (never deletes). Manual only; nothing triggers it automatically.")
519
+ .summary("Have agents tidy their own memory, or undo one tidy")
520
+ .description("Have agents tidy their own memory: merge duplicates, fix stale entries, archive what is obsolete (never deletes).\n\n" +
521
+ "Manual only; nothing triggers it automatically.\n\n" +
522
+ "With --undo <stamp> it tidies nothing and instead:\n" +
523
+ " - moves that tidy's archived files back (never overwrites)\n" +
524
+ " - re-adds them to MEMORY.md\n" +
525
+ " - needs --agent, and cannot be combined with --dry-run")
513
526
  .option("-p, --project <name>")
514
- .option("--agent <name>", "tidy only this agent (default: every agent)")
527
+ .option("--agent <name>", "tidy only this agent (default: every agent); required with --undo")
515
528
  .option("--layer <layer>", "project (default) or global; global memory is shared by every project, so it is never tidied unless you ask")
516
529
  .option("--dry-run", "only show the memory sizes and what changed in the project; wake nobody, change nothing")
530
+ .option("--undo <stamp>", "undo the tidy with this timestamp: move its archived files back")
517
531
  .action(async (opts) => {
518
532
  try {
519
533
  const layer = layerOpt(opts.layer);
534
+ if (opts.undo !== undefined) {
535
+ if (opts.dryRun)
536
+ fail("--undo cannot be combined with --dry-run.");
537
+ if (!opts.agent)
538
+ fail("--undo needs --agent <name>: an archive belongs to one agent's memory.");
539
+ const target = loadProject(opts.project);
540
+ const undoLease = takeLock(target, `restore-${tidyStamp()}`);
541
+ try {
542
+ const r = restoreMemory({ project: target, agent: opts.agent, layer, stamp: opts.undo });
543
+ for (const f of r.restored)
544
+ console.log(` restored ${f}`);
545
+ for (const s of r.skipped)
546
+ console.log(` skipped ${s.rel}: ${s.reason}`);
547
+ if (!r.restored.length && !r.skipped.length)
548
+ console.log(`Archive ${opts.undo} holds no files to restore.`);
549
+ }
550
+ finally {
551
+ undoLease.release();
552
+ }
553
+ return;
554
+ }
520
555
  const pr = loadProject(opts.project);
521
556
  if (opts.dryRun) {
522
557
  const dry = await tidyMemory({ project: pr, layer, agent: opts.agent, dryRun: true });
@@ -544,7 +579,7 @@ memoryCmd
544
579
  console.log(`No agent has ${layer} memory configured. ${layer === "project" ? "The template gives agents global memory only: set `memory.project` for an agent in project.yaml (or tidy global memory with --layer global)." : "Set `memory.global` for an agent."}`);
545
580
  for (const r of results) {
546
581
  if (r.status === "tidied")
547
- console.log(` ${r.agent} (${r.layer}): tidied. Archive: ${path.join(r.dir, ".archive", r.archive ?? "")} — undo with: agent-lyceum memory restore ${r.archive} -p ${pr.name} --agent ${r.agent}${r.layer === "global" ? " --layer global" : ""}`);
582
+ console.log(` ${r.agent} (${r.layer}): tidied. Archive: ${path.join(r.dir, ".archive", r.archive ?? "")} — undo with: agent-lyceum tidy --undo ${r.archive} -p ${pr.name} --agent ${r.agent}${r.layer === "global" ? " --layer global" : ""}`);
548
583
  else if (r.status === "skipped")
549
584
  console.log(` ${r.agent} (${r.layer}): skipped — ${r.problems.join("; ")}`);
550
585
  else if (r.status === "failed") {
@@ -559,34 +594,6 @@ memoryCmd
559
594
  fail(e instanceof ConfigError ? e.message : e.message);
560
595
  }
561
596
  });
562
- memoryCmd
563
- .command("restore <stamp>")
564
- .description("Move the files of one tidy's archive back (nothing that exists now is overwritten) and re-add them to MEMORY.md")
565
- .option("-p, --project <name>")
566
- .requiredOption("--agent <name>")
567
- .option("--layer <layer>", "project (default) or global")
568
- .action((stamp, opts) => {
569
- try {
570
- const layer = layerOpt(opts.layer);
571
- const pr = loadProject(opts.project);
572
- const lease = takeLock(pr, `restore-${tidyStamp()}`);
573
- try {
574
- const r = restoreMemory({ project: pr, agent: opts.agent, layer, stamp });
575
- for (const f of r.restored)
576
- console.log(` restored ${f}`);
577
- for (const s of r.skipped)
578
- console.log(` skipped ${s.rel}: ${s.reason}`);
579
- if (!r.restored.length && !r.skipped.length)
580
- console.log(`Archive ${stamp} holds no files to restore.`);
581
- }
582
- finally {
583
- lease.release();
584
- }
585
- }
586
- catch (e) {
587
- fail(e.message);
588
- }
589
- });
590
597
  function useColor() {
591
598
  if (process.env.NO_COLOR)
592
599
  return false;
@@ -657,7 +664,7 @@ program
657
664
  const HELP_LAYERS = {
658
665
  "Project:": ["init", "project"],
659
666
  "Diagnose:": ["doctor", "status"],
660
- "Run:": ["run", "resume", "clear", "call", "memory"],
667
+ "Run:": ["run", "resume", "clear", "call", "tidy"],
661
668
  };
662
669
  for (const [heading, names] of Object.entries(HELP_LAYERS))
663
670
  for (const c of program.commands)
package/dist/doctor.js CHANGED
@@ -134,7 +134,7 @@ export async function diagnoseProject(project, opts = {}) {
134
134
  });
135
135
  }
136
136
  for (const a of tidyAdvice(project))
137
- checks.push({ level: "info", subject: "memory", message: `${a.agent}: ${a.reason}. Consider: agent-lyceum memory tidy -p ${project.name} --agent ${a.agent} (doctor only suggests; it never tidies).` });
137
+ checks.push({ level: "info", subject: "memory", message: `${a.agent}: ${a.reason}. Consider: agent-lyceum tidy -p ${project.name} --agent ${a.agent} (doctor only suggests; it never tidies).` });
138
138
  const runtimes = await probeProjectRuntimes(project, opts);
139
139
  checks.push(...judgeRuntimes(project, runtimes));
140
140
  checks.push({ level: "info", subject: "login", message: "Login status is unknown: agent-lyceum does not read credentials, and the CLIs offer no read-only login check it can rely on." });
@@ -88,7 +88,7 @@ export interface TidyResult {
88
88
  }
89
89
  /** Scratch space of one tidy (snapshot and the invocation's logs); outside every memory dir and every run. */
90
90
  export declare const tidyWorkDir: (project: ResolvedProject, stamp: string, agent: string) => string;
91
- /** Tidy the memory of the chosen agents, one after another. Only ever called by a person (`agent-lyceum memory tidy`). */
91
+ /** Tidy the memory of the chosen agents, one after another. Only ever called by a person (`agent-lyceum tidy`). */
92
92
  export declare function tidyMemory(o: TidyOptions): Promise<TidyResult[]>;
93
93
  export interface RestoreResult {
94
94
  restored: string[];
@@ -255,7 +255,7 @@ async function tidyOne(o, t, stamp) {
255
255
  writeTidyState(t.dir, { ...(changes?.head ? { head: changes.head } : {}), at: (o.now?.() ?? new Date()).toISOString(), archive: stamp });
256
256
  return { ...base, status: "tidied", archive: stamp, problems: [] };
257
257
  }
258
- /** Tidy the memory of the chosen agents, one after another. Only ever called by a person (`agent-lyceum memory tidy`). */
258
+ /** Tidy the memory of the chosen agents, one after another. Only ever called by a person (`agent-lyceum tidy`). */
259
259
  export async function tidyMemory(o) {
260
260
  const say = o.say ?? ((s) => console.log(s));
261
261
  const targets = targetsFor(o.project, { layer: o.layer, agent: o.agent });
package/dist/status.d.ts CHANGED
@@ -110,7 +110,7 @@ export interface StatusReport {
110
110
  };
111
111
  agents: AgentReport[];
112
112
  run?: RunReport;
113
- /** Hints that a `memory tidy` might help. Only a suggestion; nothing runs it. */
113
+ /** Hints that a `tidy` might help. Only a suggestion; nothing runs it. */
114
114
  memory_advice?: string[];
115
115
  }
116
116
  export interface TaskListReport {
package/dist/status.js CHANGED
@@ -430,6 +430,6 @@ export function formatStatusReport(report, now = Date.now(), color = false) {
430
430
  if (run)
431
431
  lines.push("", `輸出 tokens:${tokens(run.output_tokens)}`);
432
432
  if (report.memory_advice?.length)
433
- lines.push("", `記憶:${report.memory_advice.join(";")}(建議執行 agent-lyceum memory tidy -p ${report.project.name};status 只提示,不會自動整理)`);
433
+ lines.push("", `記憶:${report.memory_advice.join(";")}(建議執行 agent-lyceum tidy -p ${report.project.name};status 只提示,不會自動整理)`);
434
434
  return lines.join("\n");
435
435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-lyceum",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Config + dispatcher for teams of Claude Code / Codex agents with scoped context and file-based mailboxes",
5
5
  "keywords": [
6
6
  "ai",