feat(cp): copy multiple files/dirs in one call#131
Merged
Conversation
`agentbox cp` and the in-box `agentbox-ctl cp toHost|fromHost` took a single
source per call; copying several files meant several invocations (and, from
inside a box, several host approval prompts). Both now accept multiple sources
in one call — list files/dirs before the destination, which must then be a
directory. Wildcards are handled by the invoking shell (host shell for host-side
sources, box shell for in-box sources), so no glob expansion lives in the code
and there's no box->host shell-injection surface.
- Host CLI: `cp <paths...>` (variadic, arity-split). One arg keeps the
download-to-cwd back-compat; a single source keeps full docker-cp rename
semantics. >=2 sources require a directory dest. All sources must be on the
side opposite the dest, and box sources must name one box. Size guard runs
per source.
- Providers: `uploadPath`/`downloadPath` take `string[]`. Docker groups sources
by parent dir (one tar per group), hoists mkdir/parent-chain-chown, chowns
each landed entry. Cloud loops the single-source primitive serially.
- Wire: `cp.*` RPC carries `{sources[], dest}` with backward-tolerant
normalization of the legacy `{boxPath, hostPath}` shape; shared in cp-rpc.ts.
- Relay: the cloud `runCpRpc` now re-shells `agentbox cp` like the docker path
instead of calling the cloud primitives directly — so excludes and the size
guard are honored on every provider (the cloud path silently dropped them
before, while the consent prompt advertised them). Consent prompt lists all
sources.
- Docs/skills/system prompts updated; new unit tests for parseArgs, the cp-rpc
wire helpers, and cloud multi-source orchestration.
Verified live (docker): multi-source + wildcard upload, multi-source download,
dest-not-a-dir / box-to-box errors, single-source rename + download-to-cwd
back-compat, and the in-box ctl variadic parsing.
Claude-Session: https://claude.ai/code/session_01XvuW3YwvHzvCrmXMJyC33W
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
agentbox cpand the in-boxagentbox-ctl cp toHost|fromHosttook a single source per call. Copying several files meant several invocations — and from inside a box, several host approval prompts. Both now accept multiple sources in one call: list files/dirs before the destination (which must then be a directory). Wildcards are handled by the invoking shell (host shell for host-side sources, box shell for in-box sources), so there's no glob expansion in the code and no box→host shell-injection surface.How
apps/cli/src/commands/cp.ts):cp <paths...>(variadic, arity-split). One arg keeps download-to-cwd back-compat; a single source keeps full docker-cp rename semantics.>=2sources require a directory dest. All sources must be on the side opposite the dest, and box sources must name one box. The size guard runs per source (not summed).uploadPath/downloadPathtakestring[]. Docker groups sources by parent dir (onetarper group), hoistsmkdir/parent-chain-chown out of the loop, and chowns each landed entry (neverchown -Rthe whole dest). Cloud loops the single-source primitive serially (fixed remote staging path).cp.*RPC carries{sources[], dest}with backward-tolerant normalization of the legacy{boxPath, hostPath}shape sent by an older baked-inctl— shared inpackages/relay/src/cp-rpc.ts.runCpRpcnow re-shellsagentbox cplike the docker path instead of calling the cloud primitives directly. So excludes,--no-default-excludes, and the size guard are honored identically on every provider — the cloud path previously dropped them while the consent prompt advertised them. The consent prompt now lists all sources.Tests / verification
parseArgsarity/direction/errors (apps/cli/test/cp-parse.test.ts), the cp-rpc wire helpers incl. legacy normalization + that excludes reach the argv (packages/relay/test/cp-rpc.test.ts), cloud multi-source loop + dest-dir enforcement (packages/sandbox-cloud/test/cloud-cp.test.ts). Full repo typecheck + lint + tests green (the unrelatedqueue.test.tsmaxWorkingflake only appears under parallelturbo run test; passes deterministically in isolation).Docs (
cli.mdx,sync-and-git.mdx,features.md), both agentbox-info skill copies, the setup skill, and all fivecustom-system-CLAUDE.mdprompts updated.https://claude.ai/code/session_01XvuW3YwvHzvCrmXMJyC33W
Note
Medium Risk
Touches host↔box file transfer and relay-gated cp on every provider; the cloud path behavior change (excludes/size guard) is a correctness fix but alters what actually runs after approval. Multi-source copies are serial and non-atomic.
Overview
agentbox cpandagentbox-ctl cp toHost|fromHostnow take a variadic path list: several sources, then a destination (with ≥2 sources the dest must be a directory). Single-arg download-to-cwd and single-source rename semantics are unchanged.parseArgsenforces one box side, no mixed host/box sources, and one box for multi-downloads; upload size blocking runs per source, not on the total.Providers change
uploadPath/downloadPathtostring[]. Docker groups sources by parent for tar streaming and handles multi-upload chown without re-owning the whole dest dir; cloud runs the single-source primitive serially per source.Relay / in-box wire:
cp.*RPC uses{sources, dest}with legacy{boxPath, hostPath}normalization inpackages/relay/src/cp-rpc.ts. Docker and cloud relay handlers both build argv via shared helpers and re-execagentbox cp— cloud no longer calls cloud cp primitives directly, so--exclude, default excludes, and the size guard match what the consent prompt shows. Call sites (paste-image, session teleport) pass one-element source arrays.Docs, skills, and unit tests cover parsing, cp-rpc, and cloud multi-source behavior.
Reviewed by Cursor Bugbot for commit 675689f. Configure here.