Skip to content

feat(cli)!: hosted OpenProse service commands in both ports; remove service environment selection - #10

Open
rawwerks wants to merge 1 commit into
mainfrom
feat/hosted-service-client
Open

rawwerks wants to merge 1 commit into
mainfrom
feat/hosted-service-client

Conversation

@rawwerks

Copy link
Copy Markdown

This PR was prepared and opened by Raymond's Claude Code, an AI coding agent working on his behalf from his GitHub account. Raymond reviewed and approved publishing it.

Summary

prose cli becomes a noninteractive client of the hosted OpenProse service. Both products, Rust and Bun, gain the same commands:

Group Commands
Discovery cli service status|triage|capabilities|operations|guide, cli model list, cli example list|show, cli repo list
Runs cli run quote|submit|watch|input|cancel
Run records cli run list|show|download|share
Programs cli program list|show|save|visibility|delete|revisions|draft
Results cli result list|show|publish|unpublish
Jobs cli job ... (schedule and webhook jobs, contracts, deliveries)
Wallet cli wallet balance|events|usage|redeem|topup
Organizations cli org ... (no organization deletion)

The existing cli auth and cli package commands share the same account connection, error model and output envelope.

Behavior, in brief:

  • Output is versioned, canonical JSON (openprose.service-operation/1) or JSONL (openprose.service-event/1, ending in exactly one terminal line). Commands never prompt. Both products print the same bytes.
  • Commands that spend money, publish outward, delete or cannot be undone exit 2 with CONFIRMATION_REQUIRED and show the planned request until repeated with --yes. --preview shows the plan and sends nothing.
  • run submit journals the run locally before sending it. Ctrl-C, a lost stream or the --wait deadline detach without cancelling (exit 21) and return the exact resume command. An unconfirmable submission exits RUN_SUBMISSION_AMBIGUOUS instead of resubmitting. Only run cancel --yes cancels.
  • A misspelled or misplaced command or option exits 2 with a corrected, parseable command in details.suggestedArgv. Nothing is guessed into execution.
  • Keys come from OPENPROSE_API_KEY or the OS credential store (cli auth login). On Linux the Rust product now uses secret-tool and shares one store item with the Bun product.
  • Output shows prices only. cli model list shows each model's status: available, premium (unlocks with any wallet top-up) or accepted but not recommended; choosing a locked premium model fails with SERVICE_PREMIUM_MODEL_LOCKED and a top-up preview command.

User docs: docs/hosted-service-client.md (overview), docs/service/<feature>.md (per feature), and cli/shared/service/guide.v1.md, which is printed by prose cli service guide. The normative contract is the "Hosted service operations" section of cli/SPEC.md.

Breaking changes

This PR removes the service environment selection that was previously public. The full notes are under ### Breaking in cli/CHANGELOG.md.

  • cli environment show|use|reset are gone. They fail with INVOCATION_INVALID (exit 2).
  • The global --service-environment option is gone. It fails with INVOCATION_INVALID (exit 2), and the message says the option was removed.
  • OPENPROSE_STAGING_API_KEY is no longer read. A saved service_environment key in the user cli.toml is ignored, so every command now reaches production. Run prose cli auth status to see which credential is in use.
  • cli auth *, cli org list and cli package * now print the openprose.service-operation/1 envelope instead of their own service-account, organization-list and package-operation documents. Operation names are dotted (auth.status), there is no environment member, keys are sorted, and a stored key reports credentialSource: "store". shared/schemas/service-environment.schema.json is deleted.
  • A malformed key is SERVICE_AUTH_REQUIRED with credentialProblem: "malformed". It was previously SERVICE_PROTOCOL_INVALID.
  • An invalid package reference, slug, version, --sha256 or --cursor is INVOCATION_INVALID (exit 2) in the service-operation envelope. It was previously CONFIG_INVALID.
  • In the cli package commands, a registry 404 is now SERVICE_RESOURCE_NOT_FOUND (exit 10, not retryable). It was previously the retryable SERVICE_UNAVAILABLE.

No npm package or release binary changes until the next published alpha.

Reading guide

The diff is about 1,420 files. Most of them are shared corpus cases (about 1,090); the rest are code, schemas, docs and tooling. Review in this order.

1. The contract (start here)

Path What it is
cli/shared/service/operations.v1.json The operation manifest. It lists 63 operations with grammar, options, confirmation rules, effects, exit codes, result schemas and examples. Both products embed it and derive parsing, help, confirmation and transport limits from it.
cli/shared/service/operations.schema.json, cli/shared/service/README.md The manifest schema and its checked invariants: confirmation, effect floor, the no-cost rule, exit codes and examples.
cli/SPEC.md, section "Hosted service operations" Normative behavior. The "Account commands" section is amended for the environment removal.
cli/shared/errors/taxonomy.v1.json Additive error codes and their exits.
cli/shared/schemas/service-*.schema.json, cli/shared/schemas/service/*.schema.json The output envelopes (operation, event, record, page) and the closed per-feature result projections.
cli/shared/service/responses/*.schema.json Acceptance schemas for raw service responses. They require only what the client consumes and tolerate extra fields.

2. The shared corpus and its runner

  • cli/conformance/runner/service_operations.py runs each case against a product binary through the fixture transport. service-fixture.schema.json defines the case format, and canonical_json.py holds the byte-exact comparison helpers.
  • cli/conformance/runner/service_coverage.py requires every operation to have at least one success case and one failure case. It also requires requests to match the vendored interaction export.
  • cli/conformance/cases/service/README.md explains the layout: one directory per feature, one case per file.

3. Each product's service module

The two products mirror each other file for file, so review them side by side:

Rust (cli/rust/crates/prose-runner-core/src/service/) Bun (cli/bun/src/core/service/)
mod.rs (dispatch, credential resolution, error classification) index.ts, manifest.ts
http.rs, sse.rs, fs.rs, journal.rs http.ts, sse.ts, fs.ts, journal.ts
render.rs (canonical JSON, human output, redaction) render.ts
runs.rs, run_records.rs, programs.rs, program_ref.rs, results.rs, jobs.rs, wallet.rs, organizations.rs, discovery.rs, triage.rs, not_found.rs same names in kebab case (Rust keeps key-shape checks in mod.rs, Bun in credentials.ts)
dev_endpoint.rs dev-endpoint.ts, endpoint.ts

The wiring is in cli/rust/crates/prose-cli/src/main.rs, prose-runner-core/src/{invocation,runner,service_account,config}.rs, cli/bun/src/{cli,main}.ts and cli/bun/src/core/{args,config,service-account}.ts.

4. Credential handling

  • cli/rust/crates/prose-runner-core/src/credential_store.rs is new. It is the Linux secret-tool backend: a fixed argv with no shell, the key passed on stdin, trusted-directory program lookup, a scrubbed environment, and time and output bounds. macOS continues to use /usr/bin/security.
  • cli/bun/src/core/service/credentials.ts handles key-shape classification and the credentialSource / credentialProblem details.
  • docs/service/credentials.md documents the shared store item and the threat model.
  • Tests: cli/rust/crates/prose-cli/tests/credential_store.rs and cli/bun/test/credential-store-interop.test.ts.

5. The public-surface gate

  • cli/ci/check_public_surface.py checks the tracked files. With --build-release, it also checks both products' release --help output, the guide and manifest invocations, and the strings in the release Rust binary.
  • cli/ci/public_surface_denylist.py holds the generic patterns (hostnames, key shapes, environment options, debug detail).
  • cli/ci/test_check_public_surface.py tests the gate. It is wired into run_local.py and .github/workflows/cli-distribution-check.yml.

6. Tooling

  • cli/ci/render_service_help.py renders help.v1.json and the pinned help and guide cases from the manifest. Its --check option validates every prose cli ... line in the guide and examples.
  • cli/ci/sync_service_interactions.py re-vendors the service's public interaction export.
  • cli/ci/run_local.py and test_run_local.py add the new gate inventory. check_architecture.py and check_workflows.py get their admissions.

Generated or fixture data (skim)

  • cli/conformance/cases/service/** holds about 1,090 black-box cases, each with argv, scripted service exchanges and expected bytes. Spot-check a few per feature. The runner and coverage gates enforce the rest.
  • cli/shared/service/help.v1.json is generated by render_service_help.py --write. Never edit it by hand.
  • cli/shared/service/service-interactions.{v1,source}.json is the vendored projection and its digests, written by the sync script.
  • The framework/service-guide-* and framework/help-* cases are generated from the guide and manifest.
  • Small updates to existing fixtures under cli/shared/fixtures/ and cli/conformance/cases/{core,dx,operations,fixtures} follow the environment removal and help changes.

Unrelated cleanups in this diff

  • Local machine paths are replaced with placeholders in docs/expedition-baseline.md, provenance/import.json and experiments/**.
  • Retained validation logs that recorded local paths are removed (docs/validation/).
  • Decision records are renamed to descriptive names: cli/protocol/decisions/registry-cli.md and weave-host.md.
  • docs/staging-account.md is removed along with the environment selection.

Build and test locally

Prerequisites: a Rust toolchain, Bun and Python 3, as described in cli/CONTRIBUTING.md.

# Everything provider-free, fail-fast
python3 cli/ci/run_local.py            # add --quick to skip build-heavy gates

# Contract-only gates (no product build)
python3 cli/ci/run_local.py --only shared-contracts --only service-operations-corpus \
  --only service-coverage --only service-help --only public-surface-files

# Rust: test-seam build, then the shared corpus and registry cases
python3 cli/ci/run_local.py --only service-operations-rust-build \
  --only service-operations-rust --only registry-service-rust
(cd cli/rust && cargo test --workspace --all-targets --locked --features prose-cli/test-seams)

# Bun: typecheck, unit tests, test-seam build, then the same corpus
(cd cli/bun && bun install --frozen-lockfile && bun run typecheck && bun run test)
python3 cli/ci/run_local.py --only service-operations-bun-build \
  --only service-operations-bun --only registry-service-bun

# Public-surface leak check, including both release builds
python3 cli/ci/check_public_surface.py --build-release

The corpus gates use a fixture transport and never touch the network. Nothing in the gates spends money or needs a key.

To try the release build against the service yourself:

prose cli auth login            # or: export OPENPROSE_API_KEY=...
prose cli service triage --json
prose cli run quote --json

cli run submit spends from your wallet and requires --yes.

Note for contributors: developer endpoint build

Public builds cannot be pointed anywhere but production. Contributors who work against another service origin use a separate developer build:

cargo build --release --locked -p prose-cli --features dev-endpoint   # Rust
cd cli/bun && bun run build:dev                                       # Bun, writes dist/prose-dev

Only a developer build reads OPENPROSE_API_URL, and the value must be an https origin with no path, query or credentials. cli auth login stores that build's key under an entry scoped to the origin, so it never overwrites the production key. Human output is labeled OpenProse (custom endpoint <origin>). CI keeps this build compiling and tested with the rust-*-dev-endpoint gates. See cli/CONTRIBUTING.md, "Developer endpoint build".

Known follow-ups

  • Release. The service commands ship with the next published alpha. This PR changes no published package or binary.
  • Rust on Windows has no credential-store backend yet. Set OPENPROSE_API_KEY there. The Bun product uses Bun.secrets on all platforms.
  • Response acceptance schemas marked "x-probed": false describe shapes that have not yet been checked against live responses. They require only the fields the client reads, so extra fields are tolerated.
  • Not offered, by design: organization deletion, browser sign-in pages, and the other browser-only flows.
  • Live qualification. The corpus proves the two products are equivalent against scripted service exchanges. End-to-end qualification against the live service is tracked separately from this PR.

🤖 Generated with Claude Code

`prose cli` becomes a noninteractive client of the hosted OpenProse
service, implemented independently in the Rust and Bun products and held
equivalent by one shared manifest and conformance corpus.

New commands (both products):

- cli service status|triage|capabilities|operations|guide
- cli model list (with each model's status: available, premium or
  accepted-but-not-recommended), cli example list|show, cli repo list
- cli run quote|submit|watch|input|cancel|list|show|download|share
- cli program list|show|save|visibility|delete|revisions|draft
- cli result list|show|publish|unpublish
- cli job ... (schedule and webhook jobs, contracts, deliveries)
- cli wallet balance|events|usage|redeem|topup
- cli org ... (no organization deletion)

The existing cli auth and cli package commands share the same account
connection and error model.

Behavior:

- Every command emits versioned, canonical JSON
  (openprose.service-operation/1) or JSONL
  (openprose.service-event/1, ending in exactly one terminal line) and
  never prompts. Human output shows prices in dollars and ends with
  copyable next commands.
- Commands that spend money, publish outward, delete, or cannot be undone
  stop with CONFIRMATION_REQUIRED (exit 2) until repeated with --yes;
  --preview prints the planned request without sending it.
- A submitted run is journaled locally before it is sent. Ctrl-C, a lost
  stream or the --wait deadline detach without cancelling
  (HOSTED_RUN_DETACHED, exit 21) and return the exact resume command; an
  unconfirmable submission exits RUN_SUBMISSION_AMBIGUOUS instead of
  resubmitting. Only `cli run cancel --yes` cancels.
- Misspelled or misplaced commands and options exit 2 with a complete,
  parseable corrected command in details.suggestedArgv; nothing is guessed
  into execution.
- Keys come from OPENPROSE_API_KEY or the OS credential store written by
  `cli auth login`. The Rust product on Linux uses secret-tool and reads
  and writes the same item as the Bun product.
- Results carry prices only; result schemas refuse cost-named properties.
- New error taxonomy codes, including CONFIRMATION_REQUIRED,
  SERVICE_RESOURCE_NOT_FOUND, SERVICE_WRITE_CONFLICT, GITHUB_LINK_REQUIRED,
  SERVICE_PREMIUM_MODEL_LOCKED, HOSTED_RUN_DETACHED (21), HOSTED_RUN_FAILED (22),
  RUN_SUBMISSION_AMBIGUOUS (22) and HOSTED_RUN_CANCELLED (24).

How the two implementations stay equivalent:

- cli/shared/service/operations.v1.json is the single operation manifest.
  Both products embed it and derive parsing, help, confirmation rules and
  transport limits from it; `cli service operations --json` prints it.
- Help text and the agent guide are rendered from the manifest and
  printed byte for byte by both products.
- The shared corpus under cli/conformance/cases/service/ runs unmodified
  against a test-seam build of each product through a fixture transport
  (no network). Coverage, help and corpus gates check that every
  operation has success and failure cases and that manifest, taxonomy
  and schemas agree.

Service endpoint and builds:

- Public builds talk only to the production OpenProse service.
- A contributor-only developer build (cargo feature `dev-endpoint`, Bun
  `bun run build:dev`) additionally honours OPENPROSE_API_URL. The
  override is compiled out of default and release builds, stores its key
  under a separate origin-scoped credential entry, and labels its output
  as a custom endpoint.
- A new public-surface gate (cli/ci/check_public_surface.py), run locally
  and in CI, checks tracked files, both products' release help and the
  release Rust binary for developer-only or internal service detail.

BREAKING CHANGE: service environment selection is removed. The global
--service-environment option and `cli environment show|use|reset` now fail
with INVOCATION_INVALID (exit 2), OPENPROSE_STAGING_API_KEY is no longer
read, a saved service_environment key in the user cli.toml is ignored, and
shared/schemas/service-environment.schema.json is deleted; every command
reaches the production service.

BREAKING CHANGE: `cli auth *`, `cli org list` and `cli package *` now print
the openprose.service-operation/1 envelope instead of their own
service-account, organization-list and package-operation documents
(dotted operation names such as auth.status, no environment member, sorted
keys, credentialSource "store").

BREAKING CHANGE: a malformed key is SERVICE_AUTH_REQUIRED with
credentialProblem "malformed" (was SERVICE_PROTOCOL_INVALID); a registry 404
is SERVICE_RESOURCE_NOT_FOUND and not retryable (was SERVICE_UNAVAILABLE);
an invalid package reference, slug, version, --sha256 or --cursor is
INVOCATION_INVALID (exit 2) in the service-operation envelope (was
CONFIG_INVALID).

See cli/CHANGELOG.md.

Prepared by Raymond Weitekamp's Claude Code (AI coding agent), acting on
Raymond's behalf and published from his account with his approval.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant