# Oysterun npm Publish

This document defines the Oysterun Host npm package boundary and publish gate.
It is a release/operator document, not a general npm tutorial.

Repository branch and release authority is defined in:

```text
docs/routec/dev/repository_branch_release_workflow.md
```

Beta preparation starts from one exact clean Owner-accepted Beta candidate in
a dedicated release worktree. The checked-out Beta worktree is not a package
preparation surface. Beta contains or advances to the exact source commit only
after npm publication, registry/dist-tag readback, and required smoke pass.
Later unpublished Beta stabilization commits do not change the identity of the
published version.

## Goal

The first public Host install path is:

```bash
npm install -g oysterun
oysterun
```

`npm install -g oysterun` installs package files and exposes the `oysterun`
command. It must not run setup, install a service, start a service, or ask
interactive questions.

## Package Boundary

Do not publish the monorepo root.

The npm package must be built from a clean release staging package. The staging
package is produced by:

```bash
node tool_scripts/package_oysterun_npm.mjs
```

The staging package includes only the Host runtime surface:

```text
package.json
README.md
bin/oysterun.mjs
host-service/
tool_scripts/ required by Host setup/service lifecycle
dev/client/connectivity/ shared Host/browser transport runtime
dev/client/web/ prebuilt dashboard assets
dev/client/web-chat/dist/ prebuilt Route C web-chat assets
static/
docs/routec/ minimal release docs
```

The package must exclude:

```text
backend/
dev/client/phone-app/
dev/client/web-chat/ios/
dev/client/web-chat/src/
<gitignored-operational-notes>/
.secrets/
node_modules/
host-service/node_modules/
dev/client/web-chat/node_modules/
runtime config files
cloud_identity.json
DB/log/jsonl artifacts
Apple .p8 keys
credential_copy/
DerivedData/
```

Native runtime dependency ownership and the required install/update checks are
defined in `docs/routec/dev/npm_native_dependency_install.md`. In particular,
the tarball must not bundle a maintainer machine's `node_modules`, and native
dependencies must be validated by opening an in-memory SQLite database under
the target Node runtime. The package layout verifier must also prove that every
packaged JavaScript relative import resolves inside the generated package.

The postinstall verifier must validate the exact
`@agentclientprotocol/claude-agent-acp` package, executable runner bin,
`@agentclientprotocol/sdk`, `@anthropic-ai/claude-agent-sdk`, `zod`, module
import, and package ownership. It must not start a competing provider process
while an existing Host may still be live. The deterministic release gate runs
the same verifier in its default release-runtime mode, including a
credential-free ACP `initialize` probe. The disposable-Host gate then proves
`initialize`, `session/new`, model application, and an exact provider reply
through one retained process. File existence alone is not provider readiness.

## Package Name And Command

Target npm package name:

```text
oysterun
```

Target installed command:

```text
oysterun
```

If npm name ownership blocks `oysterun`, choose the fallback package name in a
separate Owner decision. Do not silently publish under another name.

## User Install Journey

Install:

```bash
npm install -g oysterun
```

First run:

```bash
oysterun
```

If required Host config is missing or incomplete, `oysterun` enters setup
automatically.

Explicit setup:

```bash
oysterun setup
```

Setup performs the release onboarding flow:

```text
ask Host display name
ask Default Browse Root
ask Host password with hidden input
confirm first-time Host password by entering it twice
register or resume the Cloud Host identity
allocate or reuse the Oysterun Managed Endpoint
write provisioning state as pending
write ~/.oysterun/config.json and create dashboard credential hash
write Host Cloud identity state outside user-editable config
show phone app download/QR step
install service manager integration
start Host by default after explicit setup flow
wait for Host health and authenticated Managed route health
mark the Managed Endpoint ready only after Connector health succeeds
show the Cloud onboarding QR when available
show/open the Managed Endpoint /app/sessions when browser open is available
```

Normal release setup does not ask for public IP, public Port, domain,
Tailscale, or a third-party tunnel. Production still uses local listener Port
8802 by default. `oysterun setup --local` and
`oysterun setup --enable-cloud-direct` remain explicit advanced Direct paths.

If Cloud registration, route allocation, or Connector health is temporarily
unavailable, setup keeps a usable Direct local Host result and records Managed
provisioning as pending. Rerunning `oysterun setup` reuses the same Host
identity.

If setup runs in a headless or SSH environment, it should print the URL instead
of forcing a browser open.

## Service Model

First release target:

```text
macOS launchd LaunchAgent
Linux pid-file managed Host service
```

Linux must not use launchd. The first Linux release path uses the same
config/run/log layout and starts the Host as a detached pid-file managed
process through `oysterun service:start`. A boot-persistent Linux supervisor,
such as a systemd user service, is a later explicit phase and must not be
claimed until verified on Linux.

Service commands:

```bash
oysterun service:start
oysterun service:stop
oysterun service:restart
oysterun service:status
oysterun service:logs
```

Explicit service commands should fail fast when setup has not completed. They
must not silently create config or run setup.

`oysterun service:start` and `oysterun service:restart` print a fresh
15-minute one-time Host login QR after the Host start/restart succeeds.

## Update Control

The npm package must support owner-visible update awareness after install.

Host Preferences may show:

```text
current_version
channel: latest or beta
latest_version after manual npm registry check
Check for updates
Update now
```

Update now must be explicit and restart-aware. Host Preferences and terminal
commands enter the same durable P090/P110 transaction coordinator:

```text
Host Preferences: Update & Restart
Host Preferences: Install Beta & Restart
oysterun service:update --channel latest
oysterun service:update --channel beta
```

The coordinator reads the service's persisted absolute Node/npm identity,
constructs the canonical macOS/Linux tool environment, proves complete
replacement readiness before restore capture or stop, invokes the matching
absolute `npm-cli.js`, and owns rollback plus terminal status. Do not replace
that command with a bare `npm install -g oysterun`, a hand-built `PATH`, and a
separate bare restart. `oysterun service:lifecycle-status` reads the same
redacted operation record.

Update mutation is production-stack only except for an explicitly isolated
verification stack with its task-owned registry. No publish, install, recovery
reconciliation, or production Port `8802` restart is authorized merely by this
document. P076 restore state preserves supported sessions and loops without
replaying interrupted provider work.

## Cloud Endpoint

The production Cloud endpoint is a git-tracked product constant.

It is not a user-editable Host Preference field and should not be stored as a
normal user-managed runtime config value.

Dev/local Cloud endpoints require an explicit debug config, environment
variable, or setup flag.

Host Cloud identity state belongs in runtime state such as:

```text
<host_config_dir>/cloud_identity.json
```

It must not be bundled into npm packages.

## Maintainer Build And Pack

Before packing, rebuild all prebuilt browser assets in the source worktree:

```bash
npm --prefix dev/client/web-chat run build
node dev/client/web/build-index.mjs
```

This is mandatory before every npm publish. `node
tool_scripts/package_oysterun_npm.mjs` copies the existing
`dev/client/web-chat/dist/` directory into `dist/npm/oysterun`; it does not
build Route C web-chat. If a frontend source fix was made but
`dev/client/web-chat/dist/` is stale, npm will publish the old browser bundle
even though the TypeScript source is correct.

Before continuing, inspect the browser asset diff:

```bash
git diff --stat -- dev/client/web-chat/dist dev/client/web/index.html
```

Do not publish if a browser-facing source fix is expected but the rebuilt
prebuilt asset output is unchanged or missing.

Create staging package:

```bash
node tool_scripts/package_oysterun_npm.mjs
```

Inspect pack contents:

```bash
cd dist/npm/oysterun
npm pack --dry-run
```

Do not continue if the tarball includes any excluded file or directory.

## Publish Gate

Do not publish until all of these pass:

```text
host-service/package.json version is bumped to a never-published npm version
registry version check confirms the exact version is not already published
package boundary exists
packaging script produced clean staging package
npm pack --dry-run passed
tarball content review found no excluded files
`npm run test:npm-artifact` passed
generated package pins `better-sqlite3` exactly and packages its postinstall verifier
generated package pins `@agentclientprotocol/claude-agent-acp` exactly
clean temp install works
clean temp install opens an in-memory SQLite database
clean temp install executes the ACP package/import postinstall gate
standalone release verifier executes the credential-free ACP initialize gate
controlled ACP failure creates no session history and retry uses a new process
oysterun --help works
oysterun with missing config enters setup
oysterun setup can create config and start Host on macOS
Host /health passes
web login / sessions page works
phone QR/manual login works
```

Only after the gate passes and the Owner explicitly asks to publish may the
assistant publish from the staging package through the dedicated tmux/web-auth
workflow below.

The npm package version is generated from:

```text
host-service/package.json
```

The monorepo root `package.json` does not own the public `oysterun` package
version. Before every beta publish, use the fixed prepare script:

```bash
cd <repo-root>
npm run publish:beta:prepare
```

The script is the only normal beta version-bump path. It queries the registry
with `npm view oysterun versions --json`, finds the highest published
`<base>-beta.N` for the current base version, writes `N + 1` to
`host-service/package.json`, runs `npm run test:p058`, rebuilds
`dist/npm/oysterun`, installs the
generated tgz into an isolated prefix, starts the installed Host with an
isolated HOME/config and random loopback port, and requires `/health` to return
`status=ok`.

The same gate verifies the package file boundary, every packaged JavaScript
relative import, native SQLite/WebRTC runtime loading, CLI startup, and
`npm pack --dry-run`. It emits final publish commands only after all gates pass.
On failure it restores `host-service/package.json` and removes the failed
staging package.

A successful run writes:

```text
dist/npm/oysterun-release-receipt.json
```

The receipt records the package version, source commit, dirty-worktree state
hash, deterministic package-tree hash, retained tgz path/SHA-256, verification
time, and passed gates. It is generated evidence under ignored `dist/`; it is
not a source file and is not committed. `prepublishOnly` requires that receipt
and rejects any staging-tree change made after the full prepare gate.

For provider-runtime, dependency, setup, service-environment, or packaging
changes, retain a second clean-environment receipt from the exact tgz:

```text
new Ubuntu 24.04 VM
official non-root oysterun user
empty product/provider/npm state
real provider authentication
first session without warm-up
exact prompt and reply
Host restart and repeat
same-tgz reinstall and repeat
no duplicate or stale history
```

`/health`, source-worktree success, and a warm existing account are not
substitutes for this gate.

Do not manually choose the beta number during normal release work, and do not
invent a new one-off version-bump command. npm permanently rejects publishing
over an existing version, for example:

```text
npm error 403 You cannot publish over the previously published versions
```

The script may be dry-run for inspection:

```bash
cd <repo-root>
npm run publish:beta:prepare -- --dry-run
```

Do not run `npm publish` if the prepare script fails. If the current base should
change, pass an explicit base after Owner decides the new train:

```bash
npm run publish:beta:prepare -- --base 0.1.2
```

`--no-package` is not a release bypass. It is diagnostic-only and cannot emit
publish commands.

When a new public stable version is published, verify the public npm dist-tag
before site deployment:

```bash
npm view oysterun dist-tags version --json
```

The public website install/update commands in `static/site/index.html` must use
the general stable channel:

```bash
npm install -g oysterun@latest --prefer-online
```

Do not put a fixed package version such as `oysterun@0.1.1` in the public site,
and do not use `oysterun@beta`. A stable release should move the npm `latest`
dist-tag to the intended general version; the public site should only need an
HTML update when the install command itself changes.

Do not use the deprecated `npm publish --otp <code>` flow as the normal
Oysterun publish path. The assistant starts the final `npm publish` directly
inside a dedicated tmux TTY. When the registry requires write 2FA, npm uses
that TTY to present its one-time web-authentication URL; give only that URL to
the Owner and leave the same publish process running. The Owner completes
account authentication in the browser, and that same process retries the
publish before the assistant performs readback. Do not run a separate
`npm login` as a substitute: login success does not satisfy a package account's
`auth-and-writes` publish challenge. Never run the final publish through a
non-TTY command, because npm cannot open its web-OTP flow there and will return
`EOTP` instead. Never
request or print the Owner's password, OTP, recovery code, token, cookie, or
`.npmrc`.

Prepare the exact release first:

```bash
cd <release-worktree>
npm --prefix dev/client/web-chat run build
node dev/client/web/build-index.mjs
git diff --stat -- dev/client/web-chat/dist dev/client/web/index.html
npm run publish:beta:prepare

cd <release-worktree>/dist/npm/oysterun
npm pkg get version
npm pack --dry-run
cat ../oysterun-release-receipt.json
```

If preparation changes the version or tracked release assets, commit the exact
release state in the release branch and rerun every source-identity-sensitive
gate before login. Then start the assistant publish session:

```bash
tmux new-session -d -s <unique-npm-publish-session>
tmux send-keys -t <unique-npm-publish-session> \
  'cd <release-worktree>/dist/npm/oysterun && npm publish --tag beta' C-m
tmux capture-pane -pt <unique-npm-publish-session>
```

Give only the captured npm web-auth URL to the Owner. Do not start another
publish command. After authentication, wait for that same tmux process to
finish, then continue in the same tmux session with readback:

```bash
cd <release-worktree>/dist/npm/oysterun
npm whoami
npm view oysterun dist-tags --json
npm view oysterun@beta version
npm view oysterun@latest version
```

After publish and readback pass, advance `beta` to the receipt's exact clean
source commit, create the immutable `v<version>` tag, record the branch/tag
receipt, and then remove the tmux session. Do not advance Beta or remove the
diagnostic tmux session after a failed publish or readback.

The receipt must distinguish `published_source_sha`, `beta_head_at_freeze`,
`beta_head_after_release_integration`, and `beta_head_observed_now`. The first
three are immutable publication facts; the last is moving state. Every
published version remains bound to its exact source SHA, immutable tag,
artifact shasum/integrity, channel readback, and timestamps even after Beta
advances. Never claim an unpublished current Beta HEAD is the installed npm
beta version, and never use that claim as production deployment authority.

For stable, start the tmux publish process with:

```bash
npm publish --tag latest
```

## Remote Verification Is A Separate Task

An npm publish handoff must not include a fixed remote install command.
Publishing does not authorize installation, update, restart, deployment, or
verification on any existing machine. In particular, never target
`167.172.88.163` for Oysterun.

If the Owner separately requests remote beta verification, first create a
fresh disposable VM and bind it to the task under:

```text
docs/routec/dev/test_host_authority.md
.codex/prompts/DO/create_test_host_VM.md
```

Only after that exact target and deployment instruction exist may the
operator provide first-install or update commands for the registered VM. The
receipt must name the exact beta version and VM identity. Delete the VM after
the verification task through `.codex/prompts/DO/remove_test_host_VM.md`.

## Dist Tag Verification And Beta Practice

After publishing, verify registry tags explicitly. The publish success line:

```text
+ oysterun@0.1.0
```

only proves that the version was published. It does not prove which dist-tag was
assigned.

Verification commands:

```bash
npm dist-tag ls oysterun
npm view oysterun dist-tags --json
npm view oysterun@beta version
npm view oysterun@latest version
```

Install-channel smoke:

```bash
npm install -g oysterun@beta
oysterun --help
```

Current first publish result, 2026-06-06:

```text
beta:   0.1.0
latest: 0.1.0
```

This is acceptable for the first public bootstrap package because there was no
previous stable version to preserve. It means both commands install the same
package:

```bash
npm install -g oysterun@beta
npm install -g oysterun
```

Best practice for future beta releases:

```text
Use prerelease semver for beta channel:
  0.1.1-beta.0
  0.1.1-beta.1

Publish those with:
  npm login --auth-type=web
  npm publish --tag beta

Keep latest for stable releases only:
  0.1.1
  npm login --auth-type=web
  npm publish --tag latest
```

If a future beta publish accidentally moves `latest` away from the last stable
release, restore `latest` explicitly:

```bash
npm dist-tag add oysterun@<last-stable-version> latest
```

Do not use a semver-looking string as a dist-tag. `beta`, `next`, and `canary`
are valid channel names; tags like `v1.4` are not appropriate because npm
dist-tags share the same namespace as versions.

## Manual Product Smoke

After install/setup, maintainers may run a manual Cloud notification smoke.
This is not part of npm install and not part of setup automation.

Purpose:

```text
verify npm-installed Host -> Oysterun Cloud -> Apple APNs -> iPhone app
```

Checklist:

```text
1. Install package in a clean environment.
2. Run oysterun and complete setup.
3. Pair/login the iPhone app with the Host.
4. Enter sessions page.
5. Enable notifications.
6. Confirm app APNs token registration is accepted by Cloud.
7. Click Send test notification.
8. Confirm iPhone receives the test notification.
9. Run a provider complete-message flow.
10. Confirm iPhone receives the complete-message notification.
```

The iPhone app obtains the APNs token from Apple and registers it with Cloud.
The Host does not own APNs tokens or Apple `.p8` keys. The Host only submits
notification candidates to Cloud.

## Rollback

npm unpublish has strict registry limitations and should not be the normal
rollback path.

Preferred rollback:

```text
publish a patched version
deprecate the bad version with npm deprecate
tell users to install a known good version or tag
```
