Skip to content

feat: add install command for single package install#28

Merged
maiconfz merged 18 commits into
mainfrom
feat/8-install-single-package
Jul 23, 2026
Merged

feat: add install command for single package install#28
maiconfz merged 18 commits into
mainfrom
feat/8-install-single-package

Conversation

@maiconfz

Copy link
Copy Markdown
Member

Summary

  • Add agents-repo install <package> / i for single-package MVP install (protocol steps 1–12)
  • Port registry zip-scan, metadata fetch, target-aware extraction, and config/lock persistence
  • Wire global --dry-run and --no-save flags

Related Issues

Closes #8

Workflow Checklist

  • A tracking issue was opened before implementation.
  • The branch name follows <prefix>/<issue-number>-<slug>.
  • This pull request was created as a draft (gh pr create --draft or UI draft option).
  • This draft PR was opened before implementation commits (or it documents why not).
  • ## Related Issues includes a tracking reference (Closes #8).
  • Issue form was used or equivalent required fields were included (feature proposal / enhancement issue feat: add install command for single package install #8).
  • Merge to main is for human maintainers only; agents and automation must not merge this PR or push directly to main.
  • A human developer marked this PR ready for review after validation (not agents or automation).

Change Type

  • Spec change
  • Feature proposal
  • Bug or inconsistency
  • Task or chore

Scope

  • Root config files
  • src/ (including src/bin/)
  • specs/
  • scripts/
  • docs/
  • .github/

Validation Checklist

  • npm run env:check
  • npm run lint:all
  • npm run typecheck
  • npm run test
  • npm run check:secrets
  • Docs were updated where behavior or workflow changed.
  • This PR follows the repository contributor guidance.

Validation Evidence

  • Commands run: (pending implementation)
  • Key outputs: (pending implementation)

Risk and Rollback

  • Risk level: medium
  • Rollback plan: Revert PR; no data migration required.

Made with Cursor

maiconfz and others added 2 commits July 23, 2026 00:06
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>
Comment thread src/modules/install/infrastructure/packageExtractor.ts Fixed
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>
Comment thread src/modules/install/infrastructure/packageExtractor.ts Fixed
Validate archive entry names with explicit traversal checks and
resolveContainedExtractPath containment before any filesystem writes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread src/modules/install/infrastructure/packageExtractor.ts Fixed
@maiconfz
maiconfz marked this pull request as ready for review July 22, 2026 23:52
Copilot AI review requested due to automatic review settings July 22, 2026 23:52
@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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>
@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 install command + 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

Comment thread src/modules/install/infrastructure/targetExtractPaths.ts
Comment thread src/modules/cli/presentation/runCli.ts
Copilot AI review requested due to automatic review settings July 22, 2026 23:56
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>
@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated 2 comments.

Comment thread src/modules/install/infrastructure/zipSecurityScanner.ts
Comment thread src/modules/install/infrastructure/packageExtractor.ts
Copilot AI review requested due to automatic review settings July 23, 2026 00:01
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>
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 earlier entryName check (rejects "foo..bar"). Since resolveContainedExtractPath already enforces containment and assertZipEntryPathSafe is segment-based, this guard should also be segment-based.
    zip = new AdmZip(zipBytes)
  } catch (error) {
    throw new InstallZipSecurityError(

Comment thread src/modules/install/infrastructure/packageExtractor.ts
Comment thread docs/commands/install.md
Copilot AI review requested due to automatic review settings July 23, 2026 00:06
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>
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated 2 comments.

Comment thread tests/modules/install/installPersistence.test.ts
Comment thread src/modules/install/infrastructure/zipSecurityScanner.ts Outdated
Copilot AI review requested due to automatic review settings July 23, 2026 00:10
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>
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated 1 comment.

Comment thread src/modules/cli/presentation/cliErrorHandling.ts
Copilot AI review requested due to automatic review settings July 23, 2026 00:41
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>
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 23, 2026 00:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Comment thread src/modules/cli/presentation/installCommand.ts Outdated
Comment thread docs/commands/install.md
Comment thread tests/modules/cli/cliErrorHandling.test.ts Outdated
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>
Copilot AI review requested due to automatic review settings July 23, 2026 00:54
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated no new comments.

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>
Copilot AI review requested due to automatic review settings July 23, 2026 01:03
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 43 changed files in this pull request and generated 1 comment.

Comment thread src/modules/install/application/validateInstallTarget.ts
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>
Copilot AI review requested due to automatic review settings July 23, 2026 01:11
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 in assertZipEntryPathSafe is 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 by assertZipEntryPathSafe/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}`,
        )
      }

@maiconfz
maiconfz merged commit 7fc6bc0 into main Jul 23, 2026
6 checks passed
@maiconfz
maiconfz deleted the feat/8-install-single-package branch July 23, 2026 01:19
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.4.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add install command for single package install

3 participants