orizu 0.4.1 → 0.5.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.
Files changed (76) hide show
  1. package/README.md +22 -13
  2. package/dist/artifact-archive.js +152 -0
  3. package/dist/connectors-cli.js +80 -0
  4. package/dist/credentials.js +4 -1
  5. package/dist/daytona-slice-rehearsal.js +255 -0
  6. package/dist/daytona-slice.js +441 -0
  7. package/dist/egress-allowlist-cli.js +132 -0
  8. package/dist/egress-policy.js +159 -0
  9. package/dist/git-credential.js +131 -0
  10. package/dist/github-setup.js +226 -0
  11. package/dist/help.js +271 -16
  12. package/dist/hosted-bootstrap-rehearsal.js +235 -0
  13. package/dist/hosted-bootstrap.js +365 -0
  14. package/dist/hosted-broker-server.js +220 -0
  15. package/dist/hosted-git-http-server.js +136 -0
  16. package/dist/hosted-harness-claude.js +493 -0
  17. package/dist/hosted-harness-opencode.js +729 -0
  18. package/dist/hosted-harness.js +60 -0
  19. package/dist/hosted-loop.js +477 -0
  20. package/dist/hosted-run-event-sink.js +499 -0
  21. package/dist/hosted-runtime-assets.js +216 -0
  22. package/dist/hosted-session-cli.js +642 -0
  23. package/dist/index.js +855 -237
  24. package/dist/kill-switch-cli.js +89 -0
  25. package/dist/manifests-cli.js +143 -0
  26. package/dist/preview-runtime.js +4 -1
  27. package/dist/sandbox-provider.js +238 -0
  28. package/dist/secret-redaction.js +112 -0
  29. package/dist/session-timeline.js +103 -0
  30. package/dist/setup-prompt.js +4 -2
  31. package/dist/skill-installer.js +9 -1
  32. package/dist/smoke.js +250 -0
  33. package/dist/task-assignment-manifest.js +39 -2
  34. package/dist/vercel-sandbox-provider.js +260 -0
  35. package/dist/workbench-cli.js +493 -0
  36. package/dist/workspace-sync.js +924 -0
  37. package/dist/workspace.js +868 -57
  38. package/package.json +4 -1
  39. package/src/artifact-archive.ts +182 -0
  40. package/src/connectors-cli.ts +115 -0
  41. package/src/credentials.ts +6 -2
  42. package/src/daytona-slice-rehearsal.ts +320 -0
  43. package/src/daytona-slice.ts +598 -0
  44. package/src/egress-allowlist-cli.ts +147 -0
  45. package/src/egress-policy.ts +188 -0
  46. package/src/git-credential.ts +155 -0
  47. package/src/github-setup.ts +366 -0
  48. package/src/help.ts +272 -16
  49. package/src/hosted-bootstrap-rehearsal.ts +312 -0
  50. package/src/hosted-bootstrap.ts +571 -0
  51. package/src/hosted-broker-server.ts +278 -0
  52. package/src/hosted-git-http-server.ts +164 -0
  53. package/src/hosted-harness-claude.ts +621 -0
  54. package/src/hosted-harness-opencode.ts +823 -0
  55. package/src/hosted-harness.ts +124 -0
  56. package/src/hosted-loop.ts +680 -0
  57. package/src/hosted-run-event-sink.ts +632 -0
  58. package/src/hosted-runtime-assets.ts +272 -0
  59. package/src/hosted-session-cli.ts +844 -0
  60. package/src/index.ts +987 -296
  61. package/src/kill-switch-cli.ts +113 -0
  62. package/src/manifests-cli.ts +168 -0
  63. package/src/preview-runtime.ts +14 -9
  64. package/src/sandbox-provider.ts +440 -0
  65. package/src/secret-redaction.ts +124 -0
  66. package/src/session-timeline.ts +152 -0
  67. package/src/setup-prompt.ts +4 -2
  68. package/src/skill-installer.ts +13 -1
  69. package/src/smoke.ts +342 -0
  70. package/src/task-assignment-manifest.ts +70 -2
  71. package/src/vercel-sandbox-provider.ts +388 -0
  72. package/src/workbench-cli.ts +701 -0
  73. package/src/workspace-sync.ts +1148 -0
  74. package/src/workspace.ts +1051 -64
  75. package/vendor/skills/orizu-cli/SKILL.md +3 -1
  76. package/vendor/skills/orizu-cli/references/cli-reference.md +3 -3
package/dist/help.js CHANGED
@@ -16,6 +16,11 @@ const GROUPS = [
16
16
  { name: 'Apps', summary: 'Create, preview, update, inspect, and export review apps.' },
17
17
  { name: 'Tasks', summary: 'Create review tasks, mutate task status, assign reviewers, and export labels.' },
18
18
  { name: 'Datasets', summary: 'Upload, version, split, mutate, export, lock, clone, and delete datasets.' },
19
+ { name: 'Workspace', summary: 'Metadata-first sync of the workbench contract: inspect status, reconcile, pull, and apply.' },
20
+ { name: 'Sessions', summary: 'Start, inspect, and end durable workspace sessions for agent/operator work.' },
21
+ { name: 'Workbench runs', summary: 'Start, inspect, tail, and finish resumable workbench runs.' },
22
+ { name: 'Connectors', summary: 'Inspect project connector/integration readiness (read-only; secrets redacted).' },
23
+ { name: 'Promotion manifests', summary: 'List, inspect, approve, reject, and idempotently apply promotion manifests.' },
19
24
  ];
20
25
  export const COMMAND_DOCS = [
21
26
  {
@@ -54,28 +59,36 @@ export const COMMAND_DOCS = [
54
59
  },
55
60
  {
56
61
  path: ['setup'],
57
- usage: 'orizu setup [--agent <claude|codex>]... [--scope global|project] [--mode auto|link|copy] [--workspace|--no-workspace] [--no-install] [--no-handoff] [--launch <claude|codex>] [--skip-login] [--yes] [--dry-run] [--no-input] [--json]',
58
- summary: 'Guided onboarding: sign in, set up your coding agents, create a local workspace, and hand off to your agent.',
62
+ usage: 'orizu setup [--team <slug>] [--agent <claude|codex>]... [--workspace [path]|--no-workspace] [--validate] [--fix] [--no-symlinks] [--verbose] [--no-install] [--handoff|--no-handoff] [--launch <claude|codex>] [--skip-login] [--yes] [--dry-run] [--no-input|--non-interactive] [--json]',
63
+ summary: 'Guided onboarding: sign in, initialize the workbench contract, and install agent skills.',
59
64
  group: 'Agent setup',
60
65
  options: [
61
- { name: '--agent <agent>', help: 'Coding agent to set up. Repeat for multiple agents.', repeatable: true, choices: ['claude', 'codex'] },
62
- { name: '--scope <scope>', help: 'global (default) installs for you across all projects; project installs into the current repo.', choices: ['global', 'project'] },
63
- { name: '--mode <mode>', help: 'Skill install sync mode (auto, link, copy).', choices: ['auto', 'link', 'copy'] },
64
- { name: '--workspace', help: 'Create the gitignored .orizu/ workspace without prompting.' },
65
- { name: '--no-workspace', help: 'Skip workspace creation.' },
66
- { name: '--no-install', help: 'Skip the agent integration phase.' },
67
- { name: '--no-handoff', help: 'Skip printing the coding-agent setup prompt.' },
66
+ { name: '--team <slug>', help: 'Team/workbench slug. Authenticated setup materializes all projects in this team.' },
67
+ { name: '--workspace [path]', help: 'Initialize or validate a workspace path, or the current directory when no path is provided. If provided, the value must be a filesystem path.' },
68
+ { name: '--service-origin <url>', help: 'Service origin written to setup metadata. Defaults to the active CLI server.' },
69
+ { name: '--attach-workspace <id>', help: 'Record an existing Orizu workspace id for future attachment.' },
70
+ { name: '--validate', help: 'Inspect the workspace contract without writing files.' },
71
+ { name: '--fix', help: 'Apply safe idempotent repairs only.' },
72
+ { name: '--no-symlinks', help: 'Write CLAUDE.md as a pointer file instead of a symlink to AGENTS.md.' },
73
+ { name: '--verbose', help: 'Show per-file setup actions. Default setup output is summary-oriented.' },
74
+ { name: '--agent <agent>', help: 'Install the global Orizu skill for this coding agent after workspace setup. Repeat for multiple agents.', repeatable: true, choices: ['claude', 'codex'] },
75
+ { name: '--no-workspace', help: 'Skip workspace creation during guided setup. `--validate` and `--fix` still inspect or repair the workspace contract.' },
76
+ { name: '--no-install', help: 'Skip the coding-agent skill install step.' },
77
+ { name: '--handoff', help: 'Print the coding-agent setup prompt after setup. By default, setup points to `orizu setup prompt` instead of printing it inline.' },
78
+ { name: '--no-handoff', help: 'Compatibility flag; suppresses --handoff/--launch. Setup does not print the handoff prompt by default.' },
68
79
  { name: '--launch <agent>', help: 'Launch a detected coding agent with the setup prompt (interactive terminals only).', choices: ['claude', 'codex'] },
69
80
  { name: '--skip-login', help: 'Skip the authentication phase.' },
70
- { name: '--yes', help: 'Apply changes without per-step confirmation prompts.' },
81
+ { name: '--yes', help: 'Replace existing managed skill links without prompting.' },
71
82
  { name: '--dry-run', help: 'Preview every planned change without writing.' },
72
83
  { name: '--no-input', help: 'Never prompt; behave as in a non-interactive terminal.' },
84
+ { name: '--non-interactive', help: 'Alias for --no-input.' },
73
85
  { name: '--json', help: 'Emit the setup summary as JSON.' },
74
86
  ],
75
87
  examples: [
76
88
  'orizu setup',
77
- 'orizu setup --agent claude --agent codex --yes',
78
- 'orizu setup --skip-login --no-workspace --dry-run',
89
+ 'orizu setup --team highlight',
90
+ 'orizu setup --team highlight --agent codex --agent claude --non-interactive --yes',
91
+ 'orizu setup --validate --workspace ./workbench',
79
92
  ],
80
93
  },
81
94
  {
@@ -88,7 +101,7 @@ export const COMMAND_DOCS = [
88
101
  {
89
102
  path: ['install-skill'],
90
103
  usage: 'orizu install-skill [--agent <claude|codex>]... [--scope global|project] [--mode auto|link|copy] [--target <target>]... [--yes] [--dry-run]',
91
- summary: 'Set up the Orizu skill for your coding agents (Claude Code, Codex/Open Agent Skills, or project instructions).',
104
+ summary: 'Install or repair the Orizu coding-agent skill.',
92
105
  group: 'Agent setup',
93
106
  aliases: [['skills', 'install']],
94
107
  options: [
@@ -112,7 +125,7 @@ export const COMMAND_DOCS = [
112
125
  name: '--target <target>',
113
126
  help: 'Advanced: explicit install destination ID. Repeat for multiple targets.',
114
127
  repeatable: true,
115
- choices: ['agent-user', 'agents-project', 'codex-project', 'claude-user', 'claude-project', 'agents-md'],
128
+ choices: ['codex-user', 'agent-user', 'agents-project', 'codex-project', 'claude-user', 'claude-project', 'agents-md'],
116
129
  },
117
130
  { name: '--yes', help: 'Replace an existing managed skill or AGENTS.md section without prompting.' },
118
131
  { name: '--dry-run', help: 'Show planned writes without changing files.' },
@@ -201,7 +214,7 @@ export const COMMAND_DOCS = [
201
214
  },
202
215
  {
203
216
  path: ['teams', 'members', 'role'],
204
- usage: 'orizu teams members role --team <teamSlug> --email <email> --role <admin|member>',
217
+ usage: 'orizu teams members role --team <teamSlug> --email <email> --role <admin|curator|judge>',
205
218
  summary: 'Change a member role.',
206
219
  group: 'Teams',
207
220
  },
@@ -454,6 +467,18 @@ export const COMMAND_DOCS = [
454
467
  summary: 'Create a draft review task by default, with auto distribution or an explicit row-assignment JSONL file.',
455
468
  group: 'Tasks',
456
469
  },
470
+ {
471
+ path: ['tasks', 'update'],
472
+ usage: 'orizu tasks update --task <taskId> [--title <text>] [--description <text>|--description-file <path>] [--instructions <text>|--instructions-file <path>] [--dataset <datasetId>] [--app <appId> [--version <n>]] [--labels-per-item <n>] [--assignees <userIdOrEmail1,userIdOrEmail2> | --assignment-file <path>] [--json]',
473
+ summary: 'Update a draft review task before publishing.',
474
+ group: 'Tasks',
475
+ },
476
+ {
477
+ path: ['tasks', 'discard'],
478
+ usage: 'orizu tasks discard --task <taskId> [--yes] [--json]',
479
+ summary: 'Permanently discard a draft review task.',
480
+ group: 'Tasks',
481
+ },
457
482
  {
458
483
  path: ['tasks', 'publish'],
459
484
  usage: 'orizu tasks publish --task <taskId> (--assignees <userId1,userId2> | --assignment-file <path>) [--json]',
@@ -501,6 +526,12 @@ export const COMMAND_DOCS = [
501
526
  summary: 'Resume a paused task.',
502
527
  group: 'Tasks',
503
528
  },
529
+ {
530
+ path: ['tasks', 'complete'],
531
+ usage: 'orizu tasks complete --task <taskId>',
532
+ summary: 'Mark an active or paused task completed.',
533
+ group: 'Tasks',
534
+ },
504
535
  {
505
536
  path: ['tasks', 'export'],
506
537
  usage: 'orizu tasks export [--task <taskId>] [--format <csv|json|jsonl>] [--out <path>]',
@@ -579,6 +610,230 @@ export const COMMAND_DOCS = [
579
610
  summary: 'Clone a dataset.',
580
611
  group: 'Datasets',
581
612
  },
613
+ {
614
+ path: ['workspace', 'status'],
615
+ usage: 'orizu workspace status [--remote] [--json]',
616
+ summary: 'Show tracked-resource status: local dirtiness vs the last-sync cache, plus missing and untracked files; --remote adds server statuses via a server-side reconciliation.',
617
+ group: 'Workspace',
618
+ options: [
619
+ { name: '--remote', help: 'Attach server statuses. This performs the same server-side reconciliation as `orizu workspace sync` — it registers and updates tracked-resource records; there is no read-only remote status in v0. The local cache is never advanced.' },
620
+ { name: '--json', help: 'Emit the machine-readable status result.' },
621
+ ],
622
+ examples: ['orizu workspace status', 'orizu workspace status --remote --json'],
623
+ },
624
+ {
625
+ path: ['workspace', 'sync'],
626
+ usage: 'orizu workspace sync [--json]',
627
+ summary: 'Metadata-first reconcile of the whole workbench. Attaches the workspace on first run; advances the cache base only on a clean convergence.',
628
+ group: 'Workspace',
629
+ options: [
630
+ { name: '--json', help: 'Emit the machine-readable per-resource status list.' },
631
+ ],
632
+ examples: ['orizu workspace sync', 'orizu workspace sync --json'],
633
+ },
634
+ {
635
+ path: ['workspace', 'pull'],
636
+ usage: 'orizu workspace pull <path> [--json]',
637
+ summary: 'Fast-forward a resource to the server truth (v0: manifest canonical block + cache only). Refuses when local edits are ahead and no-ops when the server has no truth (local-only/stale). Bulk content materializes via the primitive command (e.g. orizu prompts pull).',
638
+ group: 'Workspace',
639
+ options: [
640
+ { name: '--json', help: 'Emit the machine-readable pull result.' },
641
+ ],
642
+ examples: ['orizu workspace pull projects/hip/prompts/judge/orizu.prompt.json'],
643
+ },
644
+ {
645
+ path: ['workspace', 'apply'],
646
+ usage: 'orizu workspace apply <path> [--json]',
647
+ summary: 'Promote a repo-owned resource to the server, addressed by the row id recorded by sync. Refuses (409) DB-native/object-storage owners, conflicts, remote-newer, and lost compare-and-set races — on any refusal, run `orizu workspace sync`, converge, then retry.',
648
+ group: 'Workspace',
649
+ options: [
650
+ { name: '--json', help: 'Emit the machine-readable apply result.' },
651
+ ],
652
+ examples: ['orizu workspace apply projects/hip/prompts/judge/orizu.prompt.json'],
653
+ },
654
+ {
655
+ path: ['session', 'start'],
656
+ usage: 'orizu session start [--project <team/project>] [--json]',
657
+ summary: 'Start a durable workspace session for the attached workspace and print the session id for later resume.',
658
+ group: 'Sessions',
659
+ options: [
660
+ { name: '--project <team/project>', help: 'Optionally scope the session to a project in the attached workspace.' },
661
+ { name: '--json', help: 'Emit the machine-readable session payload.' },
662
+ ],
663
+ examples: ['orizu session start', 'orizu session start --project highlight/hip --json'],
664
+ },
665
+ {
666
+ path: ['session', 'status'],
667
+ usage: 'orizu session status [--session <id> | --status active|ended] [--json]',
668
+ summary: 'Inspect one session by id, including run summaries, or list sessions for the attached workspace.',
669
+ group: 'Sessions',
670
+ options: [
671
+ { name: '--session <id>', help: 'Resume by session id; does not require the original terminal.' },
672
+ { name: '--status <status>', help: 'Filter attached-workspace sessions when --session is omitted.', choices: ['active', 'ended'] },
673
+ { name: '--json', help: 'Emit the machine-readable session or session-list payload.' },
674
+ ],
675
+ examples: ['orizu session status --session sess_123 --json', 'orizu session status --status active'],
676
+ },
677
+ {
678
+ path: ['session', 'end'],
679
+ usage: 'orizu session end --session <id> [--json]',
680
+ summary: 'Mark a durable workspace session ended.',
681
+ group: 'Sessions',
682
+ options: [
683
+ { name: '--session <id>', help: 'Session id to end.', required: true },
684
+ { name: '--json', help: 'Emit the machine-readable ended session payload.' },
685
+ ],
686
+ examples: ['orizu session end --session sess_123'],
687
+ },
688
+ {
689
+ path: ['session', 'finish'],
690
+ usage: 'orizu session finish --session <id> [--project <team/project>] [--json]',
691
+ summary: 'Finish a hosted session branch: no changes deletes the branch; changes create a repo_merge promotion manifest to review and approve (no GitHub PR).',
692
+ group: 'Sessions',
693
+ options: [
694
+ { name: '--session <id>', help: 'Session id whose branch to finish.', required: true },
695
+ { name: '--project <team/project>', help: 'Project scope for the manifest when the session is not already project-scoped.' },
696
+ { name: '--json', help: 'Emit the machine-readable outcome (no-changes or the created manifest).' },
697
+ ],
698
+ examples: ['orizu session finish --session sess_123', 'orizu session finish --session sess_123 --project highlight/hip --json'],
699
+ },
700
+ {
701
+ path: ['run', 'start'],
702
+ usage: 'orizu run start --session <id> --title <title> [--project <team/project>] [--json]',
703
+ summary: 'Start a resumable workbench run inside a workspace session and print the run id.',
704
+ group: 'Workbench runs',
705
+ options: [
706
+ { name: '--session <id>', help: 'Workspace session id.', required: true },
707
+ { name: '--title <title>', help: 'Human-readable run title.', required: true },
708
+ { name: '--project <team/project>', help: 'Optionally override the session project scope.' },
709
+ { name: '--json', help: 'Emit the machine-readable run payload.' },
710
+ ],
711
+ examples: ['orizu run start --session sess_123 --title "Fix evaluator drift" --json'],
712
+ },
713
+ {
714
+ path: ['run', 'status'],
715
+ usage: 'orizu run status --run <id> [--json]',
716
+ summary: 'Inspect a workbench run by id, including evidence and latest event sequence.',
717
+ group: 'Workbench runs',
718
+ options: [
719
+ { name: '--run <id>', help: 'Workbench run id.', required: true },
720
+ { name: '--json', help: 'Emit the machine-readable run payload.' },
721
+ ],
722
+ examples: ['orizu run status --run run_123 --json'],
723
+ },
724
+ {
725
+ path: ['run', 'tail'],
726
+ usage: 'orizu run tail --run <id> [--after <seq>] [--interval <seconds>] [--once] [--json]',
727
+ summary: 'Cursor-poll a workbench run event log; JSON mode emits one event object per line.',
728
+ group: 'Workbench runs',
729
+ options: [
730
+ { name: '--run <id>', help: 'Workbench run id.', required: true },
731
+ { name: '--after <seq>', help: 'Start after this event sequence. Defaults to 0.' },
732
+ { name: '--interval <seconds>', help: 'Polling interval for non-terminal runs. Defaults to 2.' },
733
+ { name: '--once', help: 'Fetch one page and exit; useful for tests and agents.' },
734
+ { name: '--json', help: 'Emit JSONL, one event object per line.' },
735
+ ],
736
+ examples: ['orizu run tail --run run_123', 'orizu run tail --run run_123 --after 42 --once --json'],
737
+ },
738
+ {
739
+ path: ['run', 'complete'],
740
+ usage: 'orizu run complete --run <id> [--summary <text>] [--json]',
741
+ summary: 'Mark a workbench run succeeded.',
742
+ group: 'Workbench runs',
743
+ options: [
744
+ { name: '--run <id>', help: 'Workbench run id.', required: true },
745
+ { name: '--summary <text>', help: 'Optional note stored in the run summary.' },
746
+ { name: '--json', help: 'Emit the machine-readable run payload.' },
747
+ ],
748
+ examples: ['orizu run complete --run run_123 --summary "All gates passed"'],
749
+ },
750
+ {
751
+ path: ['run', 'fail'],
752
+ usage: 'orizu run fail --run <id> [--summary <text>] [--json]',
753
+ summary: 'Mark a workbench run failed.',
754
+ group: 'Workbench runs',
755
+ options: [
756
+ { name: '--run <id>', help: 'Workbench run id.', required: true },
757
+ { name: '--summary <text>', help: 'Optional note stored in the run summary.' },
758
+ { name: '--json', help: 'Emit the machine-readable run payload.' },
759
+ ],
760
+ examples: ['orizu run fail --run run_123 --summary "Lint failed"'],
761
+ },
762
+ {
763
+ path: ['run', 'cancel'],
764
+ usage: 'orizu run cancel --run <id> [--summary <text>] [--json]',
765
+ summary: 'Mark a workbench run cancelled.',
766
+ group: 'Workbench runs',
767
+ options: [
768
+ { name: '--run <id>', help: 'Workbench run id.', required: true },
769
+ { name: '--summary <text>', help: 'Optional note stored in the run summary.' },
770
+ { name: '--json', help: 'Emit the machine-readable run payload.' },
771
+ ],
772
+ examples: ['orizu run cancel --run run_123 --summary "Superseded by run_456"'],
773
+ },
774
+ {
775
+ path: ['connectors', 'status'],
776
+ usage: 'orizu connectors [status] [--project <team/project>] [--json]',
777
+ summary: 'Show project connector readiness (configured/missing/stale/unauthorized/unsupported/unknown) derived read-only from stored integration state. Secrets are always redacted.',
778
+ group: 'Connectors',
779
+ options: [
780
+ { name: '--project <team/project>', help: 'Project whose connector readiness to inspect.' },
781
+ { name: '--json', help: 'Emit the machine-readable connector readiness payload.' },
782
+ ],
783
+ examples: ['orizu connectors', 'orizu connectors status --project highlight/hip --json'],
784
+ },
785
+ {
786
+ path: ['manifests', 'list'],
787
+ usage: 'orizu manifests list [--project <team/project>] [--status <status>] [--json]',
788
+ summary: 'List promotion manifests for a project, newest first, optionally filtered by status.',
789
+ group: 'Promotion manifests',
790
+ options: [
791
+ { name: '--project <team/project>', help: 'Project whose manifests to list.' },
792
+ { name: '--status <status>', help: 'Filter by status.', choices: ['draft', 'pending_approval', 'approved', 'applied', 'rejected'] },
793
+ { name: '--json', help: 'Emit the machine-readable manifest list.' },
794
+ ],
795
+ examples: ['orizu manifests list --project highlight/hip --status pending_approval'],
796
+ },
797
+ {
798
+ path: ['manifests', 'show'],
799
+ usage: 'orizu manifests show <id> [--json]',
800
+ summary: 'Show one promotion manifest, including current/proposed state, evidence, outcome, and approver.',
801
+ group: 'Promotion manifests',
802
+ options: [
803
+ { name: '--json', help: 'Emit the machine-readable manifest payload.' },
804
+ ],
805
+ examples: ['orizu manifests show manifest_123 --json'],
806
+ },
807
+ {
808
+ path: ['manifests', 'approve'],
809
+ usage: 'orizu manifests approve <id> [--json]',
810
+ summary: 'Approve a draft or pending_approval manifest; records the approver principal separately from the author.',
811
+ group: 'Promotion manifests',
812
+ options: [
813
+ { name: '--json', help: 'Emit the machine-readable manifest payload.' },
814
+ ],
815
+ examples: ['orizu manifests approve manifest_123'],
816
+ },
817
+ {
818
+ path: ['manifests', 'reject'],
819
+ usage: 'orizu manifests reject <id> [--json]',
820
+ summary: 'Reject a manifest so it can no longer be applied.',
821
+ group: 'Promotion manifests',
822
+ options: [
823
+ { name: '--json', help: 'Emit the machine-readable manifest payload.' },
824
+ ],
825
+ examples: ['orizu manifests reject manifest_123'],
826
+ },
827
+ {
828
+ path: ['manifests', 'apply'],
829
+ usage: 'orizu manifests apply <id> [--json]',
830
+ summary: 'Apply an approved manifest. Idempotent: re-applying returns the stored outcome with no second effect. v0 records the outcome and links evidence; content promotion still flows through the primitive commands.',
831
+ group: 'Promotion manifests',
832
+ options: [
833
+ { name: '--json', help: 'Emit the machine-readable manifest payload.' },
834
+ ],
835
+ examples: ['orizu manifests apply manifest_123 --json'],
836
+ },
582
837
  ];
583
838
  function groupByName(name) {
584
839
  return GROUPS.find(group => group.name === name);
@@ -625,7 +880,7 @@ export function renderRootHelp() {
625
880
  lines.push(` ${doc.usage}`);
626
881
  }
627
882
  }
628
- lines.push('', 'Examples:', ' orizu install-skill --target agent-user --yes', ' orizu apps preview --file ./App.tsx --input-schema ./input.json --output-schema ./output.json --sample-row ./row.json --screenshot ./preview.png', ' orizu tasks create --project core/evals --dataset <datasetId> --app <appId> --title "Review"', ' orizu tasks publish --task <taskId> --assignees <userId1,userId2>', '', 'More:', ' orizu <group> --help', ' orizu <command> --help', ' orizu capabilities --json', ' https://docs.orizu.ai');
883
+ lines.push('', 'Examples:', ' orizu install-skill --target codex-user --yes', ' orizu apps preview --file ./App.tsx --input-schema ./input.json --output-schema ./output.json --sample-row ./row.json --screenshot ./preview.png', ' orizu tasks create --project core/evals --dataset <datasetId> --app <appId> --title "Review"', ' orizu tasks publish --task <taskId> --assignees <userId1,userId2>', '', 'More:', ' orizu <group> --help', ' orizu <command> --help', ' orizu capabilities --json', ' https://docs.orizu.ai');
629
884
  return lines.join('\n');
630
885
  }
631
886
  export function renderGroupHelp(args) {
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Self-contained rehearsal for the hosted bootstrap (ALI-925), extending the
3
+ * ALI-973 `daytona-slice-rehearsal` pattern.
4
+ *
5
+ * The whole `bootstrapHostedSandbox` flow runs against the local-sim provider
6
+ * with NO Daytona and NO GitHub: a REAL bare git repo backs the session branch,
7
+ * and a REAL HTTP broker (`hosted-broker-server.ts`) stands in for the Orizu
8
+ * control plane (per-repo token broker + RunAPI event ingest).
9
+ *
10
+ * The broker runs as a SEPARATE PROCESS on purpose: local-sim's `exec` blocks on
11
+ * `spawnSync`, and the in-sandbox credential helper it runs calls the broker over
12
+ * the network — an in-process server could not answer while the test thread is
13
+ * blocked. The broker journals events/tokens to files the rehearsal reads back.
14
+ */
15
+ import { spawnSync } from 'child_process';
16
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs';
17
+ import { tmpdir } from 'os';
18
+ import { basename, join } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { seedLocalBareRepo } from './daytona-slice-rehearsal.js';
21
+ import { createLocalSimProvider } from './sandbox-provider.js';
22
+ import { gitHttpEnv } from './hosted-git-http-server.js';
23
+ /** The loopback host the git server binds; the helper is scoped to it. */
24
+ const LOOPBACK_HOST = '127.0.0.1';
25
+ function git(args, cwd) {
26
+ const env = { ...process.env, GIT_CONFIG_NOSYSTEM: '1', GIT_TERMINAL_PROMPT: '0' };
27
+ delete env.GIT_DIR;
28
+ delete env.GIT_WORK_TREE;
29
+ delete env.GIT_INDEX_FILE;
30
+ delete env.GIT_OBJECT_DIRECTORY;
31
+ delete env.GIT_COMMON_DIR;
32
+ const res = spawnSync('git', args, { cwd, encoding: 'utf8', env });
33
+ return { stdout: res.stdout ?? '', stderr: res.stderr ?? '', status: res.status ?? 1 };
34
+ }
35
+ function gitOrThrow(args, cwd) {
36
+ const r = git(args, cwd);
37
+ if (r.status !== 0)
38
+ throw new Error(`git ${args.join(' ')} failed: ${r.stderr.trim() || `exit ${r.status}`}`);
39
+ return r.stdout;
40
+ }
41
+ function bunSpawn() {
42
+ const b = globalThis.Bun;
43
+ if (!b?.spawn)
44
+ throw new Error('Bun.spawn is required for the hosted-bootstrap rehearsal (run under bun test)');
45
+ return b;
46
+ }
47
+ /**
48
+ * Env keys the broker server reads (kept here so callers do not hand-roll them).
49
+ */
50
+ export function brokerEnv(config) {
51
+ const env = { HB_BEARER: config.bearer, HB_REPO: config.repo };
52
+ if (config.eventsFile)
53
+ env.HB_EVENTS_FILE = config.eventsFile;
54
+ if (config.tokensFile)
55
+ env.HB_TOKENS_FILE = config.tokensFile;
56
+ if (config.sessionId)
57
+ env.HB_SESSION_ID = config.sessionId;
58
+ if (config.sessionBranch)
59
+ env.HB_SESSION_BRANCH = config.sessionBranch;
60
+ if (config.repoTokenStatus)
61
+ env.HB_REPO_TOKEN_STATUS = String(config.repoTokenStatus);
62
+ if (config.eventsStatus)
63
+ env.HB_EVENTS_STATUS = String(config.eventsStatus);
64
+ if (config.denyWrite)
65
+ env.HB_DENY_WRITE = '1';
66
+ if (config.fullRunApi)
67
+ env.HB_FULL_RUN_API = '1';
68
+ return env;
69
+ }
70
+ /**
71
+ * Spawn the broker server as its own process and resolve once it prints
72
+ * `LISTENING <port>` on stdout.
73
+ */
74
+ export async function spawnBrokerProcess(config) {
75
+ const brokerPath = fileURLToPath(new URL('./hosted-broker-server.ts', import.meta.url));
76
+ const proc = bunSpawn().spawn({
77
+ cmd: ['bun', brokerPath],
78
+ env: { ...process.env, ...brokerEnv(config) },
79
+ stdout: 'pipe',
80
+ stderr: 'inherit',
81
+ });
82
+ const decoder = new TextDecoder();
83
+ const reader = proc.stdout.getReader();
84
+ let buffered = '';
85
+ const deadline = Date.now() + 10_000;
86
+ while (Date.now() <= deadline) {
87
+ const { done, value } = await reader.read();
88
+ if (done)
89
+ break;
90
+ if (value)
91
+ buffered += decoder.decode(value, { stream: true });
92
+ const match = buffered.match(/LISTENING (\d+)/);
93
+ if (match) {
94
+ reader.releaseLock();
95
+ const port = Number.parseInt(match[1], 10);
96
+ return { apiBaseUrl: `http://127.0.0.1:${port}`, port, stop: () => proc.kill() };
97
+ }
98
+ }
99
+ reader.releaseLock();
100
+ proc.kill();
101
+ throw new Error('broker server did not report a listening port in time');
102
+ }
103
+ /**
104
+ * Spawn the authenticated smart-HTTP git server as its own process (same
105
+ * separate-process rationale as the broker) and resolve once it prints
106
+ * `LISTENING <port>`. `baseUrl` is the clone URL root; append the repo segment.
107
+ */
108
+ export async function spawnGitHttpProcess(config) {
109
+ const serverPath = fileURLToPath(new URL('./hosted-git-http-server.ts', import.meta.url));
110
+ const proc = bunSpawn().spawn({
111
+ cmd: ['bun', serverPath],
112
+ env: { ...process.env, ...gitHttpEnv(config) },
113
+ stdout: 'pipe',
114
+ stderr: 'inherit',
115
+ });
116
+ const decoder = new TextDecoder();
117
+ const reader = proc.stdout.getReader();
118
+ let buffered = '';
119
+ const deadline = Date.now() + 10_000;
120
+ while (Date.now() <= deadline) {
121
+ const { done, value } = await reader.read();
122
+ if (done)
123
+ break;
124
+ if (value)
125
+ buffered += decoder.decode(value, { stream: true });
126
+ const match = buffered.match(/LISTENING (\d+)/);
127
+ if (match) {
128
+ reader.releaseLock();
129
+ const port = Number.parseInt(match[1], 10);
130
+ return { baseUrl: `http://127.0.0.1:${port}`, port, stop: () => proc.kill() };
131
+ }
132
+ }
133
+ reader.releaseLock();
134
+ proc.kill();
135
+ throw new Error('git http server did not report a listening port in time');
136
+ }
137
+ function readJsonl(file) {
138
+ let raw = '';
139
+ try {
140
+ raw = readFileSync(file, 'utf8');
141
+ }
142
+ catch {
143
+ return [];
144
+ }
145
+ return raw
146
+ .split('\n')
147
+ .filter(line => line.length > 0)
148
+ .map(line => JSON.parse(line));
149
+ }
150
+ function readLines(file) {
151
+ try {
152
+ return readFileSync(file, 'utf8').split('\n').filter(line => line.length > 0);
153
+ }
154
+ catch {
155
+ return [];
156
+ }
157
+ }
158
+ export async function createHostedBootstrapRehearsal(options = {}) {
159
+ const bearer = options.bearer ?? 'pat_rehearsal_bearer_do_not_log';
160
+ const base = mkdtempSync(join(tmpdir(), 'orizu-hosted-boot-'));
161
+ const repo = seedLocalBareRepo(base, {
162
+ teamSlug: 'acme',
163
+ projectSlug: 'support-bot',
164
+ workspaceSlug: 'support-bot-hosted',
165
+ });
166
+ const workspaceId = 'ws-hosted-1';
167
+ const sessionId = 'sess-hosted-1';
168
+ const runId = 'run-hosted-1';
169
+ const sessionBranch = `orizu/session-${sessionId}`;
170
+ // Cut the session branch off main on the real bare repo.
171
+ gitOrThrow(['--git-dir', repo.gitDir, 'branch', sessionBranch, repo.defaultBranch]);
172
+ // Optionally seed a customer setup hook onto the session branch. `.orizu/` is
173
+ // git-ignored by the workbench contract, so the hook is force-added — an open
174
+ // question flagged in the secrets-policy doc.
175
+ if (options.setupHookBody) {
176
+ const work = mkdtempSync(join(base, 'hook-'));
177
+ gitOrThrow(['clone', '--branch', sessionBranch, repo.gitDir, work]);
178
+ mkdirSync(join(work, '.orizu'), { recursive: true });
179
+ writeFileSync(join(work, '.orizu', 'setup.sh'), options.setupHookBody);
180
+ const ident = ['-c', 'user.email=hook@orizu.local', '-c', 'user.name=Hook'];
181
+ gitOrThrow([...ident, '-C', work, 'add', '-f', '.orizu/setup.sh']);
182
+ gitOrThrow([...ident, '-C', work, 'commit', '-m', 'seed setup hook']);
183
+ gitOrThrow(['-C', work, 'push', 'origin', sessionBranch]);
184
+ rmSync(work, { recursive: true, force: true });
185
+ }
186
+ const eventsFile = join(base, 'events.jsonl');
187
+ const tokensFile = join(base, 'tokens.txt');
188
+ const authLogFile = join(base, 'git-auth.jsonl');
189
+ writeFileSync(eventsFile, '');
190
+ writeFileSync(tokensFile, '');
191
+ writeFileSync(authLogFile, '');
192
+ const broker = await spawnBrokerProcess({
193
+ bearer,
194
+ repo: repo.repoFullName,
195
+ eventsFile,
196
+ tokensFile,
197
+ repoTokenStatus: options.repoTokenStatus,
198
+ eventsStatus: options.eventsStatus,
199
+ fullRunApi: options.fullRunApi,
200
+ sessionId,
201
+ sessionBranch,
202
+ });
203
+ // Real authenticated smart-HTTP git server on loopback: this is what makes the
204
+ // clone go git → credential helper → broker → token → Basic auth → served repo,
205
+ // instead of a filesystem clone that bypasses the helper entirely.
206
+ const gitServer = await spawnGitHttpProcess({ repoDir: repo.gitDir, authLogFile });
207
+ // git includes the (non-standard) port in the credential `host` field, so the
208
+ // helper must be scoped to `127.0.0.1:<port>`, not the bare loopback host.
209
+ const host = `${LOOPBACK_HOST}:${gitServer.port}`;
210
+ const insecureHttpHosts = [host];
211
+ const cloneUrl = `${gitServer.baseUrl}/${basename(repo.gitDir)}`;
212
+ const provider = createLocalSimProvider({ rootDir: join(base, 'sandboxes'), keepOnDestroy: Boolean(options.keep) });
213
+ return {
214
+ apiBaseUrl: broker.apiBaseUrl,
215
+ bearer,
216
+ workspaceId,
217
+ sessionId,
218
+ runId,
219
+ sessionBranch,
220
+ repoFullName: repo.repoFullName,
221
+ provider,
222
+ resolveCloneUrl: () => cloneUrl,
223
+ host,
224
+ insecureHttpHosts,
225
+ events: () => readJsonl(eventsFile),
226
+ tokensIssued: () => readLines(tokensFile),
227
+ basicAuthPasswords: () => readJsonl(authLogFile).map(entry => entry.pass),
228
+ cleanup: async () => {
229
+ broker.stop();
230
+ gitServer.stop();
231
+ if (!options.keep)
232
+ rmSync(base, { recursive: true, force: true });
233
+ },
234
+ };
235
+ }