WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
at main 362 lines 18 kB view raw view rendered
1# tang 2 3A 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. 4 5## Installation 6 7```bash 8npm install -g @markbennett/tang 9``` 10 11## Quick Start 12 13```bash 14# Authenticate with your Tangled PDS handle and an App Password 15tang auth login 16 17# From inside a git repo cloned from tangled.org: 18tang issue list 19tang issue create "Bug: something is broken" --body "Detailed description" 20tang issue view 1 21tang issue close 1 22 23# SSH key management 24tang ssh-key add ~/.ssh/id_ed25519.pub 25``` 26 27## Commands 28 29| Command | Description | 30| :--- | :--- | 31| `tang auth login` | Authenticate with your PDS handle and App Password | 32| `tang auth logout` | Log out and clear stored session | 33| `tang issue list` | List issues for the current repo | 34| `tang issue create <title>` | Create a new issue | 35| `tang issue view <n>` | View an issue | 36| `tang issue close <n>` | Close an issue | 37| `tang issue reopen <n>` | Reopen an issue | 38| `tang ssh-key add <path>` | Upload a public SSH key to your account | 39| `tang context` | Show resolved repo context (DID, handle, name) | 40| `tang config` | View or set CLI configuration | 41 42Most commands accept `--json [fields]` for machine-readable output, useful for scripting and LLM integrations. 43 44--- 45 46# Architecture & Implementation Notes 47 48**Goal:** Create a context-aware CLI for tangled.org that bridges the gap between the AT Protocol (XRPC) and standard Git. 49 50**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. 51 52## Prior Art Analysis: GitHub CLI (gh) vs. Tangled CLI 53 54| Feature | GitHub CLI (gh) Approach | Tangled CLI Strategy | 55| :------------- | :--------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | 56| **Context** | Infers repo from .git/config remote URL. | **Must-Have:** Parse .git/config to resolve did:plc:... from the remote URL. | 57| **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. | 58| **Output** | TTY \= Tables. Pipe \= Text. \--json \= Structured. | **Plan:** Use is-interactive check. Default to "Human Mode". Force "Machine Mode" via flags. | 59| **Filtering** | \--json name,url (filters fields). | **Plan:** Support basic \--json flag first. Add field filtering (--json "cloneUrl,did") to save LLM context window tokens. | 60| **Extensions** | Allows custom subcommands. | _Out of Scope for V1._ | 61 62## High-Level Architecture (Refined) 63 64The CLI acts as a "Context Engine" before it even hits the API. 65`graph TD` 66`User[User / LLM] -->|Command| CLI` 67 68 `subgraph "Context Engine"` 69 `Git[Local .git/config] -->|Read Remote| Resolver[Context Resolver]` 70 `Resolver -->|Inferred DID| Payload` 71 `end` 72 73 `subgraph "Execution"` 74 `Payload -->|XRPC Request| API[Tangled AppView]` 75 `Payload -->|Git Command| Shell[Git Shell]` 76 `end` 77 78 `API --> Output` 79 `Shell --> Output` 80 81## Tech Stack (TypeScript) 82 83| Component | Library | Purpose | 84| :---------------- | :---------------------- | :--------------------------------------------------------------------------------------------- | 85| **Framework** | **commander** | CLI routing and command parsing (e.g., `tangled repo create`). | 86| **API Client** | **@atproto/api** | Official AT Protocol XRPC client, session management, and record operations. | 87| **Lexicon Tools** | **@atproto/lexicon** | Schema validation for custom Tangled.org lexicons (e.g., `sh.tangled.publicKey`). | 88| **Git Context** | **git-url-parse** | Parses remote URLs to extract the Tangled DID/NSID from `.git/config`. | 89| **Git Ops** | **simple-git** | Wraps local git operations safely. | 90| **Validation** | **zod** | Input validation and schema generation for LLMs. | 91| **Interactivity** | **@inquirer/prompts** | Modern, user-friendly prompts for interactive flows. | 92| **Formatting** | **cli-table3** | Pretty tables for "Human Mode" output (following gh CLI patterns). | 93| **OS Keychain** | **@napi-rs/keyring** | Cross-platform secure storage for AT Protocol session tokens (macOS, Windows, Linux). | 94| **TypeScript** | **tsx** | Fast TypeScript execution for development and testing. | 95 96## Agent Integration (The "LLM Friendly" Layer) 97 98To make this tool accessible to Claude Code/Gemini, we adopt gh's best patterns: 99 100### Rule 1: Context is King 101 102LLMs often hallucinate repo IDs. 103 104- **Design:** If the user/LLM runs tangled issue list inside a folder, **do not** ask for the repo DID. Infer it. 105- **Fallback:** Only error if no git remote is found. 106 107### Rule 2: Precision JSON (--json \<fields\>) 108 109LLMs have token limits. Returning a 50KB repo object is wasteful. 110 111- **Feature:** tangled repo view \--json name,cloneUrl,description 112- **Implementation:** Use lodash/pick to filter the API response before printing to stdout. 113 114### Rule 3: Fail Fast, Fail Loud 115 116LLMs 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. 117 118### Rule 4: Flexible Input for Issue Bodies 119 120Following `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: 121 122- `--body "Text"` or `-b "Text"` for a direct string. 123- `--body-file ./file.md` or `-F ./file.md` to read from a file. 124- `--body-file -` or `-F -` to read from standard input (stdin). 125 126### Summary of Improvements 127 128- **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). 129- **Filtered JSON:** Saves tokens for LLM context windows. 130- **Git Config Integration:** Treats the local .git folder as a database of configuration, reducing the need for environment variables or complex flags. 131- **Flexible Issue Body Input:** Improves usability for both humans and LLMs by allowing diverse input methods for issue descriptions. 132 133## Examples Tangled CLI Usage 134 135```bash 136tangled auth login (opens a browser for auth) 137tangled repo create my-new-repo 138cd my-new-repo 139tangled issue create "Bug: Something is broken" --body "Detailed description of the bug here." 140echo "Another bug description from stdin." | tangled issue create "Bug: From stdin" --body-file - 141tangled issue list --json "id,title" 142tangled pr create --base main --head my-feature --title "Add new feature" --body-file ./pr_description.md 143tangled pr view 123 144tangled pr comment 123 --body "Looks good, small change needed." 145``` 146 147## Basic Commands 148 149Basic commands include auth, key management, repo creation, issue management, and pull request management. 150 151`tangled auth login` 152 153- Logs in the user, ideally through a web browser flow for security. 154 `tangled auth logout` 155- Logs out the user, clearing the session. 156 `tangled ssh-key add <public-key-path>` 157- Uploads the provided public SSH key to the user's tangled.org account via the API. 158 `tangled ssh-key verify` 159- 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. 160 `tangled repo create <repo-name>` 161- Creates a new repository under the user's account. 162 `tangled repo view [--json <fields>]` 163- Displays details about the current repository. If `--json` is provided, outputs only the specified fields in JSON format. 164 `tangled issue create "<title>" [--body "<body>" | --body-file <file> | -F -]` 165- Creates a new issue in the current repository with the given title and optional body, which can be provided via flag, file, or stdin. 166 `tangled pr create --base <base-branch> --head <head-branch> --title <title> [--body <body> | --body-file <file> | -F -]` 167- Creates a new pull request in the current repository from a head branch to a base branch. 168 `tangled pr list [--json <fields>]` 169- Lists pull requests for the current repository. 170 `tangled pr view <id> [--json <fields>]` 171- Displays detailed information about a specific pull request, including comments. 172 `tangled pr comment <id> [--body <body> | --body-file <file> | -F -]` 173- Adds a comment to a pull request. 174 `tangled pr review <id> --comment <comment> [--approve | --request-changes]` 175- Submits a review for a pull request, with optional approval or request for changes. 176 177## Design Decisions & Outstanding Issues 178 179This section documents key design decisions and tracks outstanding architectural questions. 180 181### (Resolved) SSH Key Management (`gh` Compatibility) 182 183- **Original Question:** How does `gh` manage SSH keys, and can we follow that pattern? 184- **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. 185- **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. 186 187### (Decided) Secure Session Storage 188 189- **Original Question:** How should we securely store the AT Proto session token? 190- **Resolution:** Storing sensitive tokens in plaintext files is not secure. 191- **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. 192 193### (Decided) Configuration Resolution Order 194 195- **Original Question:** How should settings be resolved from different sources? 196- **Resolution:** A clear precedence order is necessary. 197- **Our Approach:** The CLI will resolve settings in the following order of precedence (highest first): 198 1. Command-line flags (e.g., `--repo-did ...`) 199 2. Environment variables (e.g., `TANGLED_REPO_DID=...`) 200 3. Project-specific config file (e.g., `.tangled/config.yml` in the current directory) 201 4. Global user config file (e.g., `~/.config/tangled/config.yml`) 202 203### (Decided for V1) Authentication Flow: App Passwords (PDS) 204 205- **Original Question:** Can we allow auth through a web browser? 206- **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. 207- **`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**. 208- **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. 209- **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. 210- **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. 211 212## Future Expansion Opportunities 213 214The 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: 215 216- **CI/CD Pipelines:** Commands to view pipeline status and manage CI/CD jobs. 217- **Repository Secrets:** A dedicated command set for managing CI/CD secrets within a repository (`tangled repo secret ...`). 218- **Advanced Git Operations:** Commands to interact with the commit log, diffs, branches, and tags directly via the API, augmenting local `git` commands. 219- **Social & Feed Interactions:** Commands for starring repositories, reacting to feed items, and managing the user's social graph (following/unfollowing). 220- **Label Management:** Commands to create, apply, and remove labels from issues and pull requests. 221- **Collaboration:** Commands to manage repository collaborators. 222- **Fork Management:** Commands for forking repositories and managing the sync status of forks. 223- **Reactions**: Commands to add and remove reactions on issues, pull requests, and comments. 224- **Commenting on Issues**: Commands to add comments to issues. 225 226## Task Management 227 228Tasks 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. 229 230## Development 231 232### Prerequisites 233 234- Node.js 22.0.0 or higher (latest LTS) 235- npm (comes with Node.js) 236 237### Installation 238 239Clone the repository and install dependencies: 240 241```bash 242npm install 243``` 244 245### Available Scripts 246 247- `npm run dev` - Run the CLI in development mode (with hot reload via tsx) 248- `npm run build` - Build TypeScript to JavaScript (output to `dist/`) 249- `npm test` - Run tests once 250- `npm run test:watch` - Run tests in watch mode 251- `npm run test:coverage` - Run tests with coverage report 252- `npm run lint` - Check code with Biome linter 253- `npm run lint:fix` - Auto-fix linting issues 254- `npm run format` - Format code with Biome 255- `npm run typecheck` - Type check without building 256 257### Running Locally 258 259When running commands against the development version, use `npm run dev` with the `--` separator to pass arguments to the CLI: 260 261```bash 262# Run the CLI in development mode 263npm run dev -- --version 264npm run dev -- --help 265npm run dev -- issue list 266npm run dev -- issue create "My issue title" --body "Issue body" 267 268# Build and run the production version 269npm run build 270node dist/index.js --version 271 272# Install globally for local testing 273npm link 274tang --version 275tang --help 276npm unlink -g @markbennett/tang # Unlink when done 277``` 278 279### Project Structure 280 281``` 282tangled-cli/ 283├── src/ 284│ ├── index.ts # Main CLI entry point 285│ ├── commands/ # Command implementations 286│ ├── lib/ # Core business logic 287│ └── utils/ # Helper functions 288├── tests/ # Test files 289├── dist/ # Build output (gitignored) 290└── package.json # Package configuration 291``` 292 293### Coding Guidelines 294 295**IMPORTANT: These guidelines must be followed for all code contributions.** 296 297#### Validation Functions Location 298 299**ALL validation logic belongs in `src/utils/validation.ts`** 300 301- Use Zod schemas for all input validation 302- Boolean validation helpers (e.g., `isValidHandle()`, `isValidTangledDid()`) go in `validation.ts` 303- Never define validation functions in other files - import from `validation.ts` 304- Validation functions should return `true/false` or use Zod's `safeParse()` pattern 305 306Example: 307```typescript 308// ✅ CORRECT: validation.ts 309export function isValidHandle(handle: string): boolean { 310 return handleSchema.safeParse(handle).success; 311} 312 313// ❌ WRONG: Don't define validators in other files 314// git.ts should import isValidHandle, not define it 315``` 316 317#### Test Coverage Requirements 318 319**ALL code must have comprehensive test coverage** 320 321- Every new feature requires tests in the corresponding `tests/` directory 322- Commands must have test files (e.g., `src/commands/foo.ts``tests/commands/foo.test.ts`) 323- Utilities must have test files (e.g., `src/utils/bar.ts``tests/utils/bar.test.ts`) 324- Tests should cover: 325 - Success cases (happy path) 326 - Error cases (validation failures, network errors, etc.) 327 - Edge cases (empty input, boundary values, etc.) 328- Aim for high test coverage - tests are not optional 329 330Example test structure: 331```typescript 332describe('MyFeature', () => { 333 describe('successfulOperation', () => { 334 it('should handle valid input', async () => { /* ... */ }); 335 it('should handle edge case', async () => { /* ... */ }); 336 }); 337 338 describe('errorHandling', () => { 339 it('should reject invalid input', async () => { /* ... */ }); 340 it('should handle network errors', async () => { /* ... */ }); 341 }); 342}); 343``` 344 345#### Pull Request Checklist 346 347Before submitting code, verify: 348- [ ] All validation functions are in `validation.ts` 349- [ ] Comprehensive tests are written and passing 350- [ ] TypeScript compilation passes (`npm run typecheck`) 351- [ ] Linting passes (`npm run lint`) 352- [ ] All tests pass (`npm test`) 353 354### Technology Stack 355 356- **TypeScript 5.7.2** - Latest stable with strict mode enabled 357- **Node.js 22+** - Latest LTS target 358- **ES2023** - Latest stable ECMAScript target 359- **Biome** - Fast linter and formatter (replaces ESLint + Prettier) 360- **Vitest** - Fast unit test framework 361- **Commander.js** - CLI framework 362- **tsx** - Fast TypeScript execution for development