cli: add command-line interface - #29
Conversation
- src/index.ts: paparam CLI entry point - src/runtime.ts: worklet spawning via pear-runtime - src/qr.ts: QR code terminal display - src/commands/: send, receive, status, cancel, disconnect, peek
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new AlterSend CLI app: runtime/type bindings and config, a paparam-based executable, join-code/QR/progress helpers, five commands (send, receive, peek, check-update, update), TypeScript/Vitest config, package manifest, README, and root npm scripts for building/testing the CLI. ChangesAlterSend CLI Application
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as index.ts
participant Command as Command Module
participant Runtime as createCliRuntime
participant Pear as PearRuntime
participant Worker as WorkerClient
participant Peer
User->>CLI: npm run cli:dev send file.txt
CLI->>Command: dispatch send(files, options)
Command->>Runtime: createCliRuntime(storage, onEvent)
Runtime->>Pear: new PearRuntime(config)
Runtime->>Pear: run(workerEntry, args --storage=...)
Runtime->>Worker: require and create worker client with onEvent
Runtime->>Worker: await client.ready
Runtime-->>Command: return {client, destroy, pear}
Command->>Worker: client.host()
Command->>User: display join code / QR
Peer->>Worker: join with code
Worker->>Command: onEvent('status','peer-connected')
Command->>Worker: client.shareFiles(...)
Worker->>Command: onEvent('status','download-progress')
Command->>Command: writeProgress()
Worker->>Peer: transfer chunks
Worker->>Command: onEvent('status','disconnected')
Command->>Runtime: destroy()
Command->>User: exit 0
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
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 unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/cli/README.md`:
- Line 158: Add a regression test that verifies the CLI falls back to a temp
directory when the default storage is locked: in the CLI test suite create a
test that, without passing --storage, sets up the same lock file path checked by
createCliRuntime() (the .../app-storage/core/db/LOCK used in
apps/cli/src/runtime.ts), then invokes the CLI runtime creation path and asserts
that the runtime used makeTempStorage() (or that the resolved storage dir is
inside a temp directory) and that providing --storage <path> bypasses the
fallback; reference createCliRuntime(), makeTempStorage(), and the storagePath
handling when locating the LOCK file to locate and implement the test.
In `@apps/cli/src/commands/check-update.ts`:
- Around line 13-18: When createCliRuntime(...) succeeds in check-update and
update commands, ensure the Pear runtime is always torn down on error: in both
check-update.ts and update.ts add a guarded cleanup before exiting by calling
runtime?.destroy() inside each catch/failure path (i.e., before any
process.exit(1) or early return), so that the runtime variable created by
createCliRuntime is destroyed on failure as well as success.
In `@apps/cli/src/commands/peek.ts`:
- Around line 12-25: The transfer-ready handler (onEvent) currently prints
offers then returns without stopping the command; change it to initiate shutdown
by calling client.disconnect() after printing the offers, and wire the existing
donePromise resolution to the status events 'disconnected' and
'peer-disconnected' (same pattern used in receive.ts) so the command resolves
only after the client emits a disconnect; ensure you do not resolve the
donePromise immediately in onEvent but wait for the
disconnected/peer-disconnected event to resolve it.
In `@apps/cli/src/commands/receive.ts`:
- Around line 45-58: When downloadFiles() rejects or clientRef.disconnect()
throws, the code only logs the error and never settles donePromise, leaving the
command running; update the promise handling around
clientRef.downloadFiles(downloads) so that any rejection from downloadFiles()
immediately settles donePromise (reject or resolve as your donePromise API
expects) and, after a successful download when calling clientRef.disconnect(),
chain a .catch on disconnect() to also settle donePromise on failure; ensure you
guard with the interrupted flag to avoid double-settling and reference the
existing clientRef.downloadFiles, clientRef.disconnect, interrupted and
donePromise symbols when making these changes.
In `@apps/cli/src/commands/send.ts`:
- Around line 77-82: The SIGINT handler currently awaits client.disconnect()
then calls exitDeferred(), which can stall if disconnect() rejects; modify the
process.on('SIGINT') handler (the interrupted guard block) to call
client.disconnect() inside a try/finally so exitDeferred() is always invoked
even on rejection — i.e., wrap await client.disconnect() in try { await
client.disconnect() } finally { exitDeferred() } and optionally log the
disconnect error inside a catch block before rethrowing or swallowing; apply the
same pattern in the corresponding SIGINT handler in receive.ts.
In `@apps/cli/src/commands/update.ts`:
- Around line 23-39: The promise created around the updater never rejects so
failures in the async onUpdated handler (which awaits
runtime!.pear.updater.applyUpdate()) are swallowed; modify onUpdated (the
handler registered with runtime!.pear.updater.on('updated', onUpdated)) to catch
any errors from applyUpdate() and call the Promise's reject (or propagate the
error to the outer try/catch) after removing the listener and clearing the
timeout, or alternatively remove async/await from onUpdated and forward the
applyUpdate() promise to the outer Promise so the outer Promise rejects on
failure; ensure you still call runtime?.destroy() on success and always clean up
the listener/timeout on both success and failure.
In `@apps/cli/src/index.ts`:
- Around line 75-97: The extractFilesFromRest function incorrectly assumes any
unknown flag without '=' has a value and does i += 2, which can skip real file
args; change the logic in extractFilesFromRest so that when encountering a flag
(item.startsWith('--')) and it is not one of the known boolean flags and has no
'=', you only skip the next token if it exists AND does not start with '--'
(i.e., it's a value), otherwise advance by 1; update the branch that sets i += 2
to perform a bounds-and-prefix check on items[i+1] before skipping it, using the
existing variables flagName and items to locate the code.
In `@apps/cli/src/progress.ts`:
- Around line 15-23: The writeProgress function marks completion and computes
the displayed percent/bar from a rounded value which can prematurely set
finalised and cause negative repeat() lengths when current > total; change
finalised to be set when state.current >= state.total, compute pct from a
clamped ratio (clamp Math.round((current/total)*100) into 0..100) or clamp the
intermediate ratio to 0..1 before multiplying, and ensure any derived filled
value uses a clamped range (0..PROGRESS_CHARS) or compute filled from the
clamped pct so PROGRESS_CHARS - filled cannot go negative; update the same logic
used by formatLine/other occurrences (see writeProgress, formatLine,
ProgressState, finalised, lastPct, lastWrite, THROTTLE_MS, PROGRESS_CHARS) to
keep behavior consistent.
In `@apps/cli/src/qr.ts`:
- Around line 7-10: The else branch in displayQR (apps/cli/src/qr.ts) prints the
join code again in non-TTY mode; remove the duplicate console.log for `Join
code: ${topic}` and only emit the suppression note `(QR code suppressed —
non-TTY environment)` so callers (e.g., send command) remain the single source
of the join code output. Ensure the function still returns/behaves the same
otherwise.
In `@apps/cli/src/runtime.ts`:
- Around line 48-75: The current preflight probe in isStorageLocked +
createCliRuntime is racy; instead keep using getDefaultStorage() but attempt to
initialize the runtime normally and only fall back to a temp dir when the real
startup fails due to the corestore LOCK. Update createCliRuntime to try using
the chosen dir (defaultPath) first, catch the specific lock error thrown during
runtime/worklet startup (detect via error message/errno from the corestore/open
routine), then call makeTempStorage() and retry initialization with that temp
dir (ensure you only fallback on lock-related errors and propagate other
errors). Keep isStorageLocked as optional utility or remove its use in this
flow; reference functions: isStorageLocked, createCliRuntime, makeTempStorage,
and the runtime initialization code path that opens corestore/worklet.
- Around line 31-36: The two functions getWorkerEntryPath() and
getWorkerClientPath() currently build checkout-relative node_modules paths which
can resolve incorrectly; change them to resolve the package using Node's module
resolution (e.g. require.resolve('`@altersend/core/dist/worklet/index.js`') and
require.resolve('`@altersend/core/dist/client/worker-client.js`')) instead of
path.join with __dirname, and throw or log a clear error if require.resolve
fails so worker startup fails fast with a helpful message.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 896485eb-a822-45fe-bf31-a97b151252c1
📒 Files selected for processing (17)
CLAUDE.mdapps/cli/README.mdapps/cli/package.jsonapps/cli/src/commands/check-update.tsapps/cli/src/commands/peek.tsapps/cli/src/commands/receive.tsapps/cli/src/commands/send.tsapps/cli/src/commands/update.tsapps/cli/src/index.tsapps/cli/src/joinCode.tsapps/cli/src/progress.tsapps/cli/src/qr.tsapps/cli/src/runtime.tsapps/cli/src/types.d.tsapps/cli/tsconfig.jsonapps/cli/vitest.config.tspackage.json
- worker paths: use require.resolve() instead of hardcoded relative path - race condition: catch lock error at runtime, fallback to temp storage - SIGINT teardown: add try/finally in send.ts and receive.ts - peek disconnect: proper done/resolve flow, calls disconnect after offers - progress overshoot: clamp values, use current>=total for finalised - QR duplicate: remove duplicate join code log in non-TTY path - error cleanup: add runtime?.destroy() in catch blocks of check-update and update - extractFilesFromRest: fix unknown flag handling, skip single token not pair - lock file: sync package-lock.json
32c01cc to
4201986
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/cli/src/runtime.ts`:
- Around line 86-99: The fallback path creates a temporary PearRuntime named
fallbackPear and starts the worker/client from it, but the function still
returns the original pear instance; fix this by returning or assigning the
fallback instance instead of the locked original: when you construct
fallbackPear (PearRuntime) and call fallbackPear.run(...) and createClient(...),
set pear = fallbackPear (or directly return fallbackPear) so runtime.pear, its
.storage and .updater/event listeners point to the actual runtime in use (ensure
code paths that expect pear afterwards use the updated reference).
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 022ca422-48da-46c7-957b-42f73fa8c9d9
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
apps/cli/src/commands/check-update.tsapps/cli/src/commands/peek.tsapps/cli/src/commands/receive.tsapps/cli/src/commands/send.tsapps/cli/src/commands/update.tsapps/cli/src/index.tsapps/cli/src/progress.tsapps/cli/src/qr.tsapps/cli/src/runtime.ts
💤 Files with no reviewable changes (1)
- apps/cli/src/qr.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/cli/src/commands/update.ts
- apps/cli/src/index.ts
- apps/cli/src/commands/receive.ts
- apps/cli/src/commands/check-update.ts
- apps/cli/src/commands/send.ts
- apps/cli/src/commands/peek.ts
denislupookov
left a comment
There was a problem hiding this comment.
Hey @nak2002k , thanks for the adding CLI support !
I tried running it locally and it crashes on running the commands, so that's the main thing to fix before this can go in. A couple of the send options don't seem to work either, and the test script runs nothing.
Can you take another look? Thanks!
|
|
||
| ## Releasing (for maintainers) | ||
|
|
||
| The CLI ships via [pear](https://pear.software) — same infrastructure as desktop app. |
There was a problem hiding this comment.
I think it should link to https://pears.com, but let's just drop that link and the releasing section for now. I'll add proper release support later.
There was a problem hiding this comment.
Hey @denislupookov,
mb I will take a look and fix the things this week.
ping me if you need any specific changes!
Summary
Adds a standalone CLI tool (
altersend) for P2P file transfer without a GUI. Targets users who prefer terminal workflows or need automation scripts.Changes
apps/cli/package with TypeScript strict modesend— share files, display join code or QR code, optional temp deletionreceive— download files from join code, auto-exits after transferpeek— preview file offers without downloadingcheck-update/update— pear-based auto-update support--no-updatesflag on all commands to skip OTA checks--storage,--output,--qr,--tempflags for relevant commandsTesting
Screenshots / recordings
N/A (terminal-only tool)
Related issues
Closes #24
Summary by CodeRabbit
New Features
Documentation
Chores