refactor: replace built-in env list with PHOTON_API_HOST runtime var#14
Conversation
Restructure the README's top half around the three actually-supported install paths so first-time readers can pick the right one without hunting: 1. one-off via npx / bunx — no install (unpinned now works as of 0.1.1) 2. global install via bun add -g — daily use 3. standalone binary downloaded from a GitHub Release — no runtime Also lifts the pho-alias note into its own subsection under Quickstart with the explicit caveat that npx/bunx ephemeral runs don't get pho (it's only created when running through an installed photon binary). The previous version led with `bun add -g` and buried npx as an afterthought in the Quickstart, which understated what the CLI now supports. Co-authored-by: Orca <help@stably.ai>
Previously BUILTIN_ENVS hardcoded production / staging / dev URLs into the public bundle, which meant our internal staging URL shipped to npm and to every standalone binary in every release. This is the kind of thing security review eventually catches — better to fix it before that. The new model: - The only URL baked in is production (https://app.photon.codes). - Every other backend is reached via the PHOTON_API_HOST env var (or `--api-host <url>` per command). - Credentials and project links are now keyed by sanitized hostname instead of by env name. Production keeps the "production" key for back-compat, so existing creds files survive the upgrade. Other URLs get keys like `staging-app-photon-codes` or `localhost-3001`. - The whole `env list / use / add / remove` tree goes away — redundant with the env var. `env current` stays as a debug helper. Net result: 23 files touched, -468/+309 lines, public bundle has zero references to anything other than the production URL. Verified locally: $ photon env current production (https://app.photon.codes) $ PHOTON_API_HOST=https://staging-app.photon.codes photon env current staging-app-photon-codes (https://staging-app.photon.codes) $ photon ping --api-host https://wins.tld # flag overrides env var → https://wins.tld $ PHOTON_API_HOST=not-a-url photon env current ✗ Invalid API host URL: "not-a-url". Must include scheme — e.g. https://your.host.tld. Bundle audit: $ strings dist/photon.js | grep -c app.photon.codes → 1 $ strings dist/photon.js | grep -c staging → 0 $ strings dist/photon.js | grep -c localhost:3001 → 0 Co-authored-by: Orca <help@stably.ai>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughReplaces env-name selection with API-host-driven backends: introduces PHOTON_API_HOST/--api-host, host-derived credential keys and storage, removes local env CRUD, updates command flags/messages/docs, and threads apiHost/url through resolve/getApi across CLI commands and libs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant CLI as "photon CLI"
participant Resolver as "resolveApiHost / resolveActiveEnv"
participant API as "getApi (API client)"
participant FS as "credentialsDir / credentialsPath"
CLI->>Resolver: receive --api-host or PHOTON_API_HOST
Resolver-->>CLI: return ResolvedEnv{name (hostKey), url}
CLI->>API: getApi({ apiHost: resolved.url, ... })
API->>FS: loadCredentials(resolved.name)
FS-->>API: credentials (or none / corrupt)
API-->>CLI: authenticated client or auth-required response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Warning Review ran into problems🔥 ProblemsThese MCP integrations need to be re-authenticated in the Integrations settings: Linear Review rate limit: 3/5 reviews remaining, refill in 17 minutes and 26 seconds. Comment |
There was a problem hiding this comment.
Pull request overview
Refactors the CLI’s backend/environment selection so only the production API URL is bundled, while all other backends are selected at runtime via PHOTON_API_HOST or --api-host <url>. This removes the previous built-in env list and config-persisted “current env”, and re-keys credentials/links storage by a hostname-derived key.
Changes:
- Replace built-in environment list +
photon env list/use/add/removeworkflow with runtime backend resolution (PHOTON_API_HOST/--api-host), keepingphoton env currentas a debug helper. - Update API/env resolution plumbing across commands and libraries to use URL-based backend selection and host-keyed credentials/links.
- Update CLI help/errors and README documentation to reflect the new backend host model and flag rename.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/errors.ts | Updates auth/session error wording to “backend” and removes UnknownEnvError. |
| src/lib/env.ts | Introduces production-only baked URL, runtime host resolution, host-key derivation, and key validation/paths. |
| src/lib/config.ts | Removes persisted env config; resolveEnv() now resolves the active backend via env var/flag/production. |
| src/lib/api.ts | Switches API client creation and credential lookup to apiHost and host-keyed env names. |
| src/lib/api-context.ts | Updates project resolution docs + --api-host wiring for link lookup and hints. |
| src/index.ts | Removes unknown-env handling; updates top-level error hints for new backend selection model. |
| src/commands/whoami.ts | Renames --env to --api-host and updates output wording. |
| src/commands/spectrum/users.ts | Ports Spectrum users subcommands to --api-host and URL-based backend selection. |
| src/commands/spectrum/profile.ts | Ports Spectrum profile subcommands to --api-host. |
| src/commands/spectrum/platforms.ts | Ports Spectrum platforms subcommands (and helper opts typing) to --api-host. |
| src/commands/spectrum/lines.ts | Ports Spectrum lines subcommands to --api-host. |
| src/commands/spectrum/avatar.ts | Ports avatar upload flow to --api-host; recovery command now emits --api-host when non-production. |
| src/commands/projects.ts | Ports projects commands to --api-host and URL-based env resolution. |
| src/commands/profile.ts | Ports profile commands to --api-host. |
| src/commands/ping.ts | Ports ping to --api-host while retaining --url bypass. |
| src/commands/logout.ts | Ports logout to --api-host. |
| src/commands/login.ts | Ports login to --api-host and logs the resolved backend key + URL. |
| src/commands/link.ts | Ports link/unlink to --api-host and updates wording (“backend”). |
| src/commands/env.ts | Collapses env management to env current and updates description text. |
| src/commands/config.ts | Removes env list/current-env persistence from config output; focuses on active backend + keys. |
| src/commands/billing.ts | Ports billing commands to --api-host. |
| src/commands/auth.ts | Reworks auth status to show authenticated backend keys (and active backend) without relying on env config. |
| README.md | Updates install/usage docs and replaces “environments” concept with “backend host” (PHOTON_API_HOST / --api-host). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/commands/spectrum/avatar.ts (1)
103-107: Normalize URL before production comparison in recovery command.Direct string comparison can treat equivalent production URLs as different (for example,
https://app.photon.codes/), causing unnecessary--api-hostin hints.Suggested refinement
- if (opts.apiHost !== PRODUCTION_URL) { + const resolvedOrigin = new URL(opts.apiHost).origin; + const productionOrigin = new URL(PRODUCTION_URL).origin; + if (resolvedOrigin !== productionOrigin) { parts.push(`--api-host ${shellQuote(opts.apiHost)}`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/spectrum/avatar.ts` around lines 103 - 107, Normalize the API host before comparing to PRODUCTION_URL so equivalent URLs (e.g. "https://app.photon.codes/") don't trigger an unnecessary --api-host; inside the block that builds parts (reference opts.apiHost, PRODUCTION_URL, parts, shellQuote in avatar.ts) canonicalize both values by trimming any trailing slash and lowercasing the host (or otherwise normalizing the URL string) and then compare the normalized strings—only push `--api-host ${shellQuote(...)}` when the normalized opts.apiHost differs from the normalized PRODUCTION_URL.src/commands/projects.ts (1)
36-37: Consider extracting a sharedaddApiHostOption()helper.The same option signature/description is repeated across many subcommands; centralizing it will reduce drift and simplify future CLI flag updates.
Also applies to: 81-82, 154-155, 304-305, 399-400, 457-458, 507-508, 527-528
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/projects.ts` around lines 36 - 37, Extract the repeated .option("--api-host <url>", "API host URL (defaults to PHOTON_API_HOST or built-in production)") calls into a small helper function addApiHostOption(cmd) that accepts a Commander command/command-builder and returns the command after calling .option(...); replace each inline .option(...) occurrence in src/commands/projects.ts (and the other command files/command builders at the listed locations) with addApiHostOption(myCommand) so all commands share a single source of truth; name the helper addApiHostOption and export it from a shared CLI utilities module (e.g., cliOptions.ts) and update imports where used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/env.ts`:
- Around line 8-17: The help text for the CLI advertises an unsupported
"--api-host" flag; update the description strings to remove the "--api-host"
mention. Specifically, edit the top-level .description("show the active backend
(set via PHOTON_API_HOST or --api-host)") and any help text associated with
env.command("current") so they only reference PHOTON_API_HOST (or add a proper
--api-host option if you prefer), ensuring env.command("current")'s .description
no longer claims support for the nonexistent flag.
In `@src/commands/whoami.ts`:
- Around line 9-10: The whoami command prints inconsistent wording for target
selection: update the token-only output that currently says "on env ..." to use
"on backend ..." (or the same "backend" phrasing used elsewhere) so both
branches are consistent; locate the string literal in the whoami command handler
(the token-auth/token-only branch around the existing message at the lines
referenced, e.g., the branch that emits "on env ...") and replace it with the
backend wording and ensure the alternate branch output at the other occurrence
(around line 41) matches exactly.
In `@src/lib/env.ts`:
- Around line 57-59: hostKey currently only returns "production" when the input
exactly equals PRODUCTION_URL, which breaks for equivalent forms (e.g., trailing
slash); update hostKey to normalize/compare origins instead of raw strings:
parse the input url and PRODUCTION_URL with the URL API (or fallback to trimming
trailing slashes) and return "production" when their origins (or normalized
forms) match, handling invalid URLs safely; apply the same
normalization/comparison logic to the other hostKey branches referenced in this
diff so equivalent URLs produce the same key.
---
Nitpick comments:
In `@src/commands/projects.ts`:
- Around line 36-37: Extract the repeated .option("--api-host <url>", "API host
URL (defaults to PHOTON_API_HOST or built-in production)") calls into a small
helper function addApiHostOption(cmd) that accepts a Commander
command/command-builder and returns the command after calling .option(...);
replace each inline .option(...) occurrence in src/commands/projects.ts (and the
other command files/command builders at the listed locations) with
addApiHostOption(myCommand) so all commands share a single source of truth; name
the helper addApiHostOption and export it from a shared CLI utilities module
(e.g., cliOptions.ts) and update imports where used.
In `@src/commands/spectrum/avatar.ts`:
- Around line 103-107: Normalize the API host before comparing to PRODUCTION_URL
so equivalent URLs (e.g. "https://app.photon.codes/") don't trigger an
unnecessary --api-host; inside the block that builds parts (reference
opts.apiHost, PRODUCTION_URL, parts, shellQuote in avatar.ts) canonicalize both
values by trimming any trailing slash and lowercasing the host (or otherwise
normalizing the URL string) and then compare the normalized strings—only push
`--api-host ${shellQuote(...)}` when the normalized opts.apiHost differs from
the normalized PRODUCTION_URL.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2db77ae3-14c0-429b-8f6b-adb74cd6e548
📒 Files selected for processing (23)
README.mdsrc/commands/auth.tssrc/commands/billing.tssrc/commands/config.tssrc/commands/env.tssrc/commands/link.tssrc/commands/login.tssrc/commands/logout.tssrc/commands/ping.tssrc/commands/profile.tssrc/commands/projects.tssrc/commands/spectrum/avatar.tssrc/commands/spectrum/lines.tssrc/commands/spectrum/platforms.tssrc/commands/spectrum/profile.tssrc/commands/spectrum/users.tssrc/commands/whoami.tssrc/index.tssrc/lib/api-context.tssrc/lib/api.tssrc/lib/config.tssrc/lib/env.tssrc/lib/errors.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Usebun <file>instead ofnode <file>orts-node <file>
Bun automatically loads .env, so don't use dotenv
UseBun.serve()with WebSockets, HTTPS, and routes instead ofexpress
Usebun:sqlitefor SQLite instead ofbetter-sqlite3
UseBun.redisfor Redis instead ofioredis
UseBun.sqlfor Postgres instead ofpgorpostgres.js
Use built-inWebSocketinstead ofwspackage
PreferBun.fileovernode:fs's readFile/writeFile for file operations
UseBun.$template literal for shell commands instead of execa
Usebun --hotto run server files with hot module reloading
Files:
src/commands/logout.tssrc/commands/ping.tssrc/commands/whoami.tssrc/lib/api.tssrc/commands/link.tssrc/commands/login.tssrc/lib/api-context.tssrc/index.tssrc/commands/env.tssrc/commands/spectrum/profile.tssrc/commands/spectrum/avatar.tssrc/lib/config.tssrc/lib/errors.tssrc/commands/spectrum/platforms.tssrc/commands/spectrum/users.tssrc/commands/profile.tssrc/lib/env.tssrc/commands/config.tssrc/commands/spectrum/lines.tssrc/commands/auth.tssrc/commands/billing.tssrc/commands/projects.ts
**/*.{html,ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild
Files:
src/commands/logout.tssrc/commands/ping.tssrc/commands/whoami.tssrc/lib/api.tssrc/commands/link.tssrc/commands/login.tssrc/lib/api-context.tssrc/index.tssrc/commands/env.tssrc/commands/spectrum/profile.tssrc/commands/spectrum/avatar.tssrc/lib/config.tssrc/lib/errors.tssrc/commands/spectrum/platforms.tssrc/commands/spectrum/users.tssrc/commands/profile.tssrc/lib/env.tssrc/commands/config.tssrc/commands/spectrum/lines.tssrc/commands/auth.tssrc/commands/billing.tssrc/commands/projects.ts
**/*.{html,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use HTML imports with
Bun.serve()for frontend instead of Vite
Files:
src/commands/logout.tssrc/commands/ping.tssrc/commands/whoami.tssrc/lib/api.tssrc/commands/link.tssrc/commands/login.tssrc/lib/api-context.tssrc/index.tssrc/commands/env.tssrc/commands/spectrum/profile.tssrc/commands/spectrum/avatar.tssrc/lib/config.tssrc/lib/errors.tssrc/commands/spectrum/platforms.tssrc/commands/spectrum/users.tssrc/commands/profile.tssrc/lib/env.tssrc/commands/config.tssrc/commands/spectrum/lines.tssrc/commands/auth.tssrc/commands/billing.tssrc/commands/projects.ts
🧠 Learnings (20)
📚 Learning: 2026-04-21T18:03:31.894Z
Learnt from: lcandy2
Repo: photon-hq/dashboard PR: 40
File: apps/web/src/app/dashboard/[projectId]/layout.tsx:40-40
Timestamp: 2026-04-21T18:03:31.894Z
Learning: In `apps/web/src/app/dashboard/[projectId]/layout.tsx` (Next.js, TypeScript), the Eden `treaty<App>` client from `elysiajs/eden` returns union types for API responses (data/error union), requiring `as any` casts or explicit type narrowing when accessing response properties like `project.name`. The proper fix is to type `getProjectById` in `apps/web/src/lib/api/projects.ts` to return `ProjectRecord | null` (from `~/shared`) so the inferred type is usable without casting.
Applied to files:
src/lib/api-context.ts
📚 Learning: 2026-04-21T18:03:17.985Z
Learnt from: lcandy2
Repo: photon-hq/dashboard PR: 40
File: apps/web/next.config.ts:6-8
Timestamp: 2026-04-21T18:03:17.985Z
Learning: In the photon-hq/dashboard repo (`apps/web/next.config.ts`), `typescript.ignoreBuildErrors: true` is intentional. Eden's `import type { App }` (cross-workspace type import from `photon-dashboard/api`) triggers unresolvable type resolution issues during `next build` in the Turborepo monorepo. Runtime behavior is correct and types resolve at dev time via the separate `typecheck` turbo task. Do not flag this as an issue.
Applied to files:
src/index.tssrc/lib/config.ts
📚 Learning: 2026-04-20T03:54:45.839Z
Learnt from: CR
Repo: photon-hq/imessage-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-20T03:54:45.839Z
Learning: Applies to src/**/*.ts : Use `SendError(msg)` factory function to return `IMessageError` instead of using `new SendError()` constructor directly; use `instanceof IMessageError` in catch blocks
Applied to files:
src/index.tssrc/lib/errors.ts
📚 Learning: 2026-04-01T08:17:58.254Z
Learnt from: CR
Repo: photon-hq/advanced-imessage-ts PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-04-01T08:17:58.254Z
Learning: Applies to **/*.{js,ts,tsx} : Always handle errors explicitly with try-catch blocks or error callbacks, never silently fail
Applied to files:
src/index.ts
📚 Learning: 2026-04-18T01:54:11.728Z
Learnt from: CR
Repo: photon-hq/cosmos PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T01:54:11.728Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Throw `Error` objects with descriptive messages, not strings or other values
Applied to files:
src/lib/errors.ts
📚 Learning: 2026-03-11T19:21:45.922Z
Learnt from: CR
Repo: photon-hq/webhook PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-11T19:21:45.922Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Throw `Error` objects with descriptive messages, not strings or other values
Applied to files:
src/lib/errors.ts
📚 Learning: 2026-04-13T23:00:20.897Z
Learnt from: CR
Repo: photon-hq/spectrum-ts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-13T23:00:20.897Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Throw `Error` objects with descriptive messages, not strings or other values in JavaScript/TypeScript
Applied to files:
src/lib/errors.ts
📚 Learning: 2026-04-15T02:30:10.124Z
Learnt from: CR
Repo: photon-hq/whatsapp-business-ts PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-15T02:30:10.124Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Throw `Error` objects with descriptive messages, not strings or other values
Applied to files:
src/lib/errors.ts
📚 Learning: 2026-04-21T18:03:24.694Z
Learnt from: lcandy2
Repo: photon-hq/dashboard PR: 40
File: apps/web/src/lib/api/projects.ts:152-158
Timestamp: 2026-04-21T18:03:24.694Z
Learning: In `apps/web/src/lib/api/projects.ts`, `getEnabledPlatforms` intentionally returns `{}` on API error as a safe default (all platform toggles shown as off in the UI). This matches the original pattern where empty object means "unable to determine." Throwing is avoided because platforms are a non-critical feature and an error would crash the entire Spectrum page.
Applied to files:
src/commands/spectrum/platforms.ts
📚 Learning: 2026-04-28T03:08:36.661Z
Learnt from: CR
Repo: photon-hq/cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-28T03:08:36.661Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Bun automatically loads .env, so don't use dotenv
Applied to files:
src/commands/config.ts
📚 Learning: 2026-03-10T22:49:19.048Z
Learnt from: CR
Repo: photon-hq/advanced-imessage-kit PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-03-10T22:49:19.048Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Bun automatically loads .env, so don't use dotenv library
Applied to files:
src/commands/config.ts
📚 Learning: 2026-04-04T19:01:14.591Z
Learnt from: CR
Repo: photon-hq/explore-send-imessage-app PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-04-04T19:01:14.591Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `Bun.$` for shell command execution instead of `execa`
Applied to files:
src/commands/config.ts
📚 Learning: 2026-04-28T03:08:36.661Z
Learnt from: CR
Repo: photon-hq/cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-28T03:08:36.661Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `Bun.$` template literal for shell commands instead of execa
Applied to files:
src/commands/config.ts
📚 Learning: 2026-04-04T19:01:14.591Z
Learnt from: CR
Repo: photon-hq/explore-send-imessage-app PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-04-04T19:01:14.591Z
Learning: Applies to package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install`
Applied to files:
README.md
📚 Learning: 2026-04-28T03:08:36.661Z
Learnt from: CR
Repo: photon-hq/cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-28T03:08:36.661Z
Learning: Applies to package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json
Applied to files:
README.md
📚 Learning: 2026-03-10T22:49:19.048Z
Learnt from: CR
Repo: photon-hq/advanced-imessage-kit PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-03-10T22:49:19.048Z
Learning: Applies to package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json scripts
Applied to files:
README.md
📚 Learning: 2026-04-28T03:08:36.661Z
Learnt from: CR
Repo: photon-hq/cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-28T03:08:36.661Z
Learning: Applies to package.json : Use `bun run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>`
Applied to files:
README.md
📚 Learning: 2026-03-10T22:49:19.048Z
Learnt from: CR
Repo: photon-hq/advanced-imessage-kit PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-03-10T22:49:19.048Z
Learning: Applies to package.json : Use `bun run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>` for running scripts
Applied to files:
README.md
📚 Learning: 2026-04-29T19:00:22.283Z
Learnt from: lcandy2
Repo: photon-hq/cli PR: 11
File: package.json:14-23
Timestamp: 2026-04-29T19:00:22.283Z
Learning: In photon-hq/cli, the `LICENSE` file was intentionally removed (commit 9c451da) per project direction. The `license` field is deliberately absent from `package.json`, and `LICENSE` is not included in the `files` array. Do not flag the missing `license` field or absent LICENSE file as issues in this repository.
Applied to files:
README.md
📚 Learning: 2026-04-21T18:03:26.523Z
Learnt from: lcandy2
Repo: photon-hq/dashboard PR: 40
File: apps/web/src/lib/api/projects.ts:104-109
Timestamp: 2026-04-21T18:03:26.523Z
Learning: In `apps/web/src/lib/api/projects.ts` (photon-hq/dashboard), `checkPhoneAvailability` intentionally returns `{ available: true }` on error as a deliberate fail-open degradation. This avoids blocking the user flow when the availability service is down. Phone number uniqueness is enforced server-side on project creation regardless. Do not flag this behavior as a bug.
Applied to files:
src/commands/projects.ts
🔇 Additional comments (19)
src/lib/errors.ts (1)
9-12: Backend-oriented error messaging is clear and consistent.These message/doc updates correctly reflect host-key/backend semantics and keep central error handling behavior intact.
Also applies to: 31-33
README.md (1)
12-124: Docs migration to backend host model is thorough and consistent.Install guidance, precedence rules, command reference, and examples all line up with the new
PHOTON_API_HOST/--api-hostbehavior.Also applies to: 154-203
src/commands/ping.ts (1)
9-13:pinghost override migration is correctly wired.The command option and
getApicall now consistently use--api-hostwithout altering runtime behavior.src/commands/billing.ts (1)
29-35: Billing subcommands were migrated consistently to--api-host.Resolution and API wiring remain coherent across plans/show/checkout/manage paths.
Also applies to: 71-81, 120-136, 193-204
src/commands/logout.ts (1)
10-13:logoutnow targets the active backend correctly.The new option and resolver usage are aligned with the refactor and keep the local-clear + best-effort server revoke flow unchanged.
src/commands/spectrum/profile.ts (1)
16-26: Spectrum profile commands are cleanly migrated to--api-host.Both
showandupdatekeep their existing behavior while using the new backend resolution path.Also applies to: 62-82
src/commands/spectrum/avatar.ts (1)
16-30: Avatar command host-context migration looks solid.Option parsing, project resolution, API client setup, and recovery command context are all aligned with the new backend model.
Also applies to: 74-80
src/commands/spectrum/lines.ts (1)
18-28: Spectrum lines commands are consistently updated for backend-host targeting.The CLI flags and API resolution flow are aligned across
list,add, andremove.Also applies to: 60-75, 103-113
src/commands/spectrum/users.ts (1)
20-30:--api-hostmigration is correctly wired for Spectrum users commands.Option parsing, project resolution, and API client construction are consistent across
list,add, andremove.Also applies to: 66-77, 114-124
src/commands/profile.ts (1)
31-37: API-host migration for profile commands looks solid.
show,init, andupdateall consistently passopts.apiHostthroughgetApi.Also applies to: 94-117, 341-378
src/index.ts (1)
66-71: Top-level auth/network hints align with the new backend selection model.The updated guidance for
--api-host/PHOTON_API_HOSTis clear and consistent with the refactor.Also applies to: 85-86
src/commands/spectrum/platforms.ts (1)
17-27: Spectrum platforms commands are consistently migrated to--api-host.The option wiring and
resolveProject/getApiflow are coherent across list/enable/disable paths.Also applies to: 54-66, 76-84
src/lib/api-context.ts (1)
20-30:resolveProjecthost-based resolution update looks correct.The precedence and no-link guidance are aligned with the new backend model.
Also applies to: 45-47
src/lib/api.ts (1)
50-53: Good env-resolution split between raw URL and resolved backend.This keeps direct URL calls explicit while preserving normal
--api-host/ env-var resolution for credentialed flows.src/commands/link.ts (1)
14-18:--api-hostplumbing is consistent in both link and unlink paths.The resolution path and persisted key usage are aligned end-to-end (
resolveEnv/getApi→env.name).Also applies to: 61-65
src/commands/config.ts (1)
18-26: Config view now cleanly reflects the resolved active backend.Nice simplification: active backend, linked project, and “other links” are now all keyed off the same resolved backend key.
Also applies to: 35-36, 50-67
src/commands/login.ts (1)
14-15: Login command migration to--api-hostis correctly applied.Resolution, auth flow, and credential persistence stay coherent with the new backend model.
Also applies to: 22-23, 36-38
src/commands/auth.ts (1)
25-33: Auth status handling for missing vs corrupt credentials is solid.The row model and fallback behavior avoid false “logged in” states while still surfacing active backend context.
Also applies to: 38-47, 62-83
src/lib/config.ts (1)
5-16:resolveEnv()as a compatibility wrapper is a good simplification.Delegating to
resolveActiveEnv()centralizes resolution logic without breaking async call sites.
Address PR #14 review (Copilot + CodeRabbit, 4 distinct findings): - resolveActiveEnv now normalizes via URL.origin so trailing slashes, paths, queries, and fragments don't break the production back-compat fallback. `https://app.photon.codes/` and `https://app.photon.codes` both resolve to key "production". Persisted creds.apiUrl is now always canonical. - hostKey switches the dot replacement from `-` to `_` so distinct hosts can never collide on disk. `a-b.com` → `a-b_com`, `a.b-com` → `a_b-com`. Also strips IPv6 brackets and replaces `:` / `%` with `_`, and surfaces a clear error if the resulting key would exceed 64 chars (instead of letting it crash deep inside credentialsPath). SAFE_KEY_RE now allows a leading `_` since IPv6 hostnames can produce keys like `_3000` after `::` collapses. - env current now declares `--api-host <url>` so the help text matches reality. Previously it advertised the flag in its description but didn't accept it. - whoami's token-only branch said "on env" — updated to "on backend" for consistency with everywhere else post-refactor. api.ts also routes the `opts.url` branch through resolveActiveEnv so URL-mode requests get the same normalization. Verified locally: $ PHOTON_API_HOST=https://app.photon.codes/ photon env current production (https://app.photon.codes) $ PHOTON_API_HOST=https://a-b.com photon env current a-b_com (https://a-b.com) $ PHOTON_API_HOST=https://a.b-com photon env current a_b-com (https://a.b-com) # distinct from a-b.com $ PHOTON_API_HOST='http://[::1]:3000' photon whoami ✗ Not authenticated for backend "__1_3000". # downstream regex no longer trips $ photon env current --api-host https://wins.tld wins_tld (https://wins.tld) Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/api.ts`:
- Around line 50-54: The URL branch currently calls resolveActiveEnv(opts.url)
which triggers hostKey() and can throw for long valid hostnames; change the URL
path to only normalize the origin without computing host keys by introducing and
using a helper like normalizeApiOrigin(raw: string) that returns new
URL(raw).origin (throwing a clear "Invalid API host URL" error on parse failure)
and set env accordingly instead of calling resolveActiveEnv; keep
resolveEnv(opts.apiHost) for the non-URL branch and leave hostKey() usage only
in the credential/host lookup flow (e.g., functions resolveEnv or wherever
hostKey is intended).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 16cd8699-a3a9-4fd4-a617-c10f49ddf986
📒 Files selected for processing (4)
src/commands/env.tssrc/commands/whoami.tssrc/lib/api.tssrc/lib/env.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/commands/whoami.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js,jsx}: Usebun <file>instead ofnode <file>orts-node <file>
Bun automatically loads .env, so don't use dotenv
UseBun.serve()with WebSockets, HTTPS, and routes instead ofexpress
Usebun:sqlitefor SQLite instead ofbetter-sqlite3
UseBun.redisfor Redis instead ofioredis
UseBun.sqlfor Postgres instead ofpgorpostgres.js
Use built-inWebSocketinstead ofwspackage
PreferBun.fileovernode:fs's readFile/writeFile for file operations
UseBun.$template literal for shell commands instead of execa
Usebun --hotto run server files with hot module reloading
Files:
src/lib/api.tssrc/lib/env.tssrc/commands/env.ts
**/*.{html,ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild
Files:
src/lib/api.tssrc/lib/env.tssrc/commands/env.ts
**/*.{html,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use HTML imports with
Bun.serve()for frontend instead of Vite
Files:
src/lib/api.tssrc/lib/env.tssrc/commands/env.ts
🧠 Learnings (1)
📚 Learning: 2026-04-04T02:06:26.185Z
Learnt from: lcandy2
Repo: photon-hq/cosmos-cli PR: 25
File: Tests/CosmosAPITests.swift:46-46
Timestamp: 2026-04-04T02:06:26.185Z
Learning: In `Tests/CosmosAPITests.swift` (PR `#25`), the hardcoded API key `2f129b0dcff0565013d2713f05df8e0192c6aac7f98123275064431d4e45a51f` at line 46 (`private static let key`) is intentionally committed. It is a testing-environment-only key for `http://testing.cosmos.internal`, an internal service that is not internet-routable and has no access to production data. Do not flag it as a critical credential leak in future reviews; a low-severity/informational note about env-var best practice is acceptable but not required.
Applied to files:
src/lib/env.ts
🔍 Remote MCP Linear
Based on the searches, I was unable to locate the specific Linear issue for PR #14 in the available Linear workspace. Let me provide a comprehensive summary based on the context you've provided:
Summary of Review Context for PR #14
Based on the AI-generated analysis and PR objectives provided, here is the relevant context for reviewing this pull request:
Core Refactoring Objective
This PR implements a significant architectural shift in the Photon CLI from a hardcoded environment list model to a runtime backend URL selection model. The key changes are:
-
Replaced Mechanism:
- Old: Built-in environment names (
production,staging,dev) with persisted "current env" state - New: Dynamic backend selection via
PHOTON_API_HOSTenvironment variable and per-command--api-host <url>flag
- Old: Built-in environment names (
-
Credentials & Link Storage:
- Old: Credentials keyed by environment name
- New: Credentials keyed by sanitized hostname derived from URL (e.g.,
~/.config/photon/credentials/<key>.json) - Production URL gets back-compat treatment with literal key
"production"
-
API Resolution:
- New
resolveApiHost()function handles override/environment variable/production fallback - New
resolveActiveEnv()normalizes URLs (usingURL.originfor consistent canonicalization) and derives filesystem-safe host keys - New
hostKey()function sanitizes hostnames for filesystem storage (underscore-safe, 64-char limit, IPv6-aware)
- New
-
CLI Interface Changes:
- Removed:
-e, --env <name>option across all commands (auth, billing, config, login, logout, ping, profile, projects, whoami, and spectrum subcommands) - Added:
--api-host <url>option in its place - Removed:
envsubcommand tree (env list,env use,env add,env remove) - Retained:
env currentas a debug helper showing resolved backend
- Removed:
-
Error Handling Updates:
- Removed:
UnknownEnvErrorclass (no longer needed without env list) - Updated error messages: "environment" → "backend" terminology
- Updated login/session failure hints to suggest
--api-host <url>orPHOTON_API_HOST
- Removed:
Scope of Changes
Lines Changed: -468 / +309 across 23 files including:
- Core config/env resolution (
src/lib/config.ts,src/lib/env.ts,src/lib/api.ts,src/lib/api-context.ts) - Command implementations (login, logout, auth, billing, config, projects, profile, whoami, ping, link)
- Spectrum subcommands (avatar, lines, platforms, profile, users)
- Error handling (
src/lib/errors.ts) - README documentation with three install paths and alias explanation
Key Design Decisions
- URL Normalization: Uses
URL.originto canonicalize variants (trailing slashes, paths, queries) - Host Key Generation: Strips IPv6 brackets, replaces dots/colons with underscores, enforces 64-char limit, production back-compat mapping
- Bundle Security: Public bundle includes only production URL; bundle audit confirms no internal URL leaks
- No Persisted "Current Env": State is runtime-only (no config file changes)
Test Status
Per PR objectives:
- Typecheck/build: ✓ Pass
- Runtime checks: Listed (several post-merge verification items remain)
Related PRs
- PR
#13: Overlaps on README install/quickstart updates - PR
#1: Main refactor introducing the env-name/config model being replaced - PR
#3: Modifies same CLI config resolution code (potential conflicts) - PR
#8: Modifies same spectrum command modules (addition context)
🔇 Additional comments (2)
src/commands/env.ts (1)
13-21: Nice fix: help text and behavior are now aligned.
env currentnow actually accepts--api-hostand resolves throughresolveEnv(opts.apiHost), so UX and command behavior are consistent.src/lib/env.ts (1)
67-98: Host-key normalization and production back-compat look good.Parsing first, comparing on canonical origin for production, and sanitizing host/port into filesystem-safe keys is a strong implementation.
PR #14 review (CodeRabbit, 1 finding on the previous fix commit): The earlier consolidation routed `getApi({ url })` through `resolveActiveEnv` so URL-mode and resolve-mode shared normalization. But `resolveActiveEnv` also calls `hostKey()`, which enforces a 64-char ceiling (and other constraints) appropriate for filesystem-safe credential keys — not for picking an arbitrary HTTP target. The result: URL-mode (used by `ping --url <host>` for unauth health checks) suddenly rejected long-but-valid hostnames. Split the concerns: - Extract `normalizeOrigin(raw)` from resolveActiveEnv. It's the URL parse + .origin step, no key derivation. Exported. - URL-mode in api.ts now uses `normalizeOrigin(opts.url)` and a fixed `name: "custom"` — no hostKey, no key-limit enforcement, since no credential lookup happens on this path anyway. - resolveActiveEnv keeps using normalizeOrigin internally, so the canonical-URL behavior in resolve-mode is unchanged. Verified locally: a 91-char hostname now succeeds via `ping --url …` (URL-mode) and still surfaces the clear "too long" error via `PHOTON_API_HOST=… env current` (resolve-mode). Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const flag = opts.apiHost ? ` --api-host ${opts.apiHost}` : ""; | ||
| die(`No project linked for backend "${env.name}".`, { | ||
| hint: `Run \`photon link <id>${flag}\`, or pass \`--project <id>\`.`, |
There was a problem hiding this comment.
The hint builds a copy/pastable command by interpolating opts.apiHost without any quoting/escaping. Since this value is a URL, it can contain shell-significant characters (&, ?, ;, spaces, etc.), which can break the command or lead to accidental shell injection when users paste it. Please shell-quote/escape the URL in the hint (or reuse a shared quoting helper).
| @@ -17,16 +17,16 @@ export interface ResolvedProject { | |||
| * 3. `~/.config/photon/links/<active-env>.json` written by `photon link` | |||
There was a problem hiding this comment.
This comment still refers to links/<active-env>.json, but links are now keyed by the resolved host key (same key used for credentials). Updating this to links/<key>.json (or similar) would keep the docs accurate and consistent with the new backend selection model.
| * 3. `~/.config/photon/links/<active-env>.json` written by `photon link` | |
| * 3. `~/.config/photon/links/<key>.json` written by `photon link`, where | |
| * `<key>` is the resolved host key for the selected backend |
| /** Host key (e.g. "production" or "staging-app-photon-codes"). */ | ||
| constructor(public envName: string) { |
There was a problem hiding this comment.
The example host key staging-app-photon-codes doesn’t match the current hostKey() encoding (which replaces . and : with _). Please update this example to reflect the actual key format (e.g. staging-app_photon_codes).
| /** Host key (e.g. "production" or "staging-app-photon-codes"). */ | ||
| constructor(public envName: string) { |
There was a problem hiding this comment.
Same as above: the example host key format here should match hostKey() (underscores for ./:), otherwise it’s misleading when debugging auth/session issues.
| $ photon env current | ||
| production (https://app.photon.codes) | ||
| $ PHOTON_API_HOST=http://localhost:3000 photon env current | ||
| localhost-3000 (http://localhost:3000) |
There was a problem hiding this comment.
The hostKey() implementation replaces : with _, so for PHOTON_API_HOST=http://localhost:3000 the key printed by photon env current will be localhost_3000, not localhost-3000. Please update this example output to match actual behavior.
| localhost-3000 (http://localhost:3000) | |
| localhost_3000 (http://localhost:3000) |
| * underscore. Length 1-64. | ||
| */ | ||
| const SAFE_ENV_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/; | ||
| // First char allows `_` because hostKey() can produce keys like `_3000` |
There was a problem hiding this comment.
This comment’s example (_3000) doesn’t seem achievable with the current hostKey() logic for IPv6 hosts (it typically yields multiple underscores, e.g. __1_3000). Please adjust the example to match the actual output so the rationale for allowing a leading _ is accurate.
| // First char allows `_` because hostKey() can produce keys like `_3000` | |
| // First char allows `_` because hostKey() can produce keys like `__1_3000` |
| .description("show currently linked project(s) across backends") | ||
| .option("--json", "output JSON") | ||
| .action(async (opts) => { | ||
| const links = await listLinks(); |
There was a problem hiding this comment.
This command is now described as operating across “backends”, but the link:status output below still uses “env” terminology (e.g., the hint and table header later in this action). Consider updating those strings/headers as well so user-facing terminology is consistent.
| Credentials are stored **per host** (`$PHOTON_CONFIG_DIR/credentials/<key>.json` by default — see [config dir](#config-dir) below — mode 600), so you can be logged into multiple backends simultaneously. The `<key>` is derived from the URL — production keeps the literal name `production` for back-compat; other hosts get a sanitized hostname (e.g. `staging-app-photon-codes`, `localhost-3000`). | ||
|
|
There was a problem hiding this comment.
The documented <key> examples use hyphens (e.g. staging-app-photon-codes, localhost-3000), but hostKey() currently derives keys by replacing ., :, and % with underscores (e.g. staging-app_photon_codes, localhost_3000). Please align the README examples/description with the actual key derivation to avoid confusing users when locating credential/link files.
| const env = await resolveEnv(opts.apiHost); | ||
| const existing = await loadLink(env.name); | ||
| if (!existing) { | ||
| console.log(c.dim(`No link to clear for env ${c.bold(env.name)}.`)); |
There was a problem hiding this comment.
This user-facing message still says “env”, but the CLI has switched terminology to “backend”/host keys. Updating the string (and keeping terminology consistent across commands) would reduce confusion.
| console.log(c.dim(`No link to clear for env ${c.bold(env.name)}.`)); | |
| console.log(c.dim(`No link to clear for backend ${c.bold(env.name)}.`)); |
Stale from PR #14's review fix that switched the dot replacement from `-` to `_`. README still showed pre-fix output: - staging-app-photon-codes -> staging-app_photon_codes - localhost-3000 -> localhost_3000 Also added a brief note about the `_` substitution rationale (avoids `a-b.com` vs `a.b-com` collisions) since users seeing the unusual underscore separator will reasonably wonder why. Co-authored-by: Orca <help@stably.ai>
* docs: fix hostKey examples (now uses _ for collision-safety) Stale from PR #14's review fix that switched the dot replacement from `-` to `_`. README still showed pre-fix output: - staging-app-photon-codes -> staging-app_photon_codes - localhost-3000 -> localhost_3000 Also added a brief note about the `_` substitution rationale (avoids `a-b.com` vs `a.b-com` collisions) since users seeing the unusual underscore separator will reasonably wonder why. Co-authored-by: Orca <help@stably.ai> * copy: reposition tagline to 'bring your agents to any interface' Was: 'Photon CLI — replaces the dashboard web UI for end-user interaction' (positioned as a web replacement). Now: 'Photon CLI — bring your agents to any interface' (positioned as the integration surface for agents reaching Photon services). Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
Why
Until now
src/lib/env.tshardcodedproduction,staging, anddevURLs into the bundle. That means our internal staging URL shipped to npm and to every standalone binary in every release. This is the kind of thing security review eventually catches — better to fix it before that.What changes
The CLI now has a single runtime knob for backend selection:
The whole
env list / use / add / removetree goes away — redundant with the env var.env currentstays as a debug helper showing the resolved host.Credentials + links keyed by sanitized hostname
Previously:
~/.config/photon/credentials/{production,staging,dev}.jsonNow:
~/.config/photon/credentials/<key>.jsonwhere the key is derived from the URL — production keeps the literal nameproductionfor back-compat (so existing creds files survive the upgrade), other URLs get sanitized hostnames likestaging-app-photon-codes,localhost-3001,api-example-com-8080. Same idea forlinks/<key>.json.This means you can be logged into multiple backends simultaneously — there's no "current env" persisted on disk, just whichever PHOTON_API_HOST resolves at command time.
Flag rename:
-e, --env <name>→--api-host <url>Across all 13 commands that took the old flag. The rename is breaking, but the old flag was meaningless once env names were no longer a thing —
--env staginghad no defined value to override.Removed
BUILTIN_ENVSconstantBuiltinEnvName,DEFAULT_ENV,isBuiltincustomEnvs/currentEnvfrom persisted configsetCurrentEnv/addCustomEnv/removeCustomEnv/listEnvsUnknownEnvError(no more enumerable env list to be unknown of)env list / use / add / removesubcommandsBundle audit
The public bundle now contains zero references to anything other than the production URL.
Diff
23 files touched, -468 / +309 lines (refactor net-shrinks the codebase).
Test plan
bunx tsc --noEmitcleanbun run buildproduces dist/photon.js (~0.38 MB)photon env currentdefaults to productionPHOTON_API_HOST=https://x.tld photon env currentshows the overridephoton ping --api-host https://y.tld(flag wins over env var)PHOTON_API_HOST=not-a-url photon env currentsurfaces a clean URL-validation errorproduction.jsoncreds keep working (verified by hostKey special-case logic)bun add -g @photon-ai/photon+PHOTON_API_HOST=https://staging-app.photon.codes photon loginwrites creds tostaging-app-photon-codes.jsonMigration notes
Users with credentials files for old custom envs (
staging.json,dev.json, etc.) keep them on disk but they become orphaned — the CLI no longer resolves a "staging" name. Re-login withPHOTON_API_HOSTset to recreate creds under the new hostname-derived key. Production users are unaffected (the special case keepsproduction.jsonworking).🤖 Generated with Claude Code
Made with Orca 🐋
Summary by CodeRabbit
New Features
Changed
Removed
Documentation