feat: add install command for single package install#28
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Add the install pipeline (registry metadata fetch, version pick, ZIP verify/scan/extract, config and lock persistence) with CLI wiring, docs, and integration tests including global scope and conflict handling. Co-authored-by: Cursor <cursoragent@cursor.com>
Roll back extracted files on save or partial extract failures, respect HOME/USERPROFILE for global installs, require metadata compatibility, and stabilize CLI registry error codes and exit statuses. Expand install tests for no-save, checksum mismatch, persistence rollback, and shared mock-registry subprocess coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
Validate archive entry names with explicit traversal checks and resolveContainedExtractPath containment before any filesystem writes. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_4d44d4c4-d028-4802-a1ee-40a89d934028) |
Reject archive entries containing ".." in the extract loop before path mapping and filesystem writes so CodeQL recognizes the sanitizer. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_db9692ad-4cbd-4e0a-bd46-18fba66eb559) |
There was a problem hiding this comment.
Pull request overview
Adds the MVP single-package install flow to the CLI (agents-repo install <package-id> / i) by wiring a new install module pipeline (registry fetch → version/artifact selection → download → sha256 verify → ZIP security scan → target-aware extraction → optional config/lock persistence) and surfacing it via the Commander CLI with global --dry-run and --no-save.
Changes:
- Implement
installcommand + root globals--dry-run/--no-save, including JSON output + exit code mapping for registry/install errors. - Add install pipeline services/infrastructure (scope resolution, version resolution, metadata fetch, ZIP scanning, extraction, persistence, rollback).
- Add Vitest coverage for install pipeline (unit + subprocess tests) and update docs/architecture to reflect delivered behavior.
Reviewed changes
Copilot reviewed 41 out of 43 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/modules/install/zipSecurityScanner.test.ts | Unit tests for ZIP security scan acceptance/rejection cases |
| tests/modules/install/validateInstallTarget.test.ts | Tests for target support validation (index + metadata) |
| tests/modules/install/resolveInstallVersion.test.ts | Tests for semver-based version selection behavior |
| tests/modules/install/packageExtractor.test.ts | Tests for target path mapping + extraction safety behavior |
| tests/modules/install/installService.test.ts | End-to-end service tests with mocked registry fetch |
| tests/modules/install/installScope.test.ts | Tests for project vs global extraction scope resolution |
| tests/modules/install/installPersistenceRollback.test.ts | Tests rollback behavior when persistence fails post-extract |
| tests/modules/install/installPersistence.test.ts | Tests config/lock write behavior and lock ref resolution |
| tests/modules/cli/installCommand.test.ts | Subprocess CLI tests for install behavior + mock registry server |
| tests/modules/cli/cliGlobals.test.ts | Tests new global CLI flags (dryRun, noSave) wiring |
| tests/modules/cli/cliErrorHandling.test.ts | Tests updated exit code mapping + JSON error payloads |
| tests/fixtures/installFixtures.ts | Fixtures for manifests/metadata/catalog + ZIP builders |
| tests/bin/agents-repo.test.ts | Updates i alias test from placeholder to real command behavior |
| src/modules/registry/infrastructure/registrySourceUrl.ts | Adds version metadata path/url builder |
| src/modules/registry/infrastructure/registryRepository.ts | Adds loadPackageMetadata fetch + validation |
| src/modules/registry/infrastructure/registryMetadataValidation.ts | Adds metadata schema parsing + compatibility parsing helpers |
| src/modules/registry/domain/packageMetadata.ts | Introduces PackageMetadata domain model/types |
| src/modules/registry/domain/errors.ts | Adds metadata/target/version resolution error types |
| src/modules/install/infrastructure/zipSecurityScanner.ts | Implements ZIP entry validation rules and frontmatter checks |
| src/modules/install/infrastructure/targetExtractPaths.ts | Implements target-aware entry path mapping + containment checks |
| src/modules/install/infrastructure/sha256Verifier.ts | Adds SHA-256 integrity verification utility |
| src/modules/install/infrastructure/packageExtractor.ts | Implements scan + safe extraction + rollback on extract failure |
| src/modules/install/infrastructure/artifactDownloader.ts | Implements artifact download with registry fetch error wrapping |
| src/modules/install/domain/installResult.ts | Defines structured install result payload (including flags) |
| src/modules/install/domain/installErrors.ts | Defines install runtime + ZIP security error types |
| src/modules/install/application/validateInstallTarget.ts | Validates install target across index/metadata/manifest |
| src/modules/install/application/resolveLockRef.ts | Resolves concrete registry ref for lockfile writes |
| src/modules/install/application/resolveInstallVersion.ts | Selects version from manifest via semver rules |
| src/modules/install/application/installService.ts | Orchestrates end-to-end install pipeline (dry-run/no-save/global) |
| src/modules/install/application/installScope.ts | Computes global vs project extract roots + mutation policy |
| src/modules/install/application/installPersistence.ts | Writes merged config and lock entry for a completed install |
| src/modules/cli/presentation/runCli.ts | Adjusts CLI runner exit behavior after parse |
| src/modules/cli/presentation/installCommand.ts | Registers install/i command and prints results/warnings |
| src/modules/cli/presentation/createCliProgram.ts | Wires install command and new root flags into globals |
| src/modules/cli/presentation/cliErrorHandling.ts | Adds JSON error output and registry/install error code mapping |
| src/modules/cli/application/cliGlobals.ts | Extends CLI globals to include dryRun and noSave |
| README.md | Documents install availability and links to install docs |
| docs/development.md | Notes new runtime deps and install-script approval status |
| docs/commands/install.md | Adds user-facing install command documentation |
| docs/architecture/ddd-decision.md | Updates architecture decision doc to reflect install implementation |
| docs/ARCHITECTURE.md | Updates architecture overview to include install delivery details |
| package.json | Adds runtime deps (adm-zip, gray-matter) and types |
| package-lock.json | Locks new dependency graph for install implementation |
Use segment-based traversal checks in assertZipEntryPathSafe while keeping inline CodeQL guards in the extractor, and move successful process.exit(0) to the bin entrypoint so runCli can return normally. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ef0c4b70-ed40-4806-a8ac-760d553b58b5) |
Emit ERR_ZIP_DISALLOWED_PAYLOAD for constrained target ZIP paths and refuse extraction when an existing symlink appears along the destination path under the extract root. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b878fb4f-dd14-4b21-bd38-40d47536b97f) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 43 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/modules/install/infrastructure/packageExtractor.ts:69
mappedName.indexOf('..')has the same false-positive problem as the earlierentryNamecheck (rejects "foo..bar"). SinceresolveContainedExtractPathalready enforces containment andassertZipEntryPathSafeis segment-based, this guard should also be segment-based.
zip = new AdmZip(zipBytes)
} catch (error) {
throw new InstallZipSecurityError(
Rely on segment-based assertZipEntryPathSafe instead of substring indexOf checks, and document global/noSave in install --json output. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0c1d39f9-d867-46b9-b8a6-6ce84cc76101) |
Read and validate agents-lock.json before persisting config so lock failures do not leave agents.json updated without a successful install. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2da16c5e-009f-471e-b6f1-31b80a99ffda) |
Assign explicit snake_case codes to all RegistryError subclasses and use error.code instead of falling back to class names in CLI error handling. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_58d1d601-14aa-488c-95fc-91e18d89df0f) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 43 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/modules/cli/presentation/installCommand.ts:44
- The --json success payload omits install warnings even though warnings are part of InstallResult. If warnings are suppressed from stderr in json mode, include them in the success JSON so machine consumers can access them.
if (json) {
process.stdout.write(
`${JSON.stringify({
packageId: result.packageId,
version: result.version,
target: result.target,
extractRoot: result.extractRoot,
artifactUrl: result.artifactUrl,
saved: result.saved,
dryRun: result.dryRun,
global: result.global,
noSave: result.noSave,
})}\n`,
);
return;
Include warnings in --json success payloads, suppress stderr warning lines in JSON mode, document the field, and reset cliGlobals after error-handling tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_20a8e1fd-0e72-466f-bc2e-c703cfc49dcf) |
Restore indexOf('..') checks in the extract loop so CodeQL recognizes the
sanitizer, and assert resolved destinations stay within the extract root.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_1c1b1da9-20d6-4544-9b27-248bfada8b2f) |
Install target validation no longer defaults missing compatibility to all supported targets, matching cli-protocol step 7 requirements. Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_06dc09b6-e8ae-4c3a-a3a2-64d5fb4089f0) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 43 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/modules/install/infrastructure/packageExtractor.ts:100
- The
entryName.indexOf('..')guard rejects safe paths that merely contain the substring ".." inside a segment (e.g.foo..bar/file.txt). The actual traversal check inassertZipEntryPathSafeis segment-based, so this can cause false-positive install failures and also makes extractor stricter than the ZIP scanner.
if (entryName.indexOf('..') !== -1) {
throw new InstallRuntimeError(
'path_traversal',
`Refusing to extract unsafe archive entry: ${entryName}`,
)
src/modules/install/infrastructure/packageExtractor.ts:111
- The
mappedName.indexOf('..')guard has the same false-positive behavior as the archive-path check (it rejects any ".." substring, not just traversal segments). Since mapped paths are already checked byassertZipEntryPathSafe/resolveContainedExtractPath, this extra substring check can incorrectly block valid installs.
const mappedName = mapZipEntryToExtractPath(targetId, entryName)
if (mappedName.indexOf('..') !== -1) {
throw new InstallRuntimeError(
'path_traversal',
`Refusing to extract unsafe mapped path: ${mappedName}`,
)
}
|
🎉 This PR is included in version 1.4.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
agents-repo install <package>/ifor single-package MVP install (protocol steps 1–12)--dry-runand--no-saveflagsRelated Issues
Closes #8
Workflow Checklist
<prefix>/<issue-number>-<slug>.gh pr create --draftor UI draft option).## Related Issuesincludes a tracking reference (Closes #8).mainis for human maintainers only; agents and automation must not merge this PR or push directly tomain.Change Type
Scope
src/(includingsrc/bin/)specs/scripts/docs/.github/Validation Checklist
npm run env:checknpm run lint:allnpm run typechecknpm run testnpm run check:secretsValidation Evidence
Risk and Rollback
Made with Cursor