Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .cli-flags.toml
Original file line number Diff line number Diff line change
@@ -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."
45 changes: 45 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
135 changes: 135 additions & 0 deletions docs/migration-decision.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand Down
62 changes: 34 additions & 28 deletions readme.md
Original file line number Diff line number Diff line change
@@ -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"
```
>
> <sup><b><i> the 'init' command clones the git repo to: "$HOME/.trbl/repos/oresoftware/tarballs" </i></b></sup><br>
> <sup><b><i> and "oresoftware/tarballs" becomes the id for that repo </i></b></sup>
>

### 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
```
>
> <sup><b><i> the 'use' command sets a certain repo as the active one (globally/for all shells) </i></b></sup>
>

### 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"
```
>
> <sup><b><i> the 'add' command writes a file to the active repo to the path and attempts to push the file to the remote.</i></b></sup>
>

----------------------------------------------------
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/<org>/<repo>/master/x/y/z/foo.tgz?$(date +%s)"
```

$ npm install --loglevel=warn -g \
"https://raw.githubusercontent.com/<org>/<repo>/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/<org>/<repo>/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.
10 changes: 10 additions & 0 deletions scripts/block-legacy-publish.js
Original file line number Diff line number Diff line change
@@ -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;