Skip to content

chore(cli): make the module CLI publishable (HT-121) - #189

Merged
zaridan merged 2 commits into
mainfrom
chore/ht-121-cli-publishable
Aug 4, 2026
Merged

chore(cli): make the module CLI publishable (HT-121)#189
zaridan merged 2 commits into
mainfrom
chore/ht-121-cli-publishable

Conversation

@zaridan

@zaridan zaridan commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🟢 SAFE TO MERGE

All gates green on head 3fb44f1d: Quality (typecheck/lint/test/coverage), CodeQL, gitleaks, and CodeRabbit with zero inline findings. Maintainer instructed merge, 2026-08-04: "merge 189".

Codex (adversarial, in place of CodeRabbit for the pre-review): 1 finding, real, fixed and pinned by a test verified failing against the unfixed build.

Still true after merge: private: true remains on — this ticket makes publishing possible and does not publish. The package name stays a maintainer decision until the first publish makes it permanent.


Original verdict at open — 🟡 NEEDS YOUR DECISION

Makes the CLI publishable and proves it by packing. Publishes nothingprivate: true stays on, and the first publish is a deliberate act that's yours. One decision below is mine and is near-irreversible once exercised.

Codex (adversarial, in place of CodeRabbit): 1 finding, real, fixed. Gates green: 2005 tests, typecheck, Biome 364 files.

Decision provenance

Decision — in plain words Source
Do HT-121 now You: "do HT-121"
Published name is @helpthread/module; the command stays helpthread-module Merge approved by you 2026-08-04 ("merge 189") — the NAME itself remains INFERRED and reversible until first publish: I first defaulted to the unscoped name because I couldn't verify the org existed. You logged in, the helpthread org is real (owner zaridan), so scoping became the better option. An npm name is permanent after first publish (no unpublish past 72h), so confirm it before that day. Changing it now is one line.
publishConfig.access: "public" Mechanical consequence — a scoped package publishes restricted by default, which would silently make a public tool private

What was broken

The package could not be installed from npm at all. Its source imported the shared verifier by repo-relative path (resolves only inside this monorepo), and its bin executed TypeScript through tsx. Either alone fails before a command can run.

What changed

esbuild (pinned exactly, no caret) bundles the CLI plus the src/modules/artifact verifier into one package-contained file. Bundling copies bytes at publish time, which keeps the CLI and the engine on one implementation of verification policy — a hand-maintained second copy would let them drift, which is the failure the shared library exists to prevent. The pinned publisher key stays a compiled-in literal, unreachable from any flag, env var, or catalog response; asserted by a test rather than assumed.

Verified by packing and installing, not by reasoning

  1. npm pack -w cli → tarball
  2. npm install <tgz> into a directory outside this repo
  3. From that isolated install: install --help and verify --help both run on plain Node, no tsx present
  4. Real offline verify of the published draft-assistant 0.3.0 assets → OK, module draft-assistant, version 0.3.0, keyId riq-2026
  5. One byte flipped → sha256-mismatch, exit 1
  6. tar tzf → exactly LICENSE, dist/main.js, package.json, README.md. No TypeScript, no tests, nothing outside the allowlist. (I re-ran 1 and 6 independently.)

That discipline caught two real bugs config-reading would not have. The first build emitted a shebang from both the source file and the esbuild banner — a second #!/usr/bin/env node is a SyntaxError on line 2 under Node's ESM loader. And Codex found dist/ was never cleaned before a build: with files admitting the whole directory and npm publish running no tests, any stale artifact would have shipped silently. Both are now pinned by tests I watched fail against the unfixed code.

Note on the test numbers

Two intermediate runs of mine reported 4 and then 12 failures in webauthn/queue/gmail code this PR never touches. Those were my own resource contention — I had stacked concurrent full-suite runs and drove load average to 93; the tests each spin up an in-memory Postgres and starved. On a settled machine: 2005/2005, exit 0, duration halved. Recording it here because the intermediate numbers are in the session history and shouldn't be mistaken for a real regression.

🤖 Generated with Claude Code

zaridan and others added 2 commits August 4, 2026 10:11
The package could not be installed from npm: its source imported the
shared verifier by repo-relative path, which resolves only inside this
monorepo, and its bin executed TypeScript through tsx. Either alone made
an install fail before a command could run.

esbuild (pinned exactly, no caret) now bundles the CLI and the
src/modules/artifact verifier into one package-contained file. Bundling
copies bytes at publish time, which keeps the CLI and the engine on ONE
implementation of verification policy; a hand-maintained second copy
would let them drift, which is the failure the shared library exists to
prevent. The pinned publisher key stays a compiled-in literal,
unreachable from any flag, env var, or catalog response — asserted by a
test rather than assumed.

Published name is @helpthread/module; the command stays
helpthread-module. The scope protects the namespace without making the
command harder to type, and publishConfig.access is public because a
scoped package would otherwise publish restricted by default. The
package remains private:true — this ticket makes publishing possible, it
does not publish.

Verified by packing and installing, not by reasoning: npm pack, install
the tarball into a directory outside this repo, then run both commands
and a real offline verify of the published draft-assistant 0.3.0 assets
from that isolated install — OK for the genuine artifact, sha256-mismatch
and non-zero exit after flipping one byte. That step earned its keep: the
first build emitted a shebang from both the source file and the esbuild
banner, and a second shebang is a SyntaxError under Node's ESM loader.
Only running the built artifact surfaces that.

The packed tarball contains exactly LICENSE, dist/main.js,
package.json, README.md — no TypeScript, no tests, nothing outside the
files allowlist. A test pins that list so it cannot silently grow.

2004 tests, typecheck clean, Biome 364 files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
files admits the whole dist/ directory and npm publish runs no tests, so
anything that ever landed there — a bundle from an older entry point, a
sourcemap from a debugging session, a file dropped by hand — would ship
silently to every operator. The build now starts from an empty directory,
making the published contents a function of the build script alone.

Pinned by a test that plants a stray file, rebuilds, and asserts it is
gone; verified failing against the un-cleaned build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
helpthread Ready Ready Preview Aug 4, 2026 6:12pm
helpthread-inbox Ready Ready Preview Aug 4, 2026 6:12pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@zaridan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e933943-4424-43f8-92a3-f7b058955a9b

📥 Commits

Reviewing files that changed from the base of the PR and between c8a60dd and 3fb44f1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • cli/LICENSE
  • cli/README.md
  • cli/bin/helpthread-module.js
  • cli/package.json
  • cli/scripts/build.mjs
  • cli/src/main.ts
  • tests/cli/build.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zaridan
zaridan merged commit b7a16eb into main Aug 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant