@sublang/playbook 18.0.1 → 18.1.0

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
@@ -32,8 +32,9 @@ Run `playbook` for an interactive tmux UI powered by [cligent](https://github.co
32
32
  Out of the box, Playbook includes **CODE** for implementation, **REVIEW** for commit-based review and fixes, **DECIDE** for independently proposed and reviewed specification decisions, **DEV** for repository-aware planning of a development request, **BRANCH** for checking out a new branch for a GitHub issue, **PR** for publishing, checking, and merging that branch through a pull request, and **INSPECT** for explaining supplied material or a running interface without changing the repository.
33
33
  CODE and DECIDE call REVIEW as a nested playbook; DEV calls CODE, or DECIDE and then CODE, and wraps them in BRANCH and PR when the request names a GitHub issue or asks for a pull request.
34
34
 
35
- The shared starter config selects one adapter for the Captain and all players
36
- from locally visible credentials: Claude first, then Codex. If neither is
35
+ The shared starter config selects the Captain's and players' adapter from
36
+ locally visible credentials: Claude first, then Codex, and where both are
37
+ configured only `dev.reviewer` seeds on Codex. If neither is
37
38
  configured, it seeds Claude and prints a notice. CODE, REVIEW, DECIDE, BRANCH, and PR bind
38
39
  their local roles explicitly to `dev.coder` and `dev.reviewer`, so nested and later
39
40
  engagements share a conversation only where their bindings name the same
@@ -51,7 +52,7 @@ If an SDK is missing or older than cligent supports, Playbook prints the pinned
51
52
  Prerequisites:
52
53
 
53
54
  - Node.js >= 20.6.0
54
- - Credentials for the configured adapter: authenticated [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) or `ANTHROPIC_API_KEY` for Claude; authenticated [Codex CLI](https://github.com/openai/codex) or `OPENAI_API_KEY` for Codex
55
+ - Credentials for the configured adapter: authenticated [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) or `ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN` for Claude; authenticated [Codex CLI](https://github.com/openai/codex) or `OPENAI_API_KEY` / `CODEX_ACCESS_TOKEN` for Codex
55
56
  - [GitHub CLI](https://cli.github.com/) (`gh`) authenticated for the repository's GitHub remote, for `/branch`, `/pr`, and `/dev` requests that name an issue or ask for a pull request
56
57
 
57
58
  Interactive `playbook` additionally needs tmux 3.3 or newer and [`glow`](https://github.com/charmbracelet/glow#installation) on `PATH`; headless `playbook run` does not.
@@ -64,16 +64,23 @@ one; see [Modules supplied at launch](#modules-supplied-at-launch). Every
64
64
  manifest role must be present exactly once. The launcher injects the rest — you do not write host wiring by
65
65
  hand.
66
66
 
67
- The launcher seeds one adapter for the Captain and all stable players from
68
- locally visible credentials: Claude first, then Codex. It uses Claude Opus 5.5
69
- for Claude or GPT-6 Sol for Codex, with Captain and Coder at `high` effort
70
- and Reviewer and Analyst at `xhigh`. If neither adapter is configured, it
71
- seeds Claude and prints a notice. Credentials do not prove SDK availability
72
- or remaining quota; launch still checks the configured adapter. Existing
73
- configs are left unchanged.
67
+ The launcher seeds the Captain and all stable players from locally visible
68
+ credentials (an API key in the environment, or `~/.claude` / `~/.codex`):
74
69
 
75
- The Claude default is shown below. A Codex seed also adds
76
- `permissions.writablePaths: ['.git']` to each agent:
70
+ | Credentials | Seeded agents |
71
+ | --- | --- |
72
+ | Claude only, or neither | every agent on Claude Opus 5.5 (with a notice when neither is configured) |
73
+ | Codex only | every agent on GPT-6 Sol |
74
+ | both | `dev.reviewer` on GPT-6 Sol, every other agent on Claude Opus 5.5 |
75
+
76
+ Captain, Coder, and Inspector run at `high` effort and Reviewer and Analyst at
77
+ `xhigh`. Credentials do not prove SDK availability or remaining quota; launch
78
+ still checks the configured adapter. Existing configs are left unchanged.
79
+ Embedding hosts seed the same agent settings through
80
+ `seedConfigText({ env, homeDir })` from `@sublang/playbook/seed-config`.
81
+
82
+ The Claude default is shown below. Each Codex agent also carries
83
+ `permissions.writablePaths: ['.git']`:
77
84
 
78
85
  ```yaml
79
86
  captain:
@@ -273,9 +280,12 @@ It may not finish the specialist’s task or invent a decision; discarding work
273
280
  Preparation has a 150-second limit.
274
281
 
275
282
  Adapter readiness is intentionally light: `claude` is ready with local
276
- Claude Code auth or `ANTHROPIC_API_KEY`; `codex` with local Codex CLI
277
- auth or `OPENAI_API_KEY`. A known adapter that is not ready blocks the
278
- launch and prints the help text.
283
+ Claude Code auth, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or
284
+ `CLAUDE_CODE_OAUTH_TOKEN`; `codex` with local Codex CLI auth,
285
+ `OPENAI_API_KEY`, `CODEX_API_KEY`, or `CODEX_ACCESS_TOKEN`. Only presence
286
+ counts; no token is checked. A known adapter that is not ready blocks the
287
+ launch and prints the help text. The same check picks the starter config's
288
+ lineup.
279
289
 
280
290
  ## Skills across agents
281
291
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sublang/playbook",
3
- "version": "18.0.1",
3
+ "version": "18.1.0",
4
4
  "type": "module",
5
5
  "description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, DECIDE, DEV, BRANCH, PR, and INSPECT workflows driven by GEARS specs.",
6
6
  "license": "Apache-2.0",
@@ -83,6 +83,8 @@
83
83
  "reference/sdlc/code.playbook/host-capabilities.d.ts",
84
84
  "reference/sdlc/code.playbook/machine-identity.js",
85
85
  "reference/sdlc/code.playbook/machine-identity.d.ts",
86
+ "reference/sdlc/code.playbook/seed-config.js",
87
+ "reference/sdlc/code.playbook/seed-config.d.ts",
86
88
  "reference/sdlc/code.playbook/code.gears.md",
87
89
  "reference/sdlc/code.playbook/playbook.config.template.yaml",
88
90
  "reference/sdlc/code.playbook/bin/playbook.js",
@@ -212,6 +214,10 @@
212
214
  "types": "./reference/sdlc/code.playbook/machine-identity.d.ts",
213
215
  "default": "./reference/sdlc/code.playbook/machine-identity.js"
214
216
  },
217
+ "./seed-config": {
218
+ "types": "./reference/sdlc/code.playbook/seed-config.d.ts",
219
+ "default": "./reference/sdlc/code.playbook/seed-config.js"
220
+ },
215
221
  "./slc/*": "./slc/*",
216
222
  "./review/playbook": {
217
223
  "types": "./reference/sdlc/review.playbook/review.playbook.d.ts",
@@ -1438,23 +1438,36 @@ export function adaptersFromComposedConfig(config) {
1438
1438
  return [...adapters];
1439
1439
  }
1440
1440
 
1441
+ // DR-097: each agent's documented environment forms, any one of which, or the
1442
+ // agent's sign-in folder, counts it as ready. No value is read.
1443
+ const CREDENTIAL_FORMS = {
1444
+ claude: {
1445
+ names: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN"],
1446
+ folder: ".claude",
1447
+ },
1448
+ codex: {
1449
+ names: ["OPENAI_API_KEY", "CODEX_API_KEY", "CODEX_ACCESS_TOKEN"],
1450
+ folder: ".codex",
1451
+ },
1452
+ };
1453
+
1441
1454
  export function checkReadiness(adapters, env = process.env, home = homedir()) {
1442
1455
  const failingAdapters = [];
1443
1456
  const unknownAdapters = [];
1444
1457
  for (const adapter of adapters) {
1445
- if (adapter === "claude") {
1446
- if (!env.ANTHROPIC_API_KEY && !existsSync(join(home, ".claude"))) {
1447
- failingAdapters.push(adapter);
1448
- }
1458
+ const forms = Object.hasOwn(CREDENTIAL_FORMS, adapter)
1459
+ ? CREDENTIAL_FORMS[adapter]
1460
+ : undefined;
1461
+ if (!forms) {
1462
+ unknownAdapters.push(adapter);
1449
1463
  continue;
1450
1464
  }
1451
- if (adapter === "codex") {
1452
- if (!env.OPENAI_API_KEY && !existsSync(join(home, ".codex"))) {
1453
- failingAdapters.push(adapter);
1454
- }
1455
- continue;
1465
+ if (
1466
+ !forms.names.some((name) => env[name]) &&
1467
+ !existsSync(join(home, forms.folder))
1468
+ ) {
1469
+ failingAdapters.push(adapter);
1456
1470
  }
1457
- unknownAdapters.push(adapter);
1458
1471
  }
1459
1472
  return { failingAdapters, unknownAdapters };
1460
1473
  }
@@ -1651,9 +1664,14 @@ const SEED_LINEUP = Object.freeze([
1651
1664
  Object.freeze({ adapter: "codex", model: "gpt-6-sol" }),
1652
1665
  ]);
1653
1666
 
1667
+ // DR-096: where both adapters are ready, this player alone takes the
1668
+ // second row, so review comes from a different provider than the work.
1669
+ const SECOND_ADAPTER_PLAYER = "dev.reviewer";
1670
+
1654
1671
  /**
1655
1672
  * PBCLI-11: the lineup a seed would write here, and whether anything
1656
- * actually probed ready.
1673
+ * actually probed ready. `reviewer` is the row `dev.reviewer` takes;
1674
+ * every other agent takes `lineup`.
1657
1675
  *
1658
1676
  * Only the credential check participates. The SDK probe constructs an
1659
1677
  * adapter and awaits `isAvailable()`, which reads whatever happens to
@@ -1666,7 +1684,12 @@ export function selectSeedLineup(env = process.env, home = homedir()) {
1666
1684
  (entry) =>
1667
1685
  checkReadiness([entry.adapter], env, home).failingAdapters.length === 0,
1668
1686
  );
1669
- return { lineup: ready[0] ?? SEED_LINEUP[0], anyReady: ready.length > 0 };
1687
+ const lineup = ready[0] ?? SEED_LINEUP[0];
1688
+ return {
1689
+ lineup,
1690
+ reviewer: ready.length > 1 ? ready[1] : lineup,
1691
+ anyReady: ready.length > 0,
1692
+ };
1670
1693
  }
1671
1694
 
1672
1695
  /**
@@ -1675,9 +1698,7 @@ export function selectSeedLineup(env = process.env, home = homedir()) {
1675
1698
  * byte copy it always was. A different adapter rewrites the agent
1676
1699
  * scalars in place, which the Document API carries comments through.
1677
1700
  */
1678
- function seedTextForLineup(templatePath, lineup) {
1679
- const template = readFileSync(templatePath, "utf8");
1680
- if (lineup.adapter === SEED_LINEUP[0].adapter) return template;
1701
+ function seedTextForLineup(template, { lineup, reviewer }) {
1681
1702
  const doc = parseYamlDocument(template);
1682
1703
  const players = doc.getIn(["players"]);
1683
1704
  const agentPaths = [
@@ -1687,18 +1708,35 @@ function seedTextForLineup(templatePath, lineup) {
1687
1708
  String(pair.key),
1688
1709
  ]),
1689
1710
  ];
1711
+ let changed = false;
1690
1712
  for (const path of agentPaths) {
1691
- doc.setIn([...path, "adapter"], lineup.adapter);
1692
- doc.setIn([...path, "model"], lineup.model);
1713
+ const entry = path[1] === SECOND_ADAPTER_PLAYER ? reviewer : lineup;
1714
+ if (entry.adapter === SEED_LINEUP[0].adapter) continue;
1715
+ changed = true;
1716
+ doc.setIn([...path, "adapter"], entry.adapter);
1717
+ doc.setIn([...path, "model"], entry.model);
1693
1718
  // A codex agent writes git metadata under its sandbox; claude's
1694
1719
  // auto mode needs no grant, so the key exists for one adapter only.
1695
- if (lineup.adapter === "codex") {
1696
- doc.setIn([...path, "permissions", "writablePaths"], [".git"]);
1697
- } else {
1698
- doc.deleteIn([...path, "permissions", "writablePaths"]);
1699
- }
1720
+ doc.setIn([...path, "permissions", "writablePaths"], [".git"]);
1700
1721
  }
1701
- return String(doc);
1722
+ return changed ? String(doc) : template;
1723
+ }
1724
+
1725
+ /**
1726
+ * PBCLI-100: the starter text a seed writes under this environment and
1727
+ * home. The public `seed-config` facade and the launcher both call it,
1728
+ * so every host seeding through it selects the same agent settings.
1729
+ */
1730
+ export function seedConfigText(
1731
+ templatePath,
1732
+ env = process.env,
1733
+ homeDir = env.HOME ?? homedir(),
1734
+ ) {
1735
+ const selection = selectSeedLineup(env, homeDir);
1736
+ return {
1737
+ text: seedTextForLineup(readFileSync(templatePath, "utf8"), selection),
1738
+ anyReady: selection.anyReady,
1739
+ };
1702
1740
  }
1703
1741
 
1704
1742
  function seedUserConfigIfMissing(
@@ -1710,21 +1748,13 @@ function seedUserConfigIfMissing(
1710
1748
  ) {
1711
1749
  if (existsSync(userConfigPath)) return;
1712
1750
  mkdirSync(dirname(userConfigPath), { recursive: true });
1713
- const { lineup, anyReady } = selectSeedLineup(env, homeDir);
1714
- if (lineup.adapter === SEED_LINEUP[0].adapter && anyReady) {
1715
- // Unchanged default path: one exclusive create of the template bytes.
1716
- copyFileSync(templatePath, userConfigPath, constants.COPYFILE_EXCL);
1717
- } else {
1718
- // Still one exclusive create, so two seeding calls cannot disagree.
1719
- writeFileSync(userConfigPath, seedTextForLineup(templatePath, lineup), {
1720
- encoding: "utf8",
1721
- flag: "wx",
1722
- });
1723
- }
1751
+ const { text, anyReady } = seedConfigText(templatePath, env, homeDir);
1752
+ // One exclusive create, so two seeding calls cannot disagree.
1753
+ writeFileSync(userConfigPath, text, { encoding: "utf8", flag: "wx" });
1724
1754
  onNotice(`playbook: created config at ${userConfigPath}\n`);
1725
1755
  if (!anyReady) {
1726
1756
  onNotice(
1727
- `playbook: no adapter probed ready; seeded ${lineup.adapter}\n`,
1757
+ `playbook: no adapter probed ready; seeded ${SEED_LINEUP[0].adapter}\n`,
1728
1758
  );
1729
1759
  }
1730
1760
  }
@@ -993,9 +993,9 @@ function helpText({
993
993
  "",
994
994
  "Adapter setup:",
995
995
  " claude: npm install -g @anthropic-ai/claude-agent-sdk, then run",
996
- " Claude Code once or set ANTHROPIC_API_KEY.",
996
+ " Claude Code once or set ANTHROPIC_API_KEY (or CLAUDE_CODE_OAUTH_TOKEN).",
997
997
  " codex: npm install -g @openai/codex-sdk, then run Codex CLI once",
998
- " or set OPENAI_API_KEY.",
998
+ " or set OPENAI_API_KEY (or CODEX_API_KEY, CODEX_ACCESS_TOKEN).",
999
999
  " Each SDK is an optional peer dependency, so you install only the",
1000
1000
  " vendors your config actually names.",
1001
1001
  "",
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ export interface SeedConfigOptions {
5
+ readonly env?: NodeJS.ProcessEnv;
6
+ readonly homeDir?: string;
7
+ }
8
+ export interface SeedConfigText {
9
+ readonly text: string;
10
+ readonly anyReady: boolean;
11
+ }
12
+ export declare function seedConfigText(options?: SeedConfigOptions): SeedConfigText;
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ // DR-096: the public `@sublang/playbook/seed-config` facade. The text is
5
+ // the launcher's own seed (playbook-cli-11); this module only fixes the
6
+ // bundled template and narrows the accepted options (playbook-cli-100).
7
+
8
+ import { fileURLToPath } from 'node:url';
9
+ import { seedConfigText as seedText } from './bin/launch-config.js';
10
+
11
+ const TEMPLATE_PATH = fileURLToPath(
12
+ new URL('./playbook.config.template.yaml', import.meta.url),
13
+ );
14
+
15
+ export function seedConfigText(options = {}) {
16
+ const env = options.env ?? process.env;
17
+ return seedText(
18
+ TEMPLATE_PATH,
19
+ env,
20
+ ...(options.homeDir !== undefined ? [options.homeDir] : []),
21
+ );
22
+ }