@markbennett/tang
v0.0.1
Published
A simple CLI for Tangled.org. Inspired by GitHub CLI, and usable by humans and AI.
Readme
tang
A CLI for Tangled.org — manage issues and repository context from the terminal. Designed to be usable by both humans and AI agents.
Installation
npm install -g @markbennett/tangQuick Start
# 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.pubCommands
| 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.mdor-F ./file.mdto 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
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
--jsonis 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
ghmanage SSH keys, and can we follow that pattern? - Resolution: Analysis shows that
ghdoes 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 addcommand follows this exact pattern. It provides a user-friendly way to upload a public key totangled.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
keytarwill 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):
- Command-line flags (e.g.,
--repo-did ...) - Environment variables (e.g.,
TANGLED_REPO_DID=...) - Project-specific config file (e.g.,
.tangled/config.ymlin the current directory) - Global user config file (e.g.,
~/.config/tangled/config.yml)
- Command-line flags (e.g.,
(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 loginFlow: When runningtangled 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
gitcommands. - 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. 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:
npm installAvailable Scripts
npm run dev- Run the CLI in development mode (with hot reload via tsx)npm run build- Build TypeScript to JavaScript (output todist/)npm test- Run tests oncenpm run test:watch- Run tests in watch modenpm run test:coverage- Run tests with coverage reportnpm run lint- Check code with Biome linternpm run lint:fix- Auto-fix linting issuesnpm run format- Format code with Biomenpm 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:
# 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 doneProject 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 configurationCoding 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 invalidation.ts - Never define validation functions in other files - import from
validation.ts - Validation functions should return
true/falseor use Zod'ssafeParse()pattern
Example:
// ✅ 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 itTest 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:
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
