projecta-rrr 1.24.4 → 1.24.6

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/AGENTS.md CHANGED
@@ -96,4 +96,4 @@ The RRR loop follows five steps:
96
96
  - Use `$rrr-help` for full skill catalogue
97
97
  - Trigger phrases are case-sensitive: `$rrr-plan-phase` not `$rrr-PlanPhase`
98
98
 
99
- <!-- generated: 2026-05-12T23:35:46.267Z | source: commands/rrr/*.md | count: 59 skills -->
99
+ <!-- generated: 2026-05-13T03:49:42.852Z | source: commands/rrr/*.md | count: 59 skills -->
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to RRR will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.24.6] - 2026-05-13
8
+
9
+ ### Added
10
+ - **Infisical machine-identity auth mode** — `$rrr-provision-coordinator --auth-mode infisical-machine-identity --infisical-project-id <id> --infisical-env <env>` writes a coordinator contract that fetches secrets at boot from Infisical via Universal Auth. Required runtime secrets become `RRR_INFI_CID` + `RRR_INFI_CS` only; Anthropic and GitHub tokens live in Infisical and rotate centrally.
11
+ - **Infisical Sprite bootstrap** — `$rrr-provision-sprites --apply --infisical-project-id <id>` installs Infisical CLI inside each Sprite, writes machine-identity creds, and wires `~/.profile` to fetch `ANTHROPIC_API_KEY` on every login shell. Uses `RRR_INFI_CID` + `RRR_INFI_CS` from the provisioning host's environment.
12
+
13
+ ### Changed
14
+ - **Default coordinator auth mode** — `infisical-machine-identity` is now the default (was `claude-code-subscription`). The subscription path remains available via `--auth-mode claude-code-subscription`.
15
+ - **Coordinator README** — generated README emits the Infisical install + login + secret-fetch boot snippet when in Infisical mode; falls back to credentials-file injection or AI Gateway when in their respective modes.
16
+ - **`$rrr-coordinator-status`** — checks for `RRR_INFI_CID`/`RRR_INFI_CS` env and `infisical --version` when in Infisical mode.
17
+
18
+ ## [1.24.5] - 2026-05-13
19
+
20
+ ### Added
21
+ - **Coordinator auth modes** — `$rrr-provision-coordinator` now writes an `auth` block with `mode: claude-code-subscription` (default) or `mode: anthropic-api-key`. Subscription mode uses `~/.claude/.credentials.json` injected via the Cloudflare Secret `CLAUDE_CREDENTIALS_JSON`, eliminating the need for `ANTHROPIC_API_KEY` on the coordinator.
22
+ - **Cloudflare AI Gateway option** — when `--auth-mode anthropic-api-key`, optional `--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]` flags wire `ANTHROPIC_BASE_URL` to a gateway URL and add `CF_AI_GATEWAY_TOKEN` to required secrets when authenticated.
23
+
24
+ ### Changed
25
+ - **Coordinator README** — generated README now includes the sandbox boot snippet that writes `CLAUDE_CREDENTIALS_JSON` to `~/.claude/.credentials.json`, and only documents AI Gateway when API-key mode is selected.
26
+ - **`$rrr-coordinator-status`** — checks for `~/.claude/.credentials.json` or `CLAUDE_CREDENTIALS_JSON` env when subscription mode is active; falls back to API key / AI Gateway env checks otherwise.
27
+
7
28
  ## [1.24.4] - 2026-05-12
8
29
 
9
30
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: rrr:provision-coordinator
3
3
  description: Create the Cloudflare coordinator contract for RRR team mode integration review
4
- argument-hint: "[--provider cloudflare-sandbox] [--name <coordinator-name>]"
4
+ argument-hint: "[--auth-mode claude-code-subscription|anthropic-api-key] [--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]] [--provider cloudflare-sandbox] [--name <coordinator-name>]"
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Write
@@ -32,8 +32,13 @@ Prepare the repo's coordinator contract so a reusable Cloudflare Claude Code/San
32
32
 
33
33
  </process>
34
34
 
35
+ <auth_modes>
36
+ - `claude-code-subscription` (default) — coordinator runs Claude Code authenticated by an Anthropic subscription. Store the contents of `~/.claude/.credentials.json` as the Cloudflare Secret `CLAUDE_CREDENTIALS_JSON`; sandbox boot writes it to disk so Claude Code resumes the session. No `ANTHROPIC_API_KEY` required.
37
+ - `anthropic-api-key` — coordinator uses `ANTHROPIC_API_KEY` (per-token billing). Pair with `--ai-gateway-account-id <id> --ai-gateway-id <id>` to route Anthropic traffic through Cloudflare AI Gateway for caching, rate limits, and observability. Add `--ai-gateway-authenticated` if the gateway requires `cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN`.
38
+ </auth_modes>
39
+
35
40
  <constraints>
36
41
  - Coordinator is report-first; never auto-merge to the base branch.
37
42
  - Developer work remains in team Sprites and team branches.
38
- - Do not store secrets in planning files.
43
+ - Do not store secrets in planning files — `CLAUDE_CREDENTIALS_JSON` and AI Gateway tokens belong in Cloudflare Secrets.
39
44
  </constraints>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projecta-rrr",
3
- "version": "1.24.4",
3
+ "version": "1.24.6",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
5
5
  "bin": {
6
6
  "projecta-rrr": "bin/install.js",
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const os = require('os');
4
5
  const path = require('path');
5
6
  const { execFileSync } = require('child_process');
6
7
  const { resolvePlanningContext, requireTeamName } = require('./state-router');
@@ -200,6 +201,9 @@ function buildSpriteProvisionPlan(projectRoot, options) {
200
201
  const workdir = spriteWorkdir(config, runtime);
201
202
  const selectedTeams = normalizeList(opts.teams || opts.team);
202
203
  const teams = selectedTeams.length > 0 ? selectedTeams : Object.keys(config.teams || {});
204
+ const infisicalProjectId = opts.infisicalProjectId || null;
205
+ const infisicalEnv = opts.infisicalEnv || 'dev';
206
+ const infisicalEnabled = !!infisicalProjectId;
203
207
  const rows = [];
204
208
 
205
209
  for (const rawTeam of teams) {
@@ -215,6 +219,34 @@ function buildSpriteProvisionPlan(projectRoot, options) {
215
219
  : `git clone <repo-url> ${workdir}`;
216
220
  const checkoutCommand = `gh auth setup-git >/dev/null 2>&1 || true; git fetch origin && git checkout -B ${teamConfig.branch} origin/${config.github.base_branch}`;
217
221
 
222
+ const loaderScript = [
223
+ '[ -f ~/.infisical-machine-creds ] && source ~/.infisical-machine-creds',
224
+ 'if [ -n "$RRR_INFI_CID" ] && [ -n "$RRR_INFI_CS" ]; then',
225
+ ' _IT=$(infisical login --method=universal-auth --client-id="$RRR_INFI_CID" --client-secret="$RRR_INFI_CS" --plain --silent 2>/dev/null)',
226
+ ' if [ -n "$_IT" ]; then',
227
+ ' export ANTHROPIC_API_KEY=$(infisical secrets get ANTHROPIC_API_KEY --token="$_IT" --projectId="$RRR_INFI_PROJ" --env="${RRR_INFI_ENV:-dev}" --plain --silent 2>/dev/null)',
228
+ ' unset _IT',
229
+ ' fi',
230
+ 'fi'
231
+ ].join('\n');
232
+ const credsScript = [
233
+ 'umask 077 && cat > ~/.infisical-machine-creds <<EOF',
234
+ 'export RRR_INFI_CID="$RRR_INFI_CID"',
235
+ 'export RRR_INFI_CS="$RRR_INFI_CS"',
236
+ 'export RRR_INFI_PROJ="$RRR_INFI_PROJ"',
237
+ 'export RRR_INFI_ENV="$RRR_INFI_ENV"',
238
+ 'EOF'
239
+ ].join('\n');
240
+ const writeLoaderCommand = `cat > ~/.infisical-load.sh <<'LOADER'\n${loaderScript}\nLOADER\ngrep -q "infisical-load.sh" ~/.profile || echo "[ -f ~/.infisical-load.sh ] && source ~/.infisical-load.sh" >> ~/.profile`;
241
+ const infisicalCommands = infisicalEnabled ? [
242
+ '# Install Infisical CLI for runtime secret fetch',
243
+ `sprite exec -s ${teamConfig.sprite} -- bash -lc 'command -v infisical || (curl -1sLf "https://artifacts-cli.infisical.com/setup.deb.sh" | sudo -E bash && sudo apt-get install -y infisical)'`,
244
+ '# Write machine-identity credentials (read-only, mode 600)',
245
+ `sprite exec -s ${teamConfig.sprite} --env "RRR_INFI_CID=$RRR_INFI_CID,RRR_INFI_CS=$RRR_INFI_CS,RRR_INFI_PROJ=${infisicalProjectId},RRR_INFI_ENV=${infisicalEnv}" -- bash -lc ${JSON.stringify(credsScript)}`,
246
+ '# Loader fetches ANTHROPIC_API_KEY on every login shell',
247
+ `sprite exec -s ${teamConfig.sprite} -- bash -lc ${JSON.stringify(writeLoaderCommand)}`
248
+ ] : [];
249
+
218
250
  rows.push({
219
251
  team,
220
252
  github_team: teamConfig.github_team,
@@ -225,6 +257,7 @@ function buildSpriteProvisionPlan(projectRoot, options) {
225
257
  repo,
226
258
  setup,
227
259
  checks,
260
+ infisical: infisicalEnabled ? { project_id: infisicalProjectId, env: infisicalEnv } : null,
228
261
  commands: [
229
262
  `sprite create ${teamConfig.sprite} --skip-console`,
230
263
  `sprite exec -s ${teamConfig.sprite} -- mkdir -p /home/sprite/work`,
@@ -233,12 +266,13 @@ function buildSpriteProvisionPlan(projectRoot, options) {
233
266
  `sprite exec -s ${teamConfig.sprite} --dir ${workdir} --env GITHUB_TOKEN=$GITHUB_TOKEN -- bash -lc '${checkoutCommand}'`,
234
267
  `sprite exec -s ${teamConfig.sprite} --dir ${workdir} -- npx projecta-rrr@latest --global --yes`,
235
268
  ...setup.map(cmd => `sprite exec -s ${teamConfig.sprite} --dir ${workdir} -- bash -lc '${cmd}'`),
269
+ ...infisicalCommands,
236
270
  `sprite checkpoint create -s ${teamConfig.sprite} --comment "rrr clean-ready ${config.milestone} ${team}"`
237
271
  ]
238
272
  });
239
273
  }
240
274
 
241
- return { config, runtime, workdir, rows };
275
+ return { config, runtime, workdir, rows, infisical: infisicalEnabled ? { project_id: infisicalProjectId, env: infisicalEnv } : null };
242
276
  }
243
277
 
244
278
  function renderSpriteProvisionPlan(plan) {
@@ -415,6 +449,31 @@ function applySpriteProvisionPlan(projectRoot, options) {
415
449
  const setup = execCapture('sprite', ['exec', '-s', row.sprite, '--dir', row.workdir, '--', 'bash', '-lc', command]);
416
450
  teamResults.push({ step: `setup: ${command}`, ...setup });
417
451
  }
452
+ if (row.infisical && process.env.RRR_INFI_CID && process.env.RRR_INFI_CS) {
453
+ const cid = process.env.RRR_INFI_CID;
454
+ const cs = process.env.RRR_INFI_CS;
455
+ const proj = row.infisical.project_id;
456
+ const env = row.infisical.env;
457
+ const installInfi = execCapture('sprite', [
458
+ 'exec', '-s', row.sprite, '--', 'bash', '-lc',
459
+ 'command -v infisical || (curl -1sLf "https://artifacts-cli.infisical.com/setup.deb.sh" | sudo -E bash >/dev/null 2>&1 && sudo apt-get install -y infisical >/dev/null 2>&1) && infisical --version'
460
+ ]);
461
+ teamResults.push({ step: 'infisical-install', ...installInfi });
462
+ const writeCreds = execCapture('sprite', [
463
+ 'exec', '-s', row.sprite,
464
+ '--env', `RRR_INFI_CID=${cid},RRR_INFI_CS=${cs},RRR_INFI_PROJ=${proj},RRR_INFI_ENV=${env}`,
465
+ '--', 'bash', '-lc',
466
+ 'umask 077 && cat > ~/.infisical-machine-creds <<EOF\nexport RRR_INFI_CID="$RRR_INFI_CID"\nexport RRR_INFI_CS="$RRR_INFI_CS"\nexport RRR_INFI_PROJ="$RRR_INFI_PROJ"\nexport RRR_INFI_ENV="$RRR_INFI_ENV"\nEOF'
467
+ ]);
468
+ teamResults.push({ step: 'infisical-creds', ...writeCreds });
469
+ const writeLoader = execCapture('sprite', [
470
+ 'exec', '-s', row.sprite, '--', 'bash', '-lc',
471
+ 'cat > ~/.infisical-load.sh <<\'LOADER\'\n[ -f ~/.infisical-machine-creds ] && source ~/.infisical-machine-creds\nif [ -n "$RRR_INFI_CID" ] && [ -n "$RRR_INFI_CS" ]; then\n _IT=$(infisical login --method=universal-auth --client-id="$RRR_INFI_CID" --client-secret="$RRR_INFI_CS" --plain --silent 2>/dev/null)\n if [ -n "$_IT" ]; then\n export ANTHROPIC_API_KEY=$(infisical secrets get ANTHROPIC_API_KEY --token="$_IT" --projectId="$RRR_INFI_PROJ" --env="${RRR_INFI_ENV:-dev}" --plain --silent 2>/dev/null)\n unset _IT\n fi\nfi\nLOADER\ngrep -q "infisical-load.sh" ~/.profile || echo "[ -f ~/.infisical-load.sh ] && source ~/.infisical-load.sh" >> ~/.profile'
472
+ ]);
473
+ teamResults.push({ step: 'infisical-loader', ...writeLoader });
474
+ } else if (row.infisical) {
475
+ teamResults.push({ step: 'infisical-skip', ok: false, output: 'infisical bootstrap requested but RRR_INFI_CID/RRR_INFI_CS not set in apply environment' });
476
+ }
418
477
  const readyForCheckpoint = teamResults.every(result => result.ok);
419
478
  const checkpoint = readyForCheckpoint
420
479
  ? execCapture('sprite', [
@@ -514,6 +573,86 @@ function coordinatorConfigPath(projectRoot) {
514
573
  return path.join(projectRoot, '.planning', 'coordinator', 'COORDINATOR.json');
515
574
  }
516
575
 
576
+ function buildAiGatewayBlock(opts) {
577
+ const accountId = opts.aiGatewayAccountId || opts.cfAccountId || null;
578
+ const gatewayId = opts.aiGatewayId || null;
579
+ if (!accountId || !gatewayId) return null;
580
+ const authenticated = opts.aiGatewayAuthenticated === true || opts.aiGatewayAuthenticated === 'true';
581
+ const baseUrl = `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/anthropic`;
582
+ return {
583
+ provider: 'cloudflare-ai-gateway',
584
+ account_id: accountId,
585
+ gateway_id: gatewayId,
586
+ anthropic_base_url: baseUrl,
587
+ authenticated,
588
+ runtime_env: { ANTHROPIC_BASE_URL: baseUrl },
589
+ notes: [
590
+ 'Only used when auth_mode = anthropic-api-key.',
591
+ 'Set ANTHROPIC_BASE_URL to route Anthropic API calls through the gateway.',
592
+ authenticated
593
+ ? 'Authenticated gateway: send `cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN` header on every request.'
594
+ : 'Unauthenticated gateway: ANTHROPIC_API_KEY is the only credential; gateway URL is the access boundary.'
595
+ ]
596
+ };
597
+ }
598
+
599
+ function buildAuthBlock(opts) {
600
+ const mode = opts.authMode || 'infisical-machine-identity';
601
+ if (mode === 'infisical-machine-identity') {
602
+ const projectId = opts.infisicalProjectId || null;
603
+ const env = opts.infisicalEnv || 'dev';
604
+ const domain = opts.infisicalDomain || 'https://app.infisical.com';
605
+ const secrets = opts.infisicalSecrets && opts.infisicalSecrets.length > 0
606
+ ? opts.infisicalSecrets
607
+ : ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN'];
608
+ return {
609
+ mode,
610
+ description: 'Coordinator fetches secrets at boot from Infisical via Universal Auth (machine identity).',
611
+ infisical: {
612
+ domain,
613
+ project_id: projectId,
614
+ env,
615
+ secrets
616
+ },
617
+ required_runtime_env: ['RRR_INFI_CID', 'RRR_INFI_CS'],
618
+ required_secrets: ['RRR_INFI_CID', 'RRR_INFI_CS'],
619
+ notes: [
620
+ 'RRR_INFI_CID + RRR_INFI_CS are Infisical Universal Auth client credentials (stored as Cloudflare Secrets / Wrangler secrets).',
621
+ 'Boot script calls `infisical login --method=universal-auth` then `infisical secrets get` for each declared secret.',
622
+ 'Anthropic, GitHub, and any future tokens rotate centrally in Infisical with no coordinator redeploy.'
623
+ ]
624
+ };
625
+ }
626
+ if (mode === 'claude-code-subscription') {
627
+ return {
628
+ mode,
629
+ description: 'Coordinator uses Claude Code with an Anthropic subscription (Max/Pro). No ANTHROPIC_API_KEY required.',
630
+ credentials_path: '~/.claude/.credentials.json',
631
+ injection: [
632
+ 'Persist the credentials file once (after a one-time `claude login`).',
633
+ 'For a Cloudflare sandbox: write the stored credentials JSON to ~/.claude/.credentials.json on container start (from a CF Secret, KV, or D1 row).',
634
+ 'For a Sprite: run `claude login` once interactively, then `sprite checkpoint create` to capture the authenticated state.'
635
+ ],
636
+ required_secrets: ['GITHUB_TOKEN', 'CLAUDE_CREDENTIALS_JSON'],
637
+ notes: [
638
+ 'CLAUDE_CREDENTIALS_JSON holds the contents of ~/.claude/.credentials.json — store as a CF Secret, not in repo.',
639
+ 'Subscription billing applies; no per-token API cost.'
640
+ ]
641
+ };
642
+ }
643
+ if (mode === 'anthropic-api-key') {
644
+ return {
645
+ mode,
646
+ description: 'Coordinator authenticates Claude Code with ANTHROPIC_API_KEY (per-token billing).',
647
+ required_secrets: ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN'],
648
+ notes: [
649
+ 'Optional: pair with Cloudflare AI Gateway via --ai-gateway-account-id/--ai-gateway-id for caching, rate limits, and observability.'
650
+ ]
651
+ };
652
+ }
653
+ throw new Error(`unknown auth_mode: ${mode}`);
654
+ }
655
+
517
656
  function writeCoordinatorProvision(options) {
518
657
  const opts = options || {};
519
658
  const projectRoot = opts.projectRoot || process.cwd();
@@ -521,8 +660,13 @@ function writeCoordinatorProvision(options) {
521
660
  const runtime = readRuntimeManifest(projectRoot);
522
661
  const dir = path.join(projectRoot, '.planning', 'coordinator');
523
662
  ensureDir(dir);
663
+ const auth = buildAuthBlock(opts);
664
+ const aiGateway = auth.mode === 'anthropic-api-key' ? buildAiGatewayBlock(opts) : null;
665
+ const requiredSecrets = [...auth.required_secrets];
666
+ if (aiGateway && aiGateway.authenticated) requiredSecrets.push('CF_AI_GATEWAY_TOKEN');
667
+
524
668
  const config = {
525
- schema_version: 1,
669
+ schema_version: 2,
526
670
  provider: opts.provider || 'cloudflare-sandbox',
527
671
  name: opts.name || `rrr-coordinator-${repoNameFor(teamConfig).toLowerCase()}`,
528
672
  generated_at: new Date().toISOString(),
@@ -532,7 +676,9 @@ function writeCoordinatorProvision(options) {
532
676
  integration_branch: teamConfig.github.integration_branch,
533
677
  runtime_manifest: path.relative(projectRoot, runtimeManifestPath(projectRoot)),
534
678
  report_first: true,
535
- required_secrets: ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN'],
679
+ auth,
680
+ ai_gateway: aiGateway,
681
+ required_secrets: requiredSecrets,
536
682
  responsibilities: [
537
683
  'clone repository into ephemeral sandbox',
538
684
  'merge team branches into integration branch',
@@ -542,6 +688,7 @@ function writeCoordinatorProvision(options) {
542
688
  ]
543
689
  };
544
690
  writeJson(coordinatorConfigPath(projectRoot), config);
691
+
545
692
  const readme = [
546
693
  `# ${config.name}`,
547
694
  '',
@@ -554,11 +701,98 @@ function writeCoordinatorProvision(options) {
554
701
  `Base branch: ${config.base_branch}`,
555
702
  `Integration branch: ${config.integration_branch}`,
556
703
  `Runtime manifest: ${config.runtime_manifest}`,
704
+ '',
705
+ '## Auth',
706
+ '',
707
+ `Mode: \`${auth.mode}\``,
708
+ '',
709
+ auth.description
710
+ ];
711
+
712
+ if (auth.mode === 'infisical-machine-identity') {
713
+ const infi = auth.infisical;
714
+ const fetches = (infi.secrets || []).map(s =>
715
+ `export ${s}=$(infisical secrets get ${s} --token="$_IT" --projectId="${infi.project_id || '<INFISICAL_PROJECT_ID>'}" --env="${infi.env}" --plain --silent)`
716
+ );
717
+ readme.push(
718
+ '',
719
+ '### Infisical machine-identity bootstrap',
720
+ '',
721
+ `- Domain: ${infi.domain}`,
722
+ `- Project ID: ${infi.project_id || '(unset — pass --infisical-project-id)'}`,
723
+ `- Env: ${infi.env}`,
724
+ `- Fetched secrets: ${(infi.secrets || []).join(', ') || '(none)'}`,
725
+ '',
726
+ 'Cloudflare sandbox boot snippet:',
727
+ '',
728
+ '```bash',
729
+ '# Install Infisical CLI (one-time, ideally baked into image)',
730
+ 'curl -1sLf "https://artifacts-cli.infisical.com/setup.deb.sh" | sudo -E bash',
731
+ 'sudo apt-get install -y infisical',
732
+ '',
733
+ '# Exchange machine-identity credentials for an access token',
734
+ '_IT=$(infisical login --method=universal-auth --client-id="$RRR_INFI_CID" --client-secret="$RRR_INFI_CS" --plain --silent)',
735
+ '',
736
+ '# Fetch and export each declared secret',
737
+ ...fetches,
738
+ 'unset _IT',
739
+ '',
740
+ 'claude --version # picks up ANTHROPIC_API_KEY automatically',
741
+ '```'
742
+ );
743
+ } else if (auth.mode === 'claude-code-subscription') {
744
+ readme.push(
745
+ '',
746
+ '### Credentials injection',
747
+ '',
748
+ ...auth.injection.map(line => `- ${line}`),
749
+ '',
750
+ 'Cloudflare sandbox boot snippet:',
751
+ '',
752
+ '```bash',
753
+ 'mkdir -p ~/.claude',
754
+ 'printf "%s" "$CLAUDE_CREDENTIALS_JSON" > ~/.claude/.credentials.json',
755
+ 'chmod 600 ~/.claude/.credentials.json',
756
+ 'claude --version # verifies authenticated',
757
+ '```'
758
+ );
759
+ }
760
+
761
+ readme.push(
557
762
  '',
558
763
  '## Required Secrets',
559
764
  '',
560
- '- `ANTHROPIC_API_KEY` for Claude Code.',
561
- '- `GITHUB_TOKEN` or GitHub App installation token for read/write repo access.',
765
+ ...requiredSecrets.map(s => `- \`${s}\``)
766
+ );
767
+
768
+ if (aiGateway) {
769
+ readme.push(
770
+ '',
771
+ '## Cloudflare AI Gateway (optional)',
772
+ '',
773
+ `Account: ${aiGateway.account_id}`,
774
+ `Gateway: ${aiGateway.gateway_id}`,
775
+ `Anthropic base URL: ${aiGateway.anthropic_base_url}`,
776
+ `Authenticated: ${aiGateway.authenticated ? 'yes' : 'no'}`,
777
+ '',
778
+ '```bash',
779
+ `export ANTHROPIC_BASE_URL="${aiGateway.anthropic_base_url}"`,
780
+ 'export ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"',
781
+ aiGateway.authenticated
782
+ ? '# header: cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN'
783
+ : '# unauthenticated gateway — URL is the access boundary',
784
+ '```'
785
+ );
786
+ } else if (auth.mode === 'anthropic-api-key') {
787
+ readme.push(
788
+ '',
789
+ '## Cloudflare AI Gateway',
790
+ '',
791
+ 'Not configured. Re-run with `--ai-gateway-account-id <id> --ai-gateway-id <id>` to route Anthropic API traffic through Cloudflare AI Gateway.'
792
+ );
793
+ }
794
+
795
+ readme.push(
562
796
  '',
563
797
  '## Execution Contract',
564
798
  '',
@@ -566,8 +800,9 @@ function writeCoordinatorProvision(options) {
566
800
  '- GitHub remains the source of truth for branches, PRs, reviews, and checks.',
567
801
  '- Coordinator is report-first: it writes reports and recommendations only; humans approve final merges.',
568
802
  '- Developer work stays in Sprites/team branches.'
569
- ].join('\n');
570
- atomicWrite(path.join(dir, 'README.md'), `${readme}\n`);
803
+ );
804
+
805
+ atomicWrite(path.join(dir, 'README.md'), `${readme.join('\n')}\n`);
571
806
  return { config, runtime, dir };
572
807
  }
573
808
 
@@ -579,8 +814,53 @@ function readCoordinatorStatus(projectRoot) {
579
814
  checks.push({ name: 'runtime manifest', ok: fs.existsSync(runtimeManifestPath(root)), detail: path.relative(root, runtimeManifestPath(root)) });
580
815
  const wrangler = execCapture('npx', ['wrangler', '--version'], { cwd: root });
581
816
  checks.push({ name: 'wrangler available', ok: wrangler.ok, detail: wrangler.output || 'not available' });
582
- checks.push({ name: 'ANTHROPIC_API_KEY present', ok: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? 'present' : 'missing locally' });
583
817
  checks.push({ name: 'GITHUB_TOKEN present', ok: !!process.env.GITHUB_TOKEN, detail: process.env.GITHUB_TOKEN ? 'present' : 'missing locally' });
818
+
819
+ const authMode = config && config.auth ? config.auth.mode : (config && config.ai_gateway ? 'anthropic-api-key' : 'infisical-machine-identity');
820
+ if (authMode === 'infisical-machine-identity') {
821
+ checks.push({
822
+ name: 'RRR_INFI_CID present',
823
+ ok: !!process.env.RRR_INFI_CID,
824
+ detail: process.env.RRR_INFI_CID ? 'present' : 'missing locally (Infisical Universal Auth client ID)'
825
+ });
826
+ checks.push({
827
+ name: 'RRR_INFI_CS present',
828
+ ok: !!process.env.RRR_INFI_CS,
829
+ detail: process.env.RRR_INFI_CS ? 'present' : 'missing locally (Infisical Universal Auth client secret)'
830
+ });
831
+ const infiCli = execCapture('infisical', ['--version']);
832
+ checks.push({
833
+ name: 'infisical CLI available',
834
+ ok: infiCli.ok,
835
+ detail: infiCli.output || 'not installed locally; coordinator runtime must install it'
836
+ });
837
+ } else if (authMode === 'claude-code-subscription') {
838
+ const credsPath = path.join(os.homedir(), '.claude', '.credentials.json');
839
+ const localCreds = fs.existsSync(credsPath);
840
+ checks.push({
841
+ name: 'Claude Code subscription credentials',
842
+ ok: localCreds || !!process.env.CLAUDE_CREDENTIALS_JSON,
843
+ detail: localCreds ? credsPath : (process.env.CLAUDE_CREDENTIALS_JSON ? 'CLAUDE_CREDENTIALS_JSON env present' : 'missing locally (run `claude login` or set CLAUDE_CREDENTIALS_JSON)')
844
+ });
845
+ } else {
846
+ checks.push({ name: 'ANTHROPIC_API_KEY present', ok: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? 'present' : 'missing locally' });
847
+ if (config && config.ai_gateway) {
848
+ const expectedBase = config.ai_gateway.anthropic_base_url;
849
+ const baseUrl = process.env.ANTHROPIC_BASE_URL;
850
+ checks.push({
851
+ name: 'ANTHROPIC_BASE_URL routes to AI Gateway',
852
+ ok: baseUrl === expectedBase,
853
+ detail: baseUrl ? (baseUrl === expectedBase ? expectedBase : `mismatch: ${baseUrl}`) : `missing locally (expected ${expectedBase})`
854
+ });
855
+ if (config.ai_gateway.authenticated) {
856
+ checks.push({
857
+ name: 'CF_AI_GATEWAY_TOKEN present',
858
+ ok: !!process.env.CF_AI_GATEWAY_TOKEN,
859
+ detail: process.env.CF_AI_GATEWAY_TOKEN ? 'present' : 'missing locally (authenticated gateway requires it)'
860
+ });
861
+ }
862
+ }
863
+ }
584
864
  return { config, checks };
585
865
  }
586
866
 
@@ -73,7 +73,7 @@ Usage:
73
73
  node scripts/rrr-team-mode.js runtime-init
74
74
  node scripts/rrr-team-mode.js provision-sprites [--apply] [--teams team-runtime]
75
75
  node scripts/rrr-team-mode.js sprite-status [--live]
76
- node scripts/rrr-team-mode.js provision-coordinator
76
+ node scripts/rrr-team-mode.js provision-coordinator [--auth-mode claude-code-subscription|anthropic-api-key] [--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]]
77
77
  node scripts/rrr-team-mode.js coordinator-status
78
78
 
79
79
  Notes:
@@ -189,7 +189,9 @@ function main() {
189
189
  if (command === 'provision-sprites') {
190
190
  const options = {
191
191
  teams: args.teams,
192
- team: args.team
192
+ team: args.team,
193
+ infisicalProjectId: args['infisical-project-id'],
194
+ infisicalEnv: args['infisical-env']
193
195
  };
194
196
  if (args.apply === true || args.apply === 'true') {
195
197
  const result = applySpriteProvisionPlan(projectRoot, options);
@@ -224,10 +226,25 @@ function main() {
224
226
  const result = writeCoordinatorProvision({
225
227
  projectRoot,
226
228
  provider: args.provider,
227
- name: args.name
229
+ name: args.name,
230
+ authMode: args['auth-mode'] || args.authMode,
231
+ infisicalProjectId: args['infisical-project-id'],
232
+ infisicalEnv: args['infisical-env'],
233
+ infisicalDomain: args['infisical-domain'],
234
+ infisicalSecrets: splitCsv(args['infisical-secrets']),
235
+ aiGatewayAccountId: args['ai-gateway-account-id'] || args['cf-account-id'],
236
+ aiGatewayId: args['ai-gateway-id'],
237
+ aiGatewayAuthenticated: args['ai-gateway-authenticated'] === true || args['ai-gateway-authenticated'] === 'true'
228
238
  });
229
239
  console.log(`Wrote ${path.relative(projectRoot, path.join(result.dir, 'COORDINATOR.json'))}`);
230
240
  console.log(`Wrote ${path.relative(projectRoot, path.join(result.dir, 'README.md'))}`);
241
+ console.log(`Auth mode: ${result.config.auth.mode}`);
242
+ if (result.config.auth && result.config.auth.infisical) {
243
+ console.log(`Infisical: ${result.config.auth.infisical.project_id || '(unset)'}@${result.config.auth.infisical.env}`);
244
+ }
245
+ if (result.config.ai_gateway) {
246
+ console.log(`AI Gateway: ${result.config.ai_gateway.anthropic_base_url}`);
247
+ }
231
248
  return;
232
249
  }
233
250