fix(bin): single bin + lazy pho symlink so unpinned npx works#12
Conversation
Reduce package.json#bin from { photon, pho } to a single string
"./dist/photon.js" so npm 11's `npx @photon-ai/photon` (no version
pin) auto-resolves the binary. With multiple bin entries, npx skips
auto-resolve even when one matches the post-scope name, leaving
users with `sh: photon: command not found` unless they pin a
version. Verified against control: `@vercel/ncc` (single bin) works
unpinned; multi-bin scoped packages don't.
Restore the `pho` shortcut at runtime instead of declaring it in
`bin`. On first invocation, `ensurePhoAlias()` walks the standard
bin-dir layouts (local node_modules/.bin, bun-global, npm-global)
adjacent to the running script, finds the existing `photon` bin,
and creates `pho` as a relative symlink. Postinstall would have
been the obvious choice but Bun blocks postinstall by default,
which would silently strip `pho` from `bun add -g` users — our
primary install path. Runtime creation costs two existsSync calls
per launch and self-heals if `pho` is removed.
Verified locally: npm install + bun add from a packed tarball both
behave identically — `photon` available immediately, `pho` created
on first `photon` run.
Co-authored-by: Orca <help@stably.ai>
📝 WalkthroughWalkthroughThe PR removes the explicit Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as photon (entry)
participant Alias as ensurePhoAlias()
participant FS as Filesystem
User->>CLI: run `photon`
CLI->>Alias: invoke ensurePhoAlias()
Alias->>CLI: inspect process.argv[1]
Alias->>FS: probe candidate bin dirs (local, bun, npm global)
alt photon binary found in candidate
Alias->>FS: check `pho` with lstat
alt `pho` missing
Alias->>FS: create symlink `pho` -> `photon`
FS-->>Alias: success (or error)
else `pho` exists
FS-->>Alias: no-op
end
else photon binary not found
Alias-->>CLI: do nothing (silently ignore)
end
CLI-->>User: continue startup and run CLI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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 16 minutes and 41 seconds. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes unpinned npx @photon-ai/photon failing under npm 11 by switching the package to a single-bin declaration while preserving the pho alias via a best-effort runtime symlink created on first photon execution.
Changes:
- Change
package.jsonbinfrom a multi-key object (photon,pho) to a single string bin to restore npm 11 unpinnednpxauto-resolve. - Add
ensurePhoAlias()runtime hook to lazily create aphosymlink next to the resolvedphotonbin. - Update README to document that
phois created automatically after the firstphotonrun.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/lib/pho-alias.ts |
New helper that locates the active bin directory and creates a pho symlink next to photon. |
src/index.ts |
Calls ensurePhoAlias() at startup so the alias is created lazily. |
package.json |
Switches to a single bin entry ("./dist/photon.js") to make unpinned npx work on npm 11. |
README.md |
Documents the new lazy-creation behavior of the pho alias. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/lib/pho-alias.ts (1)
45-47: Keep best-effort behavior, but surface failures in debug mode.Swallowing all errors is fine for UX, but a debug-only trace would make alias issues diagnosable without affecting normal users.
🛠️ Proposed debug-only logging
- } catch { - // best-effort + } catch (err) { + if (process.env.PHOTON_DEBUG === "1") { + console.error("[pho-alias] failed to ensure pho alias", err); + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/pho-alias.ts` around lines 45 - 47, Keep the best-effort swallow but change the empty catch to capture the error and emit a debug-only trace: replace the bare "catch { /* best-effort */ }" with "catch (err) { if (process.env.DEBUG === 'true' || process.env.NODE_ENV !== 'production') console.debug('pho-alias error:', err); }" (or use the existing app logger if available) so the module's alias-resolution catch block surfaces failures only in debug/dev environments; ensure you reference the same catch in the pho-alias module and keep the swallow behavior for production.
🤖 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/pho-alias.ts`:
- Around line 24-35: The current logic computes bin-directory candidates
(candidates) even when running in source/dev mode; add an install-layout guard
so we only probe/write aliases when running from the installed package layout.
Before building/using candidates (where me, pkgRoot are computed), check for a
clear installed-layout signal—e.g., verify me points into the built dist
(contains "dist/photon.js") or that package metadata or expected dist layout
exists (use fs.existsSync on resolve(pkgRoot, "package.json") or
resolve(pkgRoot, "dist")), and return early if that check fails; only then
compute/use candidates and perform alias writes.
---
Nitpick comments:
In `@src/lib/pho-alias.ts`:
- Around line 45-47: Keep the best-effort swallow but change the empty catch to
capture the error and emit a debug-only trace: replace the bare "catch { /*
best-effort */ }" with "catch (err) { if (process.env.DEBUG === 'true' ||
process.env.NODE_ENV !== 'production') console.debug('pho-alias error:', err);
}" (or use the existing app logger if available) so the module's
alias-resolution catch block surfaces failures only in debug/dev environments;
ensure you reference the same catch in the pho-alias module and keep the swallow
behavior for production.
🪄 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: 89f7e5ee-3915-45f7-8ddd-b339aab67087
📒 Files selected for processing (4)
README.mdpackage.jsonsrc/index.tssrc/lib/pho-alias.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (4)
package.json
📄 CodeRabbit inference engine (CLAUDE.md)
package.json: Usebun installinstead ofnpm install,yarn install, orpnpm installin package.json
Usebun run <script>instead ofnpm run <script>,yarn run <script>, orpnpm run <script>
Files:
package.json
**/*.{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/index.tssrc/lib/pho-alias.ts
**/*.{html,ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild
Files:
src/index.tssrc/lib/pho-alias.ts
**/*.{html,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use HTML imports with
Bun.serve()for frontend instead of Vite
Files:
src/index.tssrc/lib/pho-alias.ts
🧠 Learnings (7)
📓 Common learnings
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`
Learnt from: CR
Repo: photon-hq/spectrum-cloud PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-16T22:15:43.438Z
Learning: Applies to package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install`
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
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
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>`
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 run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>`
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
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.
📚 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.mdpackage.json
📚 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:
package.json
📚 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:
package.json
📚 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:
package.jsonsrc/lib/pho-alias.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 package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json
Applied to files:
package.jsonsrc/lib/pho-alias.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 package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json scripts
Applied to files:
package.jsonsrc/lib/pho-alias.ts
🔍 Remote MCP Linear
Based on my search of the Linear workspace, I was unable to find specific issues directly related to this PR about the @photon-ai/photon CLI package and the unpinned npx issue. The searches returned results for other Photon-related projects (like @photon-ai/uri, Cosmos CLI, and iMessage Kit) and general package management issues, but nothing specifically addressing this PR's scope.
Summary: Additional Context Found
While Linear searches did not return issues specifically about this PR's photon CLI package or the npx binary resolution problem, I can provide context based on what's been documented:
Key Implementation Considerations for Code Review:
The PR addresses a known npm packaging constraint:
- Root cause clarity: npm 11's npx skips bin auto-resolve when
package.json#binhas multiple keys. The fix converts from an object definition to a single string, allowing auto-resolution for unpinnednpx@photon-ai/photon``
Review Focus Areas:
-
Runtime
ensurePhoAlias()Implementation (src/lib/pho-alias.ts):- Symlink creation strategy searches three bin directory candidates: local
node_modules/.bin, bun global bin, npm global prefix bin - Error handling silently swallows all failures (filesystem errors, missing directories, EACCES, etc.)
- Relative symlink
./photonassumes flat bin directory structure - Runs on every invocation but short-circuits via existence check (two
existsSynccalls overhead per run)
- Symlink creation strategy searches three bin directory candidates: local
-
Integration Point (
src/index.ts):ensurePhoAlias()executes before update notifier- No explicit error handling at call site (inherent to the swallowing strategy)
-
Package.json Change:
- Breaking change from object to string bin format
- README clarified that
phoalias is created at runtime on first execution
-
Testing Verification (documented as pending):
- Symlink creation confirmed with
bun addandnpm installfrom tarball - Minification and packed tarball compatibility verified
- Missing: unpinned
npx@photon-ai/photon--versionverification (requires 0.1.1 release) - Missing: global
bun addverification
- Symlink creation confirmed with
Known Design Trade-offs:
- Rejected postinstall script approach due to Bun's default security blocking; runtime creation ensures consistency across npm/bun/yarn
- Silent error swallowing is intentional to avoid breaking the CLI on permission issues or missing directories
🔇 Additional comments (3)
README.md (1)
34-35: Docs update is clear and aligned with runtime alias behavior.Good clarification that
phois created on firstphotonexecution after install.src/index.ts (1)
24-29: Startup integration point looks correct.Calling
ensurePhoAlias()at top-level before the rest of CLI flow is the right placement for lazy alias setup.package.json (1)
24-24: Single-bin mapping is the right fix for unpinnednpx@photon-ai/photon`` resolution.This change cleanly preserves the package-name command path while aliasing is handled at runtime.
…ard source mode Three review findings on PR #12: - bun add -g actually puts the bin at ~/.bun/bin/photon (5 levels up from pkgRoot), not ~/.bun/install/global/bin. Verified by running the install end-to-end: before this fix, no candidate matched and pho was silently dropped for the very install path the PR claims to fix. Updated candidates: local node_modules/.bin (2 up), npm global <prefix>/bin (4 up), bun global ~/.bun/bin (5 up). - existsSync follows symlinks, so a broken pho link wasn't detected and the subsequent symlinkSync would throw EEXIST. Switched to lstatSync, which sees the link entry itself. If anything is at the pho path (broken or not), leave it alone. - Source-mode runs (bun run src/index.ts) were probing candidate bin dirs uselessly. Added a regex guard that early-exits unless process.argv[1] matches the installed-package shape .../node_modules/.../dist/photon.js. Co-authored-by: Orca <help@stably.ai>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/pho-alias.ts (1)
64-66: Consider opt-in debug logging in the outer catch.Silently swallowing all failures makes support/debugging harder when alias creation unexpectedly fails.
🔧 Optional tweak
- } catch { - // best-effort + } catch (error) { + if (process.env.PHOTON_DEBUG === "1") { + console.warn("[photon] failed to ensure `pho` alias:", error); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/pho-alias.ts` around lines 64 - 66, The outer catch in src/lib/pho-alias.ts currently swallows all errors; change it to perform opt-in debug logging so failures are visible when needed — detect a debug toggle (e.g., process.env.DEBUG_PHO_ALIAS or an existing logger object) and log the caught error (error message/stack) inside the catch before continuing (use console.debug or processLogger.debug if available); keep behavior unchanged when the toggle is off so this remains best-effort.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/pho-alias.ts`:
- Around line 64-66: The outer catch in src/lib/pho-alias.ts currently swallows
all errors; change it to perform opt-in debug logging so failures are visible
when needed — detect a debug toggle (e.g., process.env.DEBUG_PHO_ALIAS or an
existing logger object) and log the caught error (error message/stack) inside
the catch before continuing (use console.debug or processLogger.debug if
available); keep behavior unchanged when the toggle is off so this remains
best-effort.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7a375a8d-00cf-4d57-ba88-f9a8da84524b
📒 Files selected for processing (1)
src/lib/pho-alias.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/pho-alias.ts
**/*.{html,ts,tsx,css}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
bun build <file.html|file.ts|file.css>instead ofwebpackoresbuild
Files:
src/lib/pho-alias.ts
**/*.{html,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use HTML imports with
Bun.serve()for frontend instead of Vite
Files:
src/lib/pho-alias.ts
🧠 Learnings (17)
📓 Common learnings
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`
Learnt from: CR
Repo: photon-hq/spectrum-cloud PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-16T22:15:43.438Z
Learning: Applies to package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install`
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
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
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>`
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 run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>`
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
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: Use `bunx <package> <command>` instead of `npx <package> <command>`
Learnt from: CR
Repo: photon-hq/cli PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-28T03:08:36.661Z
Learning: Use `bunx <package> <command>` instead of `npx <package> <command>`
Learnt from: CR
Repo: photon-hq/billing PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-17T02:57:19.075Z
Learning: Run `bun x ultracite fix` before committing to ensure compliance with Ultracite code standards
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.
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 <file>` instead of `node <file>` or `ts-node <file>`
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 <file>` instead of `node <file>` or `ts-node <file>`
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} : Use `bun <file>` instead of `node <file>` or `ts-node <file>` for running TypeScript and JavaScript files
📚 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:
src/lib/pho-alias.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:
src/lib/pho-alias.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 package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json
Applied to files:
src/lib/pho-alias.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 package.json : Use `bun install` instead of `npm install`, `yarn install`, or `pnpm install` in package.json scripts
Applied to files:
src/lib/pho-alias.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 package.json : Use `bun run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>`
Applied to files:
src/lib/pho-alias.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 package.json : Use `bun run <script>` instead of `npm run <script>`, `yarn run <script>`, or `pnpm run <script>` for running scripts
Applied to files:
src/lib/pho-alias.ts
📚 Learning: 2026-04-27T17:21:55.053Z
Learnt from: lcandy2
Repo: photon-hq/dashboard PR: 57
File: biome.jsonc:4-6
Timestamp: 2026-04-27T17:21:55.053Z
Learning: In `photon-hq/dashboard` (`biome.jsonc`), the `files.includes` array intentionally uses only negated glob patterns (e.g., `["!**/drizzle/meta", "!.claude", "!**/.next"]`) without a leading positive `"**"` pattern. This is a deliberate choice; do not flag it as requiring a positive base pattern.
Applied to files:
src/lib/pho-alias.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: Use `bunx <package> <command>` instead of `npx <package> <command>`
Applied to files:
src/lib/pho-alias.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 <file>` instead of `node <file>` or `ts-node <file>`
Applied to files:
src/lib/pho-alias.ts
📚 Learning: 2026-04-16T22:15:43.439Z
Learnt from: CR
Repo: photon-hq/spectrum-cloud PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-16T22:15:43.439Z
Learning: Applies to **/*.ts?(x) : Use `Bun.$` for shell commands instead of `execa`
Applied to files:
src/lib/pho-alias.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/lib/pho-alias.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} : Use `bun <file>` instead of `node <file>` or `ts-node <file>` for running TypeScript and JavaScript files
Applied to files:
src/lib/pho-alias.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/lib/pho-alias.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} : Use `Bun.$` template tag for shell commands instead of execa library
Applied to files:
src/lib/pho-alias.ts
📚 Learning: 2026-04-17T02:57:19.075Z
Learnt from: CR
Repo: photon-hq/billing PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-17T02:57:19.075Z
Learning: Run `bun x ultracite fix` before committing to ensure compliance with Ultracite code standards
Applied to files:
src/lib/pho-alias.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} : Use top-level regex literals instead of creating them in loops in JavaScript/TypeScript
Applied to files:
src/lib/pho-alias.ts
🪛 ast-grep (0.42.1)
src/lib/pho-alias.ts
[warning] 31-33: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
${escapeForRegex(sep)}node_modules${escapeForRegex(sep)}.+${escapeForRegex(sep)}dist${escapeForRegex(sep)}photon\\.js$,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔍 Remote MCP Linear
Summary of additional Linear search results
- Ran workspace issue searches for "photon" and "pho". Results returned many unrelated design and engineering issues (e.g., ENG-918, ENG-471, DES-62, etc.) but I did not find any Linear issue that documents the unpinned npx / package.json#bin multi-key problem or the runtime pho-alias implementation referenced in this PR.,
Sources
- Linear list_issues search for "photon" —
- Linear list_issues search for "pho" —
🔇 Additional comments (2)
src/lib/pho-alias.ts (2)
30-45: Installed-layout guard and candidate probing look correct.The early guard prevents source-mode side effects, and the candidate order sensibly prefers local bin before global locations.
52-63:lstatSyncgate avoids clobbering existingphoentries.Good call to treat any existing
phopath (including broken symlinks) as authoritative and exit safely.
Aligns with industry convention (@vercel/cli, @supabase/cli, @dokploy/cli, etc.) and matches the binary's role rather than duplicating the name. The previous reasoning behind @photon-ai/photon (PR #11) was that the post-scope segment must match the bin name for unpinned `npx <pkg>` to auto-resolve. That turns out to have been an overly strict reading of PR #12's findings — the actual rule npm 11 uses is "single bin auto-resolves regardless of name match." Confirmed empirically against @dokploy/cli, which has bin `dokploy` (not `cli`) and works unpinned. Bin shape switches from string-form to single-key object: "bin": "./dist/photon.js" → "bin": { "photon": "./dist/photon.js" } This keeps the binary registered as `photon` (string form would have made it `cli`, breaking every existing user invocation). Verified locally against @photon-ai/cli@0.1.3 tarball: - bun add -g: installs `photon`, lazy `pho` symlink fires on first run - npm install (local): same - `npx --yes <tarball>` resolves to 0.1.3 via single-bin auto-resolve Existing `@photon-ai/photon` users will keep getting 0.1.3 (final version on the old name). Migration path: re-install with `bun add -g @photon-ai/cli`.
Problem. After publishing 0.1.0,
npx @photon-ai/photon --version(unpinned) fails withsh: photon: command not found. Pinned forms (@latest,@0.1.0) work;bunxworks;bun add -gthenphotonworks. The break is npm 11'snpxskipping bin auto-resolve whenbinhas multiple keys, even if one matches the package's post-scope name. Confirmed against control:npx @vercel/ncc(single bin) resolves unpinned; ours doesn't.Fix
bin: { photon, pho }→bin: "./dist/photon.js". npm registers the single bin under the package's post-scope name (photon). Unpinnednpx @photon-ai/photonnow auto-resolves.Lazy
phosymlink at runtime (src/lib/pho-alias.ts). Walks the standard bin-dir layouts adjacent to the running script — localnode_modules/.bin, bun-global<root>/bin, npm-global<prefix>/bin— finds the existingphotonbin, and createsphoas a relative symlink next to it. Firstphotoninvocation after install creates it; subsequent runs short-circuit onexistsSync(pho).Why not
postinstall?Tried. Works for npm/yarn/pnpm but Bun blocks postinstall scripts by default:
bun add -gis documented as the primary install path. Silently droppingphofor the majority of users isn't acceptable, andbun pm trust @photon-ai/photonis too much friction. Runtime creation works uniformly across all package managers without trust opt-in.Cost
After
phoexists, twoexistsSynccalls perphotoninvocation (~microseconds). Errors are swallowed —phois convenience, never load-bearing.Verified locally
Test plan
bunx tsc --noEmitcleanbun run buildproduces dist/photon.js (~0.38 MB)bun addfrom tarball:photonworks, first run createsphonpm installfrom tarball: sameprocess.argv[1]references survived minify (verified in tarball)npx @photon-ai/photon --version(unpinned) returns0.1.1bun add -g @photon-ai/photon→photon login→pho lsworksTriggers a release
Add
releaselabel after review to publish 0.1.1.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.Summary by CodeRabbit
New Features
phocommand alias is now created automatically the first time you runphoton; the CLI ensures the alias at startup.Documentation
phoalias is created automatically on first run.