npm package: @markbennett/tang
Description: A simple CLI for Tangled.org. Inspired by GitHub CLI, and usable by humans and AI.
Author: Mark Bennett
Latest version: 0.0.1

# tang

A CLI for [Tangled.org](https://tangled.org) — manage issues and repository context from the terminal. Designed to be usable by both humans and AI agents.

## Installation

```bash
npm install -g @markbennett/tang
```

## Quick Start

```bash
# Authenticate with your Tangled PDS handle and an App Password
tang auth login

# From inside a git repo cloned from tangled.org:
tang issue list
tang issue create "Bug: something is broken" --body "Detailed description"
tang issue view 1
tang issue close 1

# SSH key management
tang ssh-key add ~/.ssh/id_ed25519.pub
```

## Commands

| Command | Description |
| :--- | :--- |
| `tang auth login` | Authenticate with your PDS handle and App Password |
| `tang auth logout` | Log out and clear stored session |
| `tang issue list` | List issues for the current repo |
| `tang issue create <title>` | Create a new issue |
| `tang issue view <n>` | View an issue |
| `tang issue close <n>` | Close an issue |
| `tang issue reopen <n>` | Reopen an issue |
| `tang ssh-key add <path>` | Upload a public SSH key to your account |
| `tang context` | Show resolved repo context (DID, handle, name) |
| `tang config` | View or set CLI configuration |

Most commands accept `--json [fields]` for machine-readable output, useful for scripting and LLM integrations.

---

# Architecture & Implementation Notes

**Goal:** Create a context-aware CLI for tangled.org that bridges the gap between the AT Protocol (XRPC) and standard Git.

**Philosophy:** Follow the **GitHub CLI (gh)** standard: act as a wrapper that creates a seamless experience where the API and local Git repo feel like one unified tool.

## Prior Art Analysis: GitHub CLI (gh) vs. Tangled CLI

| Feature        | GitHub CLI (gh) Approach                             | Tangled CLI Strategy                                                                                                       |
| :------------- | :--------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| **Context**    | Infers repo from .git/config remote URL.             | **Must-Have:** Parse .git/config to resolve did:plc:... from the remote URL.                                               |
| **Auth**       | Stores oauth token; acts as a git-credential-helper. | **Plan:** Store AT Proto session; inject auth headers into git operations if possible, or manage SSH keys via API.         |
| **Output**     | TTY \= Tables. Pipe \= Text. \--json \= Structured.  | **Plan:** Use is-interactive check. Default to "Human Mode". Force "Machine Mode" via flags.                               |
| **Filtering**  | \--json name,url (filters fields).                   | **Plan:** Support basic \--json flag first. Add field filtering (--json "cloneUrl,did") to save LLM context window tokens. |
| **Extensions** | Allows custom subcommands.                           | _Out of Scope for V1._                                                                                                     |

## High-Level Architecture (Refined)

The CLI acts as a "Context Engine" before it even hits the API.
`graph TD`
`User[User / LLM] -->|Command| CLI`

    `subgraph "Context Engine"`
        `Git[Local .git/config] -->|Read Remote| Resolver[Context Resolver]`
        `Resolver -->|Inferred DID| Payload`
    `end`

    `subgraph "Execution"`
        `Payload -->|XRPC Request| API[Tangled AppView]`
        `Payload -->|Git Command| Shell[Git Shell]`
    `end`

    `API --> Output`
    `Shell --> Output`

## Tech Stack (TypeScript)

| Component         | Library                 | Purpose                                                                                        |
| :---------------- | :---------------------- | :--------------------------------------------------------------------------------------------- |
| **Framework**     | **commander**           | CLI routing and command parsing (e.g., `tangled repo create`).                                 |
| **API Client**    | **@atproto/api**        | Official AT Protocol XRPC client, session management, and record operations.                   |
| **Lexicon Tools** | **@atproto/lexicon**    | Schema validation for custom Tangled.org lexicons (e.g., `sh.tangled.publicKey`).             |
| **Git Context**   | **git-url-parse**       | Parses remote URLs to extract the Tangled DID/NSID from `.git/config`.                        |
| **Git Ops**       | **simple-git**          | Wraps local git operations safely.                                                             |
| **Validation**    | **zod**                 | Input validation and schema generation for LLMs.                                               |
| **Interactivity** | **@inquirer/prompts**   | Modern, user-friendly prompts for interactive flows.                                           |
| **Formatting**    | **cli-table3**          | Pretty tables for "Human Mode" output (following gh CLI patterns).                             |
| **OS Keychain**   | **@napi-rs/keyring**    | Cross-platform secure storage for AT Protocol session tokens (macOS, Windows, Linux).          |
| **TypeScript**    | **tsx**                 | Fast TypeScript execution for development and testing.                                         |

## Agent Integration (The "LLM Friendly" Layer)

To make this tool accessible to Claude Code/Gemini, we adopt gh's best patterns:

### Rule 1: Context is King

LLMs often hallucinate repo IDs.

- **Design:** If the user/LLM runs tangled issue list inside a folder, **do not** ask for the repo DID. Infer it.
- **Fallback:** Only error if no git remote is found.

### Rule 2: Precision JSON (--json \<fields\>)

LLMs have token limits. Returning a 50KB repo object is wasteful.

- **Feature:** tangled repo view \--json name,cloneUrl,description
- **Implementation:** Use lodash/pick to filter the API response before printing to stdout.

### Rule 3: Fail Fast, Fail Loud

LLMs can't read error messages buried in HTML or long stack traces. Provide a `--no-input` flag that forces the CLI to error if it can't resolve context or if required flags are missing.

### Rule 4: Flexible Input for Issue Bodies

Following `gh`'s pattern, `tangled issue create` will support various ways to provide the issue body, making it LLM-friendly and flexible for scripting. It will accept:

- `--body "Text"` or `-b "Text"` for a direct string.
- `--body-file ./file.md` or `-F ./file.md` to read from a file.
- `--body-file -` or `-F -` to read from standard input (stdin).

### Summary of Improvements

- **Context Inference:** This is the "killer feature" of gh that we are copying. It makes the tool usable for humans and safer for LLMs (less typing = fewer errors).
- **Filtered JSON:** Saves tokens for LLM context windows.
- **Git Config Integration:** Treats the local .git folder as a database of configuration, reducing the need for environment variables or complex flags.
- **Flexible Issue Body Input:** Improves usability for both humans and LLMs by allowing diverse input methods for issue descriptions.

## Examples Tangled CLI Usage

```bash
tangled auth login (opens a browser for auth)
tangled repo create my-new-repo
cd my-new-repo
tangled issue create "Bug: Something is broken" --body "Detailed description of the bug here."
echo "Another bug description from stdin." | tangled issue create "Bug: From stdin" --body-file -
tangled issue list --json "id,title"
tangled pr create --base main --head my-feature --title "Add new feature" --body-file ./pr_description.md
tangled pr view 123
tangled pr comment 123 --body "Looks good, small change needed."
```

## Basic Commands

Basic commands include auth, key management, repo creation, issue management, and pull request management.

`tangled auth login`

- Logs in the user, ideally through a web browser flow for security.
  `tangled auth logout`
- Logs out the user, clearing the session.
  `tangled ssh-key add <public-key-path>`
- Uploads the provided public SSH key to the user's tangled.org account via the API.
  `tangled ssh-key verify`
- Verifies that the user's SSH key is correctly set up and can authenticate with tangled.org. Returns the associated DID and handle if successful.
  `tangled repo create <repo-name>`
- Creates a new repository under the user's account.
  `tangled repo view [--json <fields>]`
- Displays details about the current repository. If `--json` is provided, outputs only the specified fields in JSON format.
  `tangled issue create "<title>" [--body "<body>" | --body-file <file> | -F -]`
- Creates a new issue in the current repository with the given title and optional body, which can be provided via flag, file, or stdin.
  `tangled pr create --base <base-branch> --head <head-branch> --title <title> [--body <body> | --body-file <file> | -F -]`
- Creates a new pull request in the current repository from a head branch to a base branch.
  `tangled pr list [--json <fields>]`
- Lists pull requests for the current repository.
  `tangled pr view <id> [--json <fields>]`
- Displays detailed information about a specific pull request, including comments.
  `tangled pr comment <id> [--body <body> | --body-file <file> | -F -]`
- Adds a comment to a pull request.
  `tangled pr review <id> --comment <comment> [--approve | --request-changes]`
- Submits a review for a pull request, with optional approval or request for changes.

## Design Decisions & Outstanding Issues

This section documents key design decisions and tracks outstanding architectural questions.

### (Resolved) SSH Key Management (`gh` Compatibility)

- **Original Question:** How does `gh` manage SSH keys, and can we follow that pattern?
- **Resolution:** Analysis shows that `gh` does _not_ manage private keys. It facilitates uploading the user's _public_ key to their GitHub account. The local SSH agent handles the private key.
- **Our Approach:** The `tangled ssh-key add` command follows this exact pattern. It provides a user-friendly way to upload a public key to `tangled.org`. This resolves the core of this issue, as it is compatible with external key managers like 1Password's SSH agent.

### (Decided) Secure Session Storage

- **Original Question:** How should we securely store the AT Proto session token?
- **Resolution:** Storing sensitive tokens in plaintext files is not secure.
- **Our Approach:** The CLI will use the operating system's native keychain for secure storage (e.g., macOS Keychain, Windows Credential Manager, or Secret Service on Linux). A library like `keytar` will be used to abstract the platform differences.

### (Decided) Configuration Resolution Order

- **Original Question:** How should settings be resolved from different sources?
- **Resolution:** A clear precedence order is necessary.
- **Our Approach:** The CLI will resolve settings in the following order of precedence (highest first):
  1.  Command-line flags (e.g., `--repo-did ...`)
  2.  Environment variables (e.g., `TANGLED_REPO_DID=...`)
  3.  Project-specific config file (e.g., `.tangled/config.yml` in the current directory)
  4.  Global user config file (e.g., `~/.config/tangled/config.yml`)

### (Decided for V1) Authentication Flow: App Passwords (PDS)

- **Original Question:** Can we allow auth through a web browser?
- **Resolution:** For the initial version, the CLI will use **App Passwords** for authentication. This is the standard and simplest method for third-party AT Protocol clients and aligns with existing practices.
- **`tangled auth login` Flow:** When running `tangled auth login`, the CLI will prompt the user for their **PDS handle** (e.g., `@mark.bsky.social`) and an **App Password**.
- **Generating an App Password:** Users typically generate App Passwords from their PDS's settings (e.g., in the official Bluesky app under "Settings -> App Passwords", or on their self-hosted PDS web interface). The CLI **does not** generate app passwords.
- **Session Management:** The session established is with the user's PDS, and this authenticated session is then used to interact with `tangled.org`'s App View/Service.
- **OAuth Support:** Implementing a web-based OAuth flow (similar to `gh`'s approach) is more complex and not a standard part of the AT Protocol client authentication flow. This approach is deferred for future consideration.

## Future Expansion Opportunities

The analysis of the `tangled.org` API revealed a rich set of features that are not yet part of the initial CLI plan but represent significant opportunities for future expansion. These include:

- **CI/CD Pipelines:** Commands to view pipeline status and manage CI/CD jobs.
- **Repository Secrets:** A dedicated command set for managing CI/CD secrets within a repository (`tangled repo secret ...`).
- **Advanced Git Operations:** Commands to interact with the commit log, diffs, branches, and tags directly via the API, augmenting local `git` commands.
- **Social & Feed Interactions:** Commands for starring repositories, reacting to feed items, and managing the user's social graph (following/unfollowing).
- **Label Management:** Commands to create, apply, and remove labels from issues and pull requests.
- **Collaboration:** Commands to manage repository collaborators.
- **Fork Management:** Commands for forking repositories and managing the sync status of forks.
- **Reactions**: Commands to add and remove reactions on issues, pull requests, and comments.
- **Commenting on Issues**: Commands to add comments to issues.

## Task Management

Tasks are tracked in the [Tangled issue tracker](https://tangled.org/markbennett.ca/tangled-cli/issues). Use `tangled issue list` or `tangled issue view <n>` to browse tasks.

## Development

### Prerequisites

- Node.js 22.0.0 or higher (latest LTS)
- npm (comes with Node.js)

### Installation

Clone the repository and install dependencies:

```bash
npm install
```

### Available Scripts

- `npm run dev` - Run the CLI in development mode (with hot reload via tsx)
- `npm run build` - Build TypeScript to JavaScript (output to `dist/`)
- `npm test` - Run tests once
- `npm run test:watch` - Run tests in watch mode
- `npm run test:coverage` - Run tests with coverage report
- `npm run lint` - Check code with Biome linter
- `npm run lint:fix` - Auto-fix linting issues
- `npm run format` - Format code with Biome
- `npm run typecheck` - Type check without building

### Running Locally

When running commands against the development version, use `npm run dev` with the `--` separator to pass arguments to the CLI:

```bash
# Run the CLI in development mode
npm run dev -- --version
npm run dev -- --help
npm run dev -- issue list
npm run dev -- issue create "My issue title" --body "Issue body"

# Build and run the production version
npm run build
node dist/index.js --version

# Install globally for local testing
npm link
tang --version
tang --help
npm unlink -g @markbennett/tang  # Unlink when done
```

### Project Structure

```
tangled-cli/
├── src/
│   ├── index.ts          # Main CLI entry point
│   ├── commands/         # Command implementations
│   ├── lib/              # Core business logic
│   └── utils/            # Helper functions
├── tests/                # Test files
├── dist/                 # Build output (gitignored)
└── package.json          # Package configuration
```

### Coding Guidelines

**IMPORTANT: These guidelines must be followed for all code contributions.**

#### Validation Functions Location

**ALL validation logic belongs in `src/utils/validation.ts`**

- Use Zod schemas for all input validation
- Boolean validation helpers (e.g., `isValidHandle()`, `isValidTangledDid()`) go in `validation.ts`
- Never define validation functions in other files - import from `validation.ts`
- Validation functions should return `true/false` or use Zod's `safeParse()` pattern

Example:
```typescript
// ✅ CORRECT: validation.ts
export function isValidHandle(handle: string): boolean {
  return handleSchema.safeParse(handle).success;
}

// ❌ WRONG: Don't define validators in other files
// git.ts should import isValidHandle, not define it
```

#### Test Coverage Requirements

**ALL code must have comprehensive test coverage**

- Every new feature requires tests in the corresponding `tests/` directory
- Commands must have test files (e.g., `src/commands/foo.ts` → `tests/commands/foo.test.ts`)
- Utilities must have test files (e.g., `src/utils/bar.ts` → `tests/utils/bar.test.ts`)
- Tests should cover:
  - Success cases (happy path)
  - Error cases (validation failures, network errors, etc.)
  - Edge cases (empty input, boundary values, etc.)
- Aim for high test coverage - tests are not optional

Example test structure:
```typescript
describe('MyFeature', () => {
  describe('successfulOperation', () => {
    it('should handle valid input', async () => { /* ... */ });
    it('should handle edge case', async () => { /* ... */ });
  });

  describe('errorHandling', () => {
    it('should reject invalid input', async () => { /* ... */ });
    it('should handle network errors', async () => { /* ... */ });
  });
});
```

#### Pull Request Checklist

Before submitting code, verify:
- [ ] All validation functions are in `validation.ts`
- [ ] Comprehensive tests are written and passing
- [ ] TypeScript compilation passes (`npm run typecheck`)
- [ ] Linting passes (`npm run lint`)
- [ ] All tests pass (`npm test`)

### Technology Stack

- **TypeScript 5.7.2** - Latest stable with strict mode enabled
- **Node.js 22+** - Latest LTS target
- **ES2023** - Latest stable ECMAScript target
- **Biome** - Fast linter and formatter (replaces ESLint + Prettier)
- **Vitest** - Fast unit test framework
- **Commander.js** - CLI framework
- **tsx** - Fast TypeScript execution for development