diff --git a/.cli-flags.toml b/.cli-flags.toml new file mode 100644 index 0000000..fc214a2 --- /dev/null +++ b/.cli-flags.toml @@ -0,0 +1,86 @@ +[help] +url = "https://github.com/ORESoftware/tarballs-cli/issues/1" +columns = ["options", "env", "type", "default", "description"] + +[parse] +command_env = "TRBL_COMMAND" +positionals_env = "TRBL_POSITIONALS" +unknown_options_env = "TRBL_UNKNOWN_OPTIONS" +errors_env = "TRBL_PARSE_ERRORS" +allow_unknown = false + +# This manifest is the intended migration contract. The legacy shell dispatcher +# does not yet consume it; issue #1 remains open until flags-2-env is the single +# argv parser and the command-specific positional schemas are enforced. + +[flags.help] +env = "TRBL_HELP" +aliases = ["help"] +short = "h" +type = "bool" +default = false +help = "Print generated help and exit without performing repository effects." + +[flags.version] +env = "TRBL_VERSION_REQUESTED" +aliases = ["version"] +type = "bool" +default = false +help = "Print the CLI version and exit." + +[flags.dry_run] +env = "TRBL_DRY_RUN" +aliases = ["dry-run", "plan"] +type = "bool" +default = true +help = "Plan the command without changing files, Git repositories, or remotes." + +[flags.json] +env = "TRBL_JSON" +aliases = ["json"] +type = "bool" +default = false +help = "Emit bounded machine-readable diagnostics without credentials or file contents." + +[flags.repository_root] +env = "TRBL_REPOSITORY_ROOT" +aliases = ["repository-root", "repo-root"] +type = "string" +help = "Explicit local repository-store root; it must pass ownership, symlink, and traversal checks." + +[flags.lock_timeout_ms] +env = "TRBL_LOCK_TIMEOUT_MS" +aliases = ["lock-timeout-ms"] +type = "integer" +default = 10000 +help = "Maximum time to wait for a local repository lock before failing closed." + +[commands.init] +help = "Register and clone one approved remote into the managed local repository store." + +[commands.use] +help = "Select an already registered repository without mutating a shell profile." + +[commands.add] +help = "Plan or copy one local artifact into an approved relative destination." + +[commands.get] +help = "Resolve one registered artifact without evaluating remote content." + +[commands.view] +help = "Show bounded metadata for the active repository and tracked artifacts." + +[commands.fetch] +help = "Fetch immutable remote metadata for one registered repository." + +[commands.push] +help = "Push reviewed local commits to an explicitly configured remote without force." + +[commands.repair] +help = "Validate and repair local metadata without deleting repositories or rewriting history." + +[commands.remove] +help = "Remove one registration after a plan and confirmation; remote state is unchanged." + +[commands.remove-all] +help = "Plan removal of all local registrations; remote repositories and refs are unchanged." diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1638371 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,45 @@ +# Repository agent instructions + +Read and obey the current organization baseline and [`ORESoftware/my-ai/AGENTS.md`](https://github.com/ORESoftware/my-ai/blob/main/AGENTS.md) before changing this repository. Where rules differ, follow the stricter rule. + +## Current repository state + +`tarballs-cli` / `tarzan` is a legacy shell-dispatched CLI under migration review in [issue #1](https://github.com/ORESoftware/tarballs-cli/issues/1). Do not describe it as modernized, Zed-published, reproducible, or production-ready until that issue's completion evidence exists. + +## Release and installation freeze + +- Do not publish the npm package, create a Zed release, add another package registry, or restore install-time side effects while issue #1 is open. +- Installation must not write to a user's home directory, mutate shell profiles, clone repositories, contact remotes, or read credentials. +- Do not bypass the `private` package guard or the prepublish blocker added during the migration gate. +- A release-enabling change requires an explicit migration/retirement decision, consumer evidence, green packaging tests, reviewed immutable dependency receipts, and a separate pull request. + +## Command and input boundary + +The intended command vocabulary is defined in root `.cli-flags.toml`. New runtime work must use the reviewed `flags-2-env` integration as the only argv parsing/coercion boundary. Reject unknown commands, flags, and extra positional arguments. Treat filenames, repository identifiers, refs, remote URLs, archive paths, and environment values as untrusted input. + +Before any command performs an effect, validate: + +- path traversal, symlink escape, special-file, ownership, and destination constraints; +- remote URL scheme/host/userinfo and credential absence; +- command and ref syntax without shell re-parsing; +- concurrency, lock, interruption, and idempotency behavior; +- dry-run output and diagnostic redaction. + +Never use `eval`, concatenate untrusted shell commands, place credentials in URLs or process arguments, or log SSH keys, tokens, credential-helper output, repository contents, or raw provider errors. + +## Git and repository effects + +Keep remote writes behind an explicit effect adapter and an opt-in command. Default tests must use temporary local repositories and must not contact GitHub or another registry. Do not force-push, rewrite history, delete remote refs, or overwrite non-empty destinations. Preserve concurrent work and resolve conflicts semantically; never choose one side wholesale. + +## Migration sequence + +1. Inventory consumers and commands actually in use. +2. Add characterization and adversarial tests around the current public behavior. +3. Replace the shell dispatcher incrementally with a maintained runtime. +4. Integrate flags-2-env once at process entry. +5. Add immutable Zed metadata and dependency receipts without publishing. +6. Prove non-interactive packaging/install behavior in an isolated home directory. +7. Obtain review of the retirement or migration decision. +8. Enable release only in a dedicated, exact-head, least-privilege pull request. + +Do not hand-edit generated artifacts or claim checks that were not run. Pull requests must link issue #1 and the corresponding Linear work, describe rollback and consumer impact, and remain draft while release, migration, or destructive behavior is unresolved. diff --git a/docs/migration-decision.md b/docs/migration-decision.md new file mode 100644 index 0000000..0a83411 --- /dev/null +++ b/docs/migration-decision.md @@ -0,0 +1,135 @@ +# Tarzan / tarballs-cli migration decision + +Tracking: [ORESoftware/tarballs-cli#1](https://github.com/ORESoftware/tarballs-cli/issues/1) · Linear: `DEN-3944` + +## Current disposition + +**Freeze the legacy package while preparing a migration.** Do not publish, archive, delete, or silently revive the current implementation. + +This repository still contains useful command intent, but the present shell dispatcher, global active-repository state, direct Git effects, install-time home-directory write, old CI assumptions, and missing parser/security tests are not an acceptable release boundary. The freeze preserves source and consumer compatibility evidence while preventing another npm publication from being mistaken for a supported release. + +The branch implementing this decision: + +- marks the package private; +- removes the npm `postinstall` entry that writes under `$HOME`; +- adds a fail-closed `prepublishOnly` blocker; +- records repository-local agent and credential rules; +- declares the intended command vocabulary in `.cli-flags.toml` without claiming the legacy shell dispatcher consumes it. + +## Why migration is preferred over immediate archive + +The source contains ten named workflows—`init`, `use`, `add`, `get`, `view`, `fetch`, `push`, `repair`, `remove`, and `remove-all`—and historical documentation describes an artifact-to-Git repository workflow. Before retirement, the owner must know whether any scripts, CI images, globally installed packages, aliases, or repositories still rely on those names and storage conventions. + +An immediate archive could strand consumers or erase useful behavior before it is mapped to `zed-pkg`, an artifact-storage client, or another maintained home. Conversely, the presence of source is not evidence that every command should survive. The migration must inventory actual use and preserve only justified behavior. + +## Consumer inventory receipt + +The inventory must be machine-readable and bind its result to immutable source evidence. At minimum it records: + +- audit start/end time and actor/workflow identity; +- exact GitHub organizations, repositories, default-branch SHAs, package registries, images, and local configuration roots searched; +- every reference to `tarballs-cli`, package name, `tarzan`, `trbl`, `$HOME/.trbl`, and `$HOME/.oresoftware/bash/tarzan.sh`; +- whether the reference installs, invokes, documents, vendors, or merely mentions the CLI; +- inaccessible repositories, pagination limits, excluded private surfaces, and stale results; +- proposed replacement and migration owner for each active consumer; +- content digest of the normalized receipt. + +A partial search is reported as partial. Absence cannot be inferred from inaccessible organizations, local machines, registries, or images. + +## Runtime migration contract + +The maintained runtime must use `flags-2-env/flags-2-env@0.3.0` as the sole argv parsing and coercion boundary. The root `.cli-flags.toml` is the reviewed command vocabulary, but command-specific positional schemas and compatibility aliases still require implementation and tests. + +Architecture: + +```text +argv + environment + -> flags-2-env parse/validation + -> immutable command value + -> pure validation and effect plan + -> filesystem/Git adapter + -> bounded redacted receipt +``` + +The parser rejects unknown flags, invalid coercions, unsupported commands, and excess positionals. Defaults do not override explicit environment values. The domain layer never rereads process argv/environment. + +## Effect and security requirements + +Before any file or Git effect: + +- canonicalize an explicitly allowed base without following an attacker-controlled final symlink; +- reject traversal, absolute destination paths where relative paths are required, special files, symlink escape, and ownership/mode violations; +- validate repository identifiers and refs with closed grammars; +- permit only reviewed remote URL schemes/hosts and reject userinfo, fragments, encoded credentials, ambiguous scp-like forms, link-local/metadata targets, and control characters; +- construct subprocess argv arrays without shell interpolation or `eval`; +- acquire a per-repository lock with bounded timeout and stale-lock recovery evidence; +- stage effects in a run-scoped directory and promote atomically where possible; +- make interruption/retry behavior explicit and idempotent; +- prohibit force pushes, destructive history rewrites, and remote deletion; +- redact tokens, SSH material, credential-helper output, home paths, file contents, and raw provider errors. + +`dry-run` is the default in the declared contract. An apply path requires a deliberate opt-in plus an immutable plan or confirmation mechanism. Tests use temporary local repositories and fake remotes; no default test contacts GitHub. + +## Install and package requirements + +A future installation must: + +- perform no network, repository, shell-profile, home-directory, or credential effect during package-manager lifecycle hooks; +- install only reviewed package files; +- be reproducible from a clean checkout and immutable dependency receipt; +- run smoke tests with an isolated temporary `HOME`; +- verify `npm pack --dry-run` / equivalent output and prohibit secrets, local state, test credentials, caches, and legacy CI artifacts from the package; +- publish only from an exact reviewed commit through a least-privilege workflow; +- produce an immutable npm/Zed release receipt and rollback/deprecation plan. + +The old `assets/postinstall.sh` source is retained only as migration evidence; it is no longer invoked by npm. Removing it from history is neither necessary nor authorized. + +## Zed ownership decision + +Before adding `.zpkg.toml`, decide which package owns the behavior: + +1. a command group inside `zed-pkg` when this is fundamentally package/artifact resolution; +2. a dedicated artifact-storage client when it manages generic immutable artifacts; +3. a reduced `tarballs-cli` successor when independent command/product ownership remains justified. + +Do not duplicate upload/resolution policy across homes. The chosen owner must expose an immutable source revision, dependency lock/receipt, compatibility mapping, and consumer migration plan. Zed metadata may be staged only after that decision; metadata alone cannot close #1. + +## Test gates + +The migration PR cannot become ready until tests cover at least: + +- every command and help/error path; +- missing, duplicate, unknown, and excess arguments; +- traversal, absolute paths, symlink races/escape, hard links, special files, and permission failures; +- remote URL credentials, fragments, control characters, unsupported schemes, host allow/deny policy, and command injection; +- interrupted clone/fetch/copy/commit/push sequences; +- concurrent writers, lock timeout, stale lock, and idempotent retry; +- dirty repositories, moved heads, non-fast-forward updates, merge conflicts, and rejected force behavior; +- redaction of environment, URLs, errors, paths, and subprocess diagnostics; +- isolated install with no writes outside the package target; +- deterministic dry-run and receipt output; +- behavior comparison for every command retained from the legacy CLI. + +## Release, retirement, and closure gates + +Issue #1 remains open. It may close only after one of these receipts is reviewed: + +### Migration + +- complete consumer inventory; +- approved ownership decision; +- maintained runtime and flags-2-env integration; +- green exact-head tests and reproducible packaging; +- immutable Zed/npm release receipt; +- consumer migration evidence and rollback/deprecation plan. + +### Retirement + +- complete consumer inventory; +- named replacement or explicit no-replacement rationale; +- package deprecation and publishing disablement evidence; +- documentation/redirect plan; +- owner-approved archive timing; +- salvage record for unique tests, contracts, and command behavior. + +Until then, the safe repository state is preserved source plus blocked release/install side effects—not a new production release. diff --git a/package.json b/package.json index 146208c..947516d 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "tarballs-cli", "version": "0.0.103", - "description": "Semver-oriented TypeScript library skeleton.", + "private": true, + "description": "Legacy tarzan/trbl CLI frozen pending the migration decision in issue #1.", "main": "dist/index.js", "bin": { "tarzan": "cli/trbl.sh", @@ -11,7 +12,7 @@ "typings": "dist/index.d.ts", "scripts": { "test": "suman test", - "postinstall": "./assets/postinstall.sh" + "prepublishOnly": "node ./scripts/block-legacy-publish.js" }, "repository": { "type": "git", diff --git a/readme.md b/readme.md index 1d6991b..1e6e0a7 100755 --- a/readme.md +++ b/readme.md @@ -1,51 +1,57 @@ +# Tarzan / tarballs-cli -#### Tarzan / tarballs-cli +> **Legacy package — release and installation frozen.** The current shell-dispatched CLI is retained for consumer and behavior discovery, but it is not approved for a new npm or Zed release. npm publication is blocked and the former install-time home-directory write has been removed from `package.json`. See [issue #1](https://github.com/ORESoftware/tarballs-cli/issues/1) and [`docs/migration-decision.md`](docs/migration-decision.md). Do not follow the historical global-install examples below for new systems. ---------------------------------------------------------- +## Historical behavior retained for migration review -### Clone a git repo, to which you will push files (tarballs, etc). +### Clone a Git repository to which files were pushed ```bash -$ tarzan init oresoftware/tarballs "git@github.com:ORESoftware/tarballs.git" +tarzan init oresoftware/tarballs "git@github.com:ORESoftware/tarballs.git" ``` -> -> the 'init' command clones the git repo to: "$HOME/.trbl/repos/oresoftware/tarballs"
-> and "oresoftware/tarballs" becomes the id for that repo -> -### Use a repo to push files to +Historically, `init` cloned the repository under `$HOME/.trbl/repos/oresoftware/tarballs` and registered `oresoftware/tarballs` as its ID. The replacement must validate ownership, paths, symlinks, remotes, credentials, concurrency, and interruption before preserving this behavior. + +### Select a repository ```bash -$ tarzan use oresoftware/tarballs +tarzan use oresoftware/tarballs ``` -> -> the 'use' command sets a certain repo as the active one (globally/for all shells) -> -### Add a local file and push it to the remote repo +Historically, `use` selected one repository globally for all shells. The migration must replace hidden global state with an explicit, validated configuration and must not mutate shell profiles during installation. + +### Add and push an artifact ```bash -$ tarzan add foo.tgz "x/y/z/foo.tgz" +tarzan add foo.tgz "x/y/z/foo.tgz" ``` -> -> the 'add' command writes a file to the active repo to the path and attempts to push the file to the remote. -> ----------------------------------------------------- +Historically, `add` copied a file into the active repository and attempted to push it. Any maintained version must default to dry-run, validate paths and remotes, serialize concurrent writers, avoid shell interpolation, prohibit force pushes, and emit bounded redacted receipts. -### Install a tarball from Github, with NPM, etc +## Historical raw-GitHub installation example + +The previous documentation installed tarballs from mutable branch URLs with a cache-busting query string. This is **not** an approved modern supply-chain workflow: ```bash +npm install --loglevel=warn -g \ + "https://raw.githubusercontent.com///master/x/y/z/foo.tgz?$(date +%s)" +``` -$ npm install --loglevel=warn -g \ - "https://raw.githubusercontent.com///master/x/y/z/foo.tgz?$(date +%s)" +Do not use this pattern for new builds or Docker images. The migration decision must select an owning package system and use immutable content/revision digests, authenticated provenance, reproducible packaging, and reviewed release receipts. -``` +## Intended command contract -### Using a Dockerfile / building a Docker image: +Root [`.cli-flags.toml`](.cli-flags.toml) declares the ten commands that must be evaluated during migration: -```bash -RUN npm install --loglevel=warn -g \ - "https://raw.githubusercontent.com///master/x/y/z/foo.tgz?$(date +%s)" -``` +- `init` +- `use` +- `add` +- `get` +- `view` +- `fetch` +- `push` +- `repair` +- `remove` +- `remove-all` +The legacy shell dispatcher does not yet consume the manifest. Issue #1 remains open until flags-2-env is the single parser, retained commands have characterization and adversarial tests, and either a migration or retirement receipt is approved. diff --git a/scripts/block-legacy-publish.js b/scripts/block-legacy-publish.js new file mode 100644 index 0000000..dc115fd --- /dev/null +++ b/scripts/block-legacy-publish.js @@ -0,0 +1,10 @@ +'use strict'; + +const message = [ + 'tarballs-cli publication is blocked while the legacy CLI is under migration review.', + 'See https://github.com/ORESoftware/tarballs-cli/issues/1 for the consumer, parser, packaging, and release gates.', + 'Do not bypass this guard or restore install-time side effects in the same change.', +].join(' '); + +process.stderr.write(`${message}\n`); +process.exitCode = 1;