Rust CLI for tangled

Update documentation to reflect current implementation status

- README.md: Complete rewrite with all implemented features
- Comprehensive command overview (auth, repo, issue, pr, knot, spindle)
- Installation instructions (source + AUR)
- Quick start guide with examples
- Configuration and environment variables
- Examples for issues, PRs, and CI/CD
- Development workflow section
- AGENTS.md: Updated from initial handoff to current status doc
- Implementation checklist (all features marked as complete except spindle run)
- Architecture patterns (ServiceAuth, repo creation, listing, PR merging)
- Working with tangled-core reference
- Next steps for contributors (focus on spindle run)
- Troubleshooting guide
- docs/getting-started.md: Expanded from stub to comprehensive tutorial
- Step-by-step installation guide
- First steps (auth, list, create, clone)
- Detailed examples for issues, PRs, and CI/CD
- Advanced topics (migration, output formats, quiet/verbose)
- Configuration and environment variables
- Troubleshooting section

Removed outdated "commands are stubs" messaging and added
accurate feature descriptions for all implemented functionality.

Changed files
+659 -399
docs
+188 -375
AGENTS.md
··· 1 - # Tangled CLI – Agent Handoff (Massive Context) 1 + # Tangled CLI – Current Implementation Status 2 2 3 - This document is a complete handoff for the next Codex instance working on the Tangled CLI (Rust). It explains what exists, what to build next, where to edit, how to call the APIs, how to persist sessions, how to print output, and how to validate success. 3 + This document provides an overview of the Tangled CLI implementation status for AI agents or developers working on the project. 4 4 5 - Primary focus for this session: implement authentication (auth login/status/logout) and repository listing (repo list). 5 + ## Implementation Status 6 6 7 - -------------------------------------------------------------------------------- 7 + ### ✅ Fully Implemented 8 8 9 - ## 0) TL;DR – Immediate Actions 9 + #### Authentication (`auth`) 10 + - `login` - Authenticate with AT Protocol using `com.atproto.server.createSession` 11 + - `status` - Show current authentication status 12 + - `logout` - Clear stored session from keyring 10 13 11 - - Implement `auth login` using AT Protocol `com.atproto.server.createSession`. 12 - - Prompt for handle/password if flags aren’t provided. 13 - - POST to `/xrpc/com.atproto.server.createSession` at the configured PDS (default `https://bsky.social`). 14 - - Persist `{accessJwt, refreshJwt, did, handle}` via `SessionManager` (keyring-backed). 15 - - `auth status` reads keyring and prints handle + did; `auth logout` clears keyring. 14 + #### Repositories (`repo`) 15 + - `list` - List repositories using `com.atproto.repo.listRecords` with `collection=sh.tangled.repo` 16 + - `create` - Create repositories with two-step flow: 17 + 1. Create PDS record via `com.atproto.repo.createRecord` 18 + 2. Initialize bare repo via `sh.tangled.repo.create` with ServiceAuth 19 + - `clone` - Clone repositories using libgit2 with SSH agent support 20 + - `info` - Display repository information including stats and languages 21 + - `delete` - Delete repositories (both PDS record and knot repo) 22 + - `star` / `unstar` - Star/unstar repositories via `sh.tangled.feed.star` 16 23 17 - - Implement `repo list` using Tangled’s repo list method (tentative `sh.tangled.repo.list`). 18 - - GET `/xrpc/sh.tangled.repo.list` with optional params: `user`, `knot`, `starred`. 19 - - Include `Authorization: Bearer <accessJwt>` if required. 20 - - Print results as table (default) or JSON (`--format json`). 24 + #### Issues (`issue`) 25 + - `list` - List issues via `com.atproto.repo.listRecords` with `collection=sh.tangled.repo.issue` 26 + - `create` - Create issues via `com.atproto.repo.createRecord` 27 + - `show` - Show issue details and comments 28 + - `edit` - Edit issue title, body, or state 29 + - `comment` - Add comments to issues 21 30 22 - Keep edits minimal and scoped to these features. 31 + #### Pull Requests (`pr`) 32 + - `list` - List PRs via `com.atproto.repo.listRecords` with `collection=sh.tangled.repo.pull` 33 + - `create` - Create PRs using `git format-patch` for patches 34 + - `show` - Show PR details and diff 35 + - `review` - Review PRs with approve/request-changes flags 36 + - `merge` - Merge PRs via `sh.tangled.repo.merge` with ServiceAuth 23 37 24 - -------------------------------------------------------------------------------- 38 + #### Knot Management (`knot`) 39 + - `migrate` - Migrate repositories between knots 40 + - Validates working tree is clean and pushed 41 + - Creates new repo on target knot with source seeding 42 + - Updates PDS record to point to new knot 25 43 26 - ## 1) Repository Map (Paths You Will Touch) 44 + #### Spindle CI/CD (`spindle`) 45 + - `config` - Enable/disable or configure spindle URL for a repository 46 + - Updates the `spindle` field in `sh.tangled.repo` record 47 + - `list` - List pipeline runs via `com.atproto.repo.listRecords` with `collection=sh.tangled.pipeline` 48 + - `logs` - Stream workflow logs via WebSocket (`wss://spindle.tangled.sh/spindle/logs/{knot}/{rkey}/{name}`) 49 + - `secret list` - List secrets via `sh.tangled.repo.listSecrets` with ServiceAuth 50 + - `secret add` - Add secrets via `sh.tangled.repo.addSecret` with ServiceAuth 51 + - `secret remove` - Remove secrets via `sh.tangled.repo.removeSecret` with ServiceAuth 27 52 28 - - CLI (binary): 29 - - `tangled/crates/tangled-cli/src/commands/auth.rs` → implement login/status/logout. 30 - - `tangled/crates/tangled-cli/src/commands/repo.rs` → implement list. 31 - - `tangled/crates/tangled-cli/src/cli.rs` → already contains arguments and subcommands; no structural changes needed. 32 - - `tangled/crates/tangled-cli/src/main.rs` → no change. 53 + ### 🚧 Partially Implemented / Stubs 33 54 34 - - Config + session: 35 - - `tangled/crates/tangled-config/src/session.rs` → already provides `Session` + `SessionManager` (keyring). 36 - - `tangled/crates/tangled-config/src/config.rs` → optional use for PDS/base URL (MVP can use CLI flags/env vars). 55 + #### Spindle CI/CD (`spindle`) 56 + - `run` - Manually trigger a workflow (stub) 57 + - **TODO**: Parse `.tangled.yml` to determine workflows 58 + - **TODO**: Create pipeline record and trigger spindle ingestion 59 + - **TODO**: Support manual trigger inputs 37 60 38 - - API client: 39 - - `tangled/crates/tangled-api/src/client.rs` → add XRPC helpers and implement `login_with_password` and `list_repos`. 40 - 41 - -------------------------------------------------------------------------------- 42 - 43 - ## 2) Current State Snapshot 44 - 45 - - Workspace is scaffolded and compiles after wiring dependencies (network needed to fetch crates): 46 - - `tangled-cli`: clap CLI with subcommands; commands currently log stubs. 47 - - `tangled-config`: TOML config loader/saver; keyring-backed session store. 48 - - `tangled-api`: client struct with placeholder methods. 49 - - `tangled-git`: stubs for future. 50 - - Placeholder lexicons in `tangled/lexicons/sh.tangled/*` are not authoritative; use AT Protocol docs and inspect real endpoints later. 51 - 52 - Goal: replace CLI stubs with real API calls for auth + repo list. 53 - 54 - -------------------------------------------------------------------------------- 55 - 56 - ## 3) Endpoints & Data Shapes 57 - 58 - ### 3.1 AT Protocol – Create Session 59 - 60 - - Method: `com.atproto.server.createSession` 61 - - HTTP: `POST /xrpc/com.atproto.server.createSession` 62 - - Request JSON: 63 - - `identifier: string` → user handle or email (e.g., `alice.bsky.social`). 64 - - `password: string` → password or app password. 65 - - Response JSON (subset used): 66 - - `accessJwt: string` 67 - - `refreshJwt: string` 68 - - `did: string` (e.g., `did:plc:...`) 69 - - `handle: string` 70 - 71 - Persist to keyring using `SessionManager`. 72 - 73 - ### 3.2 Tangled – Repo List (tentative) 74 - 75 - - Method: `sh.tangled.repo.list` (subject to change; wire in a constant to adjust easily). 76 - - HTTP: `GET /xrpc/sh.tangled.repo.list?user=<..>&knot=<..>&starred=<true|false>` 77 - - Auth: likely required; include `Authorization: Bearer <accessJwt>`. 78 - - Response JSON (envelope): 79 - - `{ "repos": [{ "name": string, "knot": string, "private": bool, ... }] }` 80 - 81 - If method name or response shape differs, adapt the client code; keep CLI interface stable. 82 - 83 - -------------------------------------------------------------------------------- 84 - 85 - ## 4) Implementation Plan 86 - 87 - ### 4.1 Add XRPC helpers and methods in `tangled-api` 88 - 89 - File: `tangled/crates/tangled-api/src/client.rs` 90 - 91 - - Extend `TangledClient` with: 92 - - `fn xrpc_url(&self, method: &str) -> String` → combines `base_url` + `/xrpc/` + `method`. 93 - - `async fn post_json<TReq: Serialize, TRes: DeserializeOwned>(&self, method, req, bearer) -> Result<TRes>`. 94 - - `async fn get_json<TRes: DeserializeOwned>(&self, method, params, bearer) -> Result<TRes>`. 95 - - Include `Authorization: Bearer <token>` when `bearer` is provided. 96 - 97 - - Implement: 98 - - `pub async fn login_with_password(&self, handle: &str, password: &str, pds: &str) -> Result<Session>` 99 - - POST to `com.atproto.server.createSession` at `self.base_url` (which should be the PDS base). 100 - - Map response to `tangled_config::session::Session` and return it (caller will persist). 101 - - `pub async fn list_repos(&self, user: Option<&str>, knot: Option<&str>, starred: bool, bearer: Option<&str>) -> Result<Vec<Repository>>` 102 - - GET `sh.tangled.repo.list` with params present only if set. 103 - - Return parsed `Vec<Repository>` from an envelope `{ repos: [...] }`. 104 - 105 - Error handling: For non-2xx, read the response body, return `anyhow!("{status}: {body}")`. 61 + ## Architecture Overview 106 62 107 - ### 4.2 Wire CLI auth commands 63 + ### Workspace Structure 108 64 109 - File: `tangled/crates/tangled-cli/src/commands/auth.rs` 65 + - `crates/tangled-cli` - CLI binary with clap-based argument parsing 66 + - `crates/tangled-config` - Configuration and keyring-backed session management 67 + - `crates/tangled-api` - XRPC client wrapper for AT Protocol and Tangled APIs 68 + - `crates/tangled-git` - Git operation helpers (currently unused) 110 69 111 - - `login`: 112 - - Determine PDS: use `--pds` arg if provided, else default `https://bsky.social` (later from config/env). 113 - - Prompt for missing handle/password. 114 - - `let client = tangled_api::TangledClient::new(&pds);` 115 - - `let session = client.login_with_password(&handle, &password, &pds).await?;` 116 - - `tangled_config::session::SessionManager::default().save(&session)?;` 117 - - Print: `Logged in as '{handle}' ({did})`. 70 + ### Key Patterns 118 71 119 - - `status`: 120 - - Load `SessionManager::default().load()?`. 121 - - If Some: print `Logged in as '{handle}' ({did})`. 122 - - Else: print `Not logged in. Run: tangled auth login`. 72 + #### ServiceAuth Flow 73 + Many Tangled API operations require ServiceAuth tokens: 74 + 1. Obtain token via `com.atproto.server.getServiceAuth` from PDS 75 + - `aud` parameter must be `did:web:<target-host>` 76 + - `exp` parameter should be Unix timestamp + 600 seconds 77 + 2. Use token as `Authorization: Bearer <serviceAuth>` for Tangled API calls 123 78 124 - - `logout`: 125 - - `SessionManager::default().clear()?`. 126 - - Print `Logged out` if something was cleared; otherwise `No session found` is acceptable. 79 + #### Repository Creation Flow 80 + Two-step process: 81 + 1. **PDS**: Create `sh.tangled.repo` record via `com.atproto.repo.createRecord` 82 + 2. **Tangled API**: Initialize bare repo via `sh.tangled.repo.create` with ServiceAuth 127 83 128 - ### 4.3 Wire CLI repo list 84 + #### Repository Listing 85 + Done entirely via PDS (not Tangled API): 86 + 1. Resolve handle → DID if needed via `com.atproto.identity.resolveHandle` 87 + 2. List records via `com.atproto.repo.listRecords` with `collection=sh.tangled.repo` 88 + 3. Filter client-side (e.g., by knot) 129 89 130 - File: `tangled/crates/tangled-cli/src/commands/repo.rs` 90 + #### Pull Request Merging 91 + 1. Fetch PR record to get patch and target branch 92 + 2. Obtain ServiceAuth token 93 + 3. Call `sh.tangled.repo.merge` with `{did, name, patch, branch, commitMessage, commitBody}` 131 94 132 - - Load session; if absent, print `Please login first: tangled auth login` and exit 1 (or 0 with friendly message; choose one and be consistent). 133 - - Build a client for Tangled API base (for now, default to `https://tangled.org` or allow `TANGLED_API_BASE` env var to override): 134 - - `let base = std::env::var("TANGLED_API_BASE").unwrap_or_else(|_| "https://tangled.org".into());` 135 - - `let client = tangled_api::TangledClient::new(base);` 136 - - Call `client.list_repos(args.user.as_deref(), args.knot.as_deref(), args.starred, Some(session.access_jwt.as_str())).await?`. 137 - - Print: 138 - - If `Cli.format == OutputFormat::Json`: `serde_json::to_string_pretty(&repos)`. 139 - - Else: simple columns `NAME KNOT PRIVATE` using `println!` formatting for now. 95 + ### Base URLs and Defaults 140 96 141 - -------------------------------------------------------------------------------- 97 + - **PDS Base** (auth + record operations): Default `https://bsky.social`, stored in session 98 + - **Tangled API Base** (server operations): Default `https://tngl.sh`, can override via `TANGLED_API_BASE` 99 + - **Spindle Base** (CI/CD): Default `wss://spindle.tangled.sh` for WebSocket logs, can override via `TANGLED_SPINDLE_BASE` 142 100 143 - ## 5) Code Snippets (Copy/Paste Friendly) 101 + ### Session Management 144 102 145 - ### 5.1 In `tangled-api/src/client.rs` 103 + Sessions are stored in the system keyring: 104 + - Linux: GNOME Keyring / KWallet via Secret Service API 105 + - macOS: macOS Keychain 106 + - Windows: Windows Credential Manager 146 107 108 + Session includes: 147 109 ```rust 148 - use anyhow::{anyhow, bail, Result}; 149 - use serde::{de::DeserializeOwned, Deserialize, Serialize}; 150 - use tangled_config::session::Session; 151 - 152 - #[derive(Clone, Debug)] 153 - pub struct TangledClient { pub(crate) base_url: String } 154 - 155 - impl TangledClient { 156 - pub fn new(base_url: impl Into<String>) -> Self { Self { base_url: base_url.into() } } 157 - pub fn default() -> Self { Self::new("https://tangled.org") } 158 - 159 - fn xrpc_url(&self, method: &str) -> String { 160 - format!("{}/xrpc/{}", self.base_url.trim_end_matches('/'), method) 161 - } 162 - 163 - async fn post_json<TReq: Serialize, TRes: DeserializeOwned>( 164 - &self, 165 - method: &str, 166 - req: &TReq, 167 - bearer: Option<&str>, 168 - ) -> Result<TRes> { 169 - let url = self.xrpc_url(method); 170 - let client = reqwest::Client::new(); 171 - let mut reqb = client.post(url).header(reqwest::header::CONTENT_TYPE, "application/json"); 172 - if let Some(token) = bearer { reqb = reqb.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token)); } 173 - let res = reqb.json(req).send().await?; 174 - let status = res.status(); 175 - if !status.is_success() { 176 - let body = res.text().await.unwrap_or_default(); 177 - return Err(anyhow!("{}: {}", status, body)); 178 - } 179 - Ok(res.json::<TRes>().await?) 180 - } 181 - 182 - async fn get_json<TRes: DeserializeOwned>( 183 - &self, 184 - method: &str, 185 - params: &[(&str, String)], 186 - bearer: Option<&str>, 187 - ) -> Result<TRes> { 188 - let url = self.xrpc_url(method); 189 - let client = reqwest::Client::new(); 190 - let mut reqb = client.get(url).query(&params); 191 - if let Some(token) = bearer { reqb = reqb.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token)); } 192 - let res = reqb.send().await?; 193 - let status = res.status(); 194 - if !status.is_success() { 195 - let body = res.text().await.unwrap_or_default(); 196 - return Err(anyhow!("{}: {}", status, body)); 197 - } 198 - Ok(res.json::<TRes>().await?) 199 - } 200 - 201 - pub async fn login_with_password(&self, handle: &str, password: &str, _pds: &str) -> Result<Session> { 202 - #[derive(Serialize)] 203 - struct Req<'a> { #[serde(rename = "identifier")] identifier: &'a str, #[serde(rename = "password")] password: &'a str } 204 - #[derive(Deserialize)] 205 - struct Res { #[serde(rename = "accessJwt")] access_jwt: String, #[serde(rename = "refreshJwt")] refresh_jwt: String, did: String, handle: String } 206 - let body = Req { identifier: handle, password }; 207 - let res: Res = self.post_json("com.atproto.server.createSession", &body, None).await?; 208 - Ok(Session { access_jwt: res.access_jwt, refresh_jwt: res.refresh_jwt, did: res.did, handle: res.handle, ..Default::default() }) 209 - } 210 - 211 - pub async fn list_repos(&self, user: Option<&str>, knot: Option<&str>, starred: bool, bearer: Option<&str>) -> Result<Vec<Repository>> { 212 - #[derive(Deserialize)] 213 - struct Envelope { repos: Vec<Repository> } 214 - let mut q = vec![]; 215 - if let Some(u) = user { q.push(("user", u.to_string())); } 216 - if let Some(k) = knot { q.push(("knot", k.to_string())); } 217 - if starred { q.push(("starred", true.to_string())); } 218 - let env: Envelope = self.get_json("sh.tangled.repo.list", &q, bearer).await?; 219 - Ok(env.repos) 220 - } 110 + struct Session { 111 + access_jwt: String, 112 + refresh_jwt: String, 113 + did: String, 114 + handle: String, 115 + pds: Option<String>, // PDS base URL 221 116 } 222 - 223 - #[derive(Debug, Clone, Serialize, Deserialize, Default)] 224 - pub struct Repository { pub did: Option<String>, pub rkey: Option<String>, pub name: String, pub knot: Option<String>, pub description: Option<String>, pub private: bool } 225 117 ``` 226 118 227 - ### 5.2 In `tangled-cli/src/commands/auth.rs` 119 + ## Working with tangled-core 228 120 229 - ```rust 230 - use anyhow::Result; 231 - use dialoguer::{Input, Password}; 232 - use tangled_config::session::SessionManager; 233 - use crate::cli::{AuthCommand, AuthLoginArgs, Cli}; 121 + The `../tangled-core` repository contains the server implementation and lexicon definitions. 234 122 235 - pub async fn run(_cli: &Cli, cmd: AuthCommand) -> Result<()> { 236 - match cmd { 237 - AuthCommand::Login(args) => login(args).await, 238 - AuthCommand::Status => status().await, 239 - AuthCommand::Logout => logout().await, 240 - } 241 - } 123 + ### Key Files to Check 242 124 243 - async fn login(mut args: AuthLoginArgs) -> Result<()> { 244 - let handle: String = match args.handle.take() { Some(h) => h, None => Input::new().with_prompt("Handle").interact_text()? }; 245 - let password: String = match args.password.take() { Some(p) => p, None => Password::new().with_prompt("Password").interact()? }; 246 - let pds = args.pds.unwrap_or_else(|| "https://bsky.social".to_string()); 247 - let client = tangled_api::TangledClient::new(&pds); 248 - let mut session = match client.login_with_password(&handle, &password, &pds).await { 249 - Ok(sess) => sess, 250 - Err(e) => { 251 - println!("\x1b[93mIf you're on your own PDS, make sure to pass the --pds flag\x1b[0m"); 252 - return Err(e); 253 - } 254 - }; 255 - SessionManager::default().save(&session)?; 256 - println!("Logged in as '{}' ({})", session.handle, session.did); 257 - Ok(()) 258 - } 125 + - **Lexicons**: `../tangled-core/lexicons/**/*.json` 126 + - Defines XRPC method schemas (NSIDs, parameters, responses) 127 + - Example: `sh.tangled.repo.create`, `sh.tangled.repo.merge` 259 128 260 - async fn status() -> Result<()> { 261 - let mgr = SessionManager::default(); 262 - match mgr.load()? { 263 - Some(s) => println!("Logged in as '{}' ({})", s.handle, s.did), 264 - None => println!("Not logged in. Run: tangled auth login"), 265 - } 266 - Ok(()) 267 - } 129 + - **XRPC Routes**: `../tangled-core/knotserver/xrpc/xrpc.go` 130 + - Shows which endpoints require ServiceAuth 131 + - Maps NSIDs to handler functions 268 132 269 - async fn logout() -> Result<()> { 270 - let mgr = SessionManager::default(); 271 - if mgr.load()?.is_some() { mgr.clear()?; println!("Logged out"); } else { println!("No session found"); } 272 - Ok(()) 273 - } 274 - ``` 133 + - **API Handlers**: `../tangled-core/knotserver/xrpc/*.go` 134 + - Implementation details for server-side operations 135 + - Example: `create_repo.go`, `merge.go` 275 136 276 - ### 5.3 In `tangled-cli/src/commands/repo.rs` 137 + ### Useful Search Commands 277 138 278 - ```rust 279 - use anyhow::{anyhow, Result}; 280 - use tangled_config::session::SessionManager; 281 - use crate::cli::{Cli, RepoCommand, RepoListArgs}; 139 + ```bash 140 + # Find a specific NSID 141 + rg -n "sh\.tangled\.repo\.create" ../tangled-core 282 142 283 - pub async fn run(_cli: &Cli, cmd: RepoCommand) -> Result<()> { 284 - match cmd { RepoCommand::List(args) => list(args).await, _ => Ok(println!("not implemented")) } 285 - } 143 + # List all lexicons 144 + ls ../tangled-core/lexicons/repo 286 145 287 - async fn list(args: RepoListArgs) -> Result<()> { 288 - let mgr = SessionManager::default(); 289 - let session = mgr.load()?.ok_or_else(|| anyhow!("Please login first: tangled auth login"))?; 290 - let base = std::env::var("TANGLED_API_BASE").unwrap_or_else(|_| "https://tangled.org".into()); 291 - let client = tangled_api::TangledClient::new(base); 292 - let repos = client.list_repos(args.user.as_deref(), args.knot.as_deref(), args.starred, Some(session.access_jwt.as_str())).await?; 293 - // Simple output: table or JSON to be improved later 294 - println!("NAME\tKNOT\tPRIVATE"); 295 - for r in repos { println!("{}\t{}\t{}", r.name, r.knot.unwrap_or_default(), r.private); } 296 - Ok(()) 297 - } 146 + # Check ServiceAuth usage 147 + rg -n "ServiceAuth|VerifyServiceAuth" ../tangled-core 298 148 ``` 299 149 300 - -------------------------------------------------------------------------------- 150 + ## Next Steps for Contributors 301 151 302 - ## 6) Configuration, Env Vars, and Security 152 + ### Priority: Implement `spindle run` 303 153 304 - - PDS base (auth): default `https://bsky.social`. Accept CLI flag `--pds`; later read from config. 305 - - Tangled API base (repo list): default `https://tangled.org`; allow override via `TANGLED_API_BASE` env var. 306 - - Do not log passwords or tokens. 307 - - Store tokens only in keyring (already implemented). 154 + The only remaining stub is `spindle run` for manually triggering workflows. Implementation plan: 308 155 309 - -------------------------------------------------------------------------------- 156 + 1. **Parse `.tangled.yml`** in the current repository to extract workflow definitions 157 + - Look for workflow names, triggers, and manual trigger inputs 310 158 311 - ## 7) Testing Plan (MVP) 159 + 2. **Create pipeline record** on PDS via `com.atproto.repo.createRecord`: 160 + ```rust 161 + collection: "sh.tangled.pipeline" 162 + record: { 163 + triggerMetadata: { 164 + kind: "manual", 165 + repo: { knot, did, repo, defaultBranch }, 166 + manual: { inputs: [...] } 167 + }, 168 + workflows: [{ name, engine, clone, raw }] 169 + } 170 + ``` 312 171 313 - - Client unit tests with `mockito` for `createSession` and `repo list` endpoints; simulate expected JSON. 314 - - CLI smoke tests optional for this pass. If added, use `assert_cmd` to check printed output strings. 315 - - Avoid live network calls in tests. 172 + 3. **Notify spindle** (if needed) or let the ingester pick up the new record 316 173 317 - -------------------------------------------------------------------------------- 174 + 4. **Support workflow selection** when multiple workflows exist: 175 + - `--workflow <name>` flag to select specific workflow 176 + - Default to first workflow if not specified 318 177 319 - ## 8) Acceptance Criteria 178 + 5. **Support manual inputs** (if workflow defines them): 179 + - Prompt for input values or accept via flags 320 180 321 - - `tangled auth login`: 322 - - Prompts or uses flags; successful call saves session and prints `Logged in as ...`. 323 - - On failure, shows HTTP status and error message, plus helpful hint about --pds flag for users on their own PDS. 324 - - `tangled auth status`: 325 - - Shows handle + did if session exists; otherwise says not logged in. 326 - - `tangled auth logout`: 327 - - Clears keyring; prints confirmation. 328 - - `tangled repo list`: 329 - - Performs authenticated GET and prints a list (even if empty) without panicking. 330 - - JSON output possible later; table output acceptable for now. 181 + ### Code Quality Tasks 331 182 332 - -------------------------------------------------------------------------------- 183 + - Add more comprehensive error messages for common failure cases 184 + - Improve table formatting for list commands (consider using `tabled` crate features) 185 + - Add shell completion generation (bash, zsh, fish) 186 + - Add more unit tests with `mockito` for API client methods 187 + - Add integration tests with `assert_cmd` for CLI commands 333 188 334 - ## 9) Troubleshooting Notes 335 - 336 - - Keyring errors on Linux may indicate no secret service running; suggest enabling GNOME Keyring or KWallet. 337 - - If `repo list` returns 404, the method name or base URL may be wrong; adjust `sh.tangled.repo.list` or `TANGLED_API_BASE`. 338 - - If 401, session may be missing/expired; run `auth login` again. 339 - 340 - -------------------------------------------------------------------------------- 341 - 342 - ## 10) Non‑Goals for This Pass 189 + ### Documentation Tasks 343 190 344 - - Refresh token flow, device code, OAuth. 345 - - PRs, issues, knots, spindle implementation. 346 - - Advanced formatting, paging, completions. 347 - 348 - -------------------------------------------------------------------------------- 191 + - Add man pages for all commands 192 + - Create video tutorials for common workflows 193 + - Add troubleshooting guide for common issues 349 194 350 - ## 11) Future Follow‑ups 351 - 352 - - Refresh flow (`com.atproto.server.refreshSession`) and retry once on 401. 353 - - Persist base URLs and profiles in config; add `tangled config` commands. 354 - - Proper table/json formatting and shell completions. 355 - 356 - -------------------------------------------------------------------------------- 357 - 358 - ## 12) Quick Operator Commands 359 - 360 - - Build CLI: `cargo build -p tangled-cli` 361 - - Help: `cargo run -p tangled-cli -- --help` 362 - - Login: `cargo run -p tangled-cli -- auth login --handle <handle>` 363 - - Status: `cargo run -p tangled-cli -- auth status` 364 - - Repo list: `TANGLED_API_BASE=https://tangled.org cargo run -p tangled-cli -- repo list --user <handle>` 365 - 366 - -------------------------------------------------------------------------------- 195 + ## Development Workflow 367 196 368 - End of handoff. Implement auth login and repo list as described, keeping changes focused and testable. 197 + ### Building 369 198 199 + ```sh 200 + cargo build # Debug build 201 + cargo build --release # Release build 202 + ``` 370 203 371 - -------------------------------------------------------------------------------- 204 + ### Running 372 205 373 - ## 13) Tangled Core (../tangled-core) – Practical Notes 206 + ```sh 207 + cargo run -p tangled-cli -- <command> 208 + ``` 374 209 375 - This workspace often needs to peek at the Tangled monorepo to confirm XRPC endpoints and shapes. Here are concise tips and findings that informed this CLI implementation. 210 + ### Testing 376 211 377 - ### Where To Look 212 + ```sh 213 + cargo test # Run all tests 214 + cargo test -- --nocapture # Show println output 215 + ``` 378 216 379 - - Lexicons (authoritative NSIDs and shapes): `../tangled-core/lexicons/**` 380 - - Repo create: `../tangled-core/lexicons/repo/create.json` → `sh.tangled.repo.create` 381 - - Repo record schema: `../tangled-core/lexicons/repo/repo.json` → `sh.tangled.repo` 382 - - Misc repo queries (tree, log, tags, etc.) under `../tangled-core/lexicons/repo/` 383 - - Note: there is no `sh.tangled.repo.list` lexicon in the core right now; listing is done via ATproto records. 384 - - Knotserver XRPC routes (what requires auth vs open): `../tangled-core/knotserver/xrpc/xrpc.go` 385 - - Mutating repo ops (e.g., `sh.tangled.repo.create`) are behind ServiceAuth middleware. 386 - - Read-only repo queries (tree, log, etc.) are open. 387 - - Create repo handler (server-side flow): `../tangled-core/knotserver/xrpc/create_repo.go` 388 - - Validates ServiceAuth; expects rkey for the `sh.tangled.repo` record that already exists on the user's PDS. 389 - - ServiceAuth middleware (how Bearer is validated): `../tangled-core/xrpc/serviceauth/service_auth.go` 390 - - Validates a ServiceAuth token with Audience = `did:web:<knot-or-service-host>`. 391 - - Appview client for ServiceAuth: `../tangled-core/appview/xrpcclient/xrpc.go` (method: `ServerGetServiceAuth`). 217 + ### Code Quality 392 218 393 - ### How To Search Quickly (rg examples) 219 + ```sh 220 + cargo fmt # Format code 221 + cargo clippy # Run linter 222 + cargo clippy -- -W clippy::all # Strict linting 223 + ``` 394 224 395 - - Find a specific NSID across the repo: 396 - - `rg -n "sh\.tangled\.repo\.create" ../tangled-core` 397 - - See which endpoints are routed and whether they’re behind ServiceAuth: 398 - - `rg -n "chi\..*Get\(|chi\..*Post\(" ../tangled-core/knotserver/xrpc` 399 - - Then open `xrpc.go` and respective handlers. 400 - - Discover ServiceAuth usage and audience DID: 401 - - `rg -n "ServerGetServiceAuth|VerifyServiceAuth|serviceauth" ../tangled-core` 402 - - List lexicons by area: 403 - - `ls ../tangled-core/lexicons/repo` or `rg -n "\bid\": \"sh\.tangled\..*\"" ../tangled-core/lexicons` 225 + ## Troubleshooting Common Issues 404 226 405 - ### Repo Listing (client-side pattern) 227 + ### Keyring Errors on Linux 406 228 407 - - There is no `sh.tangled.repo.list` in core. To list a user’s repos: 408 - 1) Resolve handle → DID if needed via PDS: `GET com.atproto.identity.resolveHandle`. 409 - 2) List records from the user’s PDS: `GET com.atproto.repo.listRecords` with `collection=sh.tangled.repo`. 410 - 3) Filter client-side (e.g., by `knot`). “Starred” filtering is not currently defined in core. 229 + Ensure a secret service is running: 230 + ```sh 231 + systemctl --user enable --now gnome-keyring-daemon 232 + ``` 411 233 412 - ### Repo Creation (two-step flow) 234 + ### Invalid Token Errors 413 235 414 - - Step 1 (PDS): create the `sh.tangled.repo` record in the user’s repo: 415 - - `POST com.atproto.repo.createRecord` with `{ repo: <did>, collection: "sh.tangled.repo", record: { name, knot, description?, createdAt } }`. 416 - - Extract `rkey` from the returned `uri` (`at://<did>/<collection>/<rkey>`). 417 - - Step 2 (Tangled API base): call the server to initialize the bare repo on the knot: 418 - - Obtain ServiceAuth: `GET com.atproto.server.getServiceAuth` from PDS with `aud=did:web:<tngl.sh or target-host>`. 419 - - `POST sh.tangled.repo.create` on the Tangled API base with `{ rkey, defaultBranch?, source? }` and `Authorization: Bearer <serviceAuth>`. 420 - - Server validates token via `xrpc/serviceauth`, confirms actor permissions, and creates the git repo. 236 + - For record operations: Use PDS client, not Tangled API client 237 + - For server operations: Ensure ServiceAuth audience DID matches target host 421 238 422 - ### Base URLs, DIDs, and Defaults 239 + ### Repository Not Found 423 240 424 - - Tangled API base (server): default is `https://tngl.sh`. Do not use the marketing/landing site. 425 - - PDS base (auth + record ops): default `https://bsky.social` unless a different PDS was chosen on login. 426 - - ServiceAuth audience DID is `did:web:<host>` where `<host>` is the Tangled API base hostname. 427 - - CLI stores the PDS URL in the session to keep the CLI stateful. 241 + - Verify repo exists: `tangled repo info owner/name` 242 + - Check you're using the correct owner (handle or DID) 243 + - Ensure you have access permissions 428 244 429 - ### Common Errors and Fixes 245 + ### WebSocket Connection Failures 430 246 431 - - `InvalidToken` when listing repos: listing should use the PDS (`com.atproto.repo.listRecords`), not the Tangled API base. 432 - - 404 on `repo.create`: verify ServiceAuth audience matches the target host and that the rkey exists on the PDS. 433 - - Keychain issues on Linux: ensure a Secret Service (e.g., GNOME Keyring or KWallet) is running. 247 + - Check spindle base URL is correct (default: `wss://spindle.tangled.sh`) 248 + - Verify the job_id format: `knot:rkey:name` 249 + - Ensure the workflow has actually run and has logs 434 250 435 - ### Implementation Pointers (CLI) 251 + ## Additional Resources 436 252 437 - - Auth 438 - - `com.atproto.server.createSession` against the PDS, save `{accessJwt, refreshJwt, did, handle, pds}` in keyring. 439 - - List repos 440 - - Use session.handle by default; resolve to DID, then `com.atproto.repo.listRecords` on PDS. 441 - - Create repo 442 - - Build the PDS record first; then ServiceAuth → `sh.tangled.repo.create` on `tngl.sh`. 253 + - Main README: `README.md` - User-facing documentation 254 + - Getting Started Guide: `docs/getting-started.md` - Tutorial for new users 255 + - Lexicons: `../tangled-core/lexicons/` - XRPC method definitions 256 + - Server Implementation: `../tangled-core/knotserver/` - Server-side code 443 257 444 - ### Testing Hints 258 + --- 445 259 446 - - Avoid live calls; use `mockito` to stub both PDS and Tangled API base endpoints. 447 - - Unit test decoding with minimal JSON envelopes: record lists, createRecord `uri`, and repo.create (empty body or simple ack). 260 + Last updated: 2025-10-14
+168 -17
README.md
··· 1 - # Tangled CLI (Rust) 1 + # Tangled CLI 2 2 3 3 A Rust CLI for Tangled, a decentralized git collaboration platform built on the AT Protocol. 4 4 5 - Status: project scaffold with CLI, config, API and git crates. Commands are stubs pending endpoint wiring. 5 + ## Features 6 + 7 + Tangled CLI is a fully functional tool for managing repositories, issues, pull requests, and CI/CD workflows on the Tangled platform. 8 + 9 + ### Implemented Commands 10 + 11 + - **Authentication** (`auth`) 12 + - `login` - Authenticate with AT Protocol credentials 13 + - `status` - Show current authentication status 14 + - `logout` - Clear stored session 15 + 16 + - **Repositories** (`repo`) 17 + - `list` - List your repositories or another user's repos 18 + - `create` - Create a new repository on a knot 19 + - `clone` - Clone a repository to your local machine 20 + - `info` - Show detailed repository information 21 + - `delete` - Delete a repository 22 + - `star` / `unstar` - Star or unstar repositories 23 + 24 + - **Issues** (`issue`) 25 + - `list` - List issues for a repository 26 + - `create` - Create a new issue 27 + - `show` - Show issue details and comments 28 + - `edit` - Edit issue title, body, or state 29 + - `comment` - Add a comment to an issue 30 + 31 + - **Pull Requests** (`pr`) 32 + - `list` - List pull requests for a repository 33 + - `create` - Create a pull request from a branch 34 + - `show` - Show pull request details and diff 35 + - `review` - Review a pull request (approve/request changes) 36 + - `merge` - Merge a pull request 37 + 38 + - **Knot Management** (`knot`) 39 + - `migrate` - Migrate a repository to another knot 40 + 41 + - **CI/CD with Spindle** (`spindle`) 42 + - `config` - Enable/disable or configure spindle for a repository 43 + - `list` - List pipeline runs for a repository 44 + - `logs` - Stream logs from a workflow execution 45 + - `secret` - Manage secrets for CI/CD workflows 46 + - `list` - List secrets for a repository 47 + - `add` - Add or update a secret 48 + - `remove` - Remove a secret 49 + - `run` - Manually trigger a workflow (not yet implemented) 6 50 7 - ## Workspace 51 + ## Installation 8 52 9 - - `crates/tangled-cli`: CLI binary (clap-based) 10 - - `crates/tangled-config`: Config + session management 11 - - `crates/tangled-api`: XRPC client wrapper (stubs) 12 - - `crates/tangled-git`: Git helpers (stubs) 13 - - `lexicons/sh.tangled`: Placeholder lexicons 53 + ### Build from Source 14 54 15 - ## Quick start 55 + Requires Rust toolchain (1.70+) and network access to fetch dependencies. 16 56 17 57 ```sh 18 - cargo run -p tangled-cli -- --help 58 + cargo build --release 19 59 ``` 20 60 21 - ### Install from AUR (community maintained) 61 + The binary will be available at `target/release/tangled-cli`. 62 + 63 + ### Install from AUR (Arch Linux) 64 + 65 + Community-maintained package: 22 66 23 67 ```sh 24 68 yay -S tangled-cli-git 25 69 ``` 26 70 27 - Building requires network to fetch crates. 71 + ## Quick Start 72 + 73 + 1. **Login to Tangled**: 74 + ```sh 75 + tangled auth login --handle your.handle.bsky.social 76 + ``` 77 + 78 + 2. **List your repositories**: 79 + ```sh 80 + tangled repo list 81 + ``` 82 + 83 + 3. **Create a new repository**: 84 + ```sh 85 + tangled repo create myproject --description "My cool project" 86 + ``` 87 + 88 + 4. **Clone a repository**: 89 + ```sh 90 + tangled repo clone username/reponame 91 + ``` 92 + 93 + ## Workspace Structure 94 + 95 + - `crates/tangled-cli` - CLI binary with clap-based argument parsing 96 + - `crates/tangled-config` - Configuration and session management (keyring-backed) 97 + - `crates/tangled-api` - XRPC client wrapper for AT Protocol and Tangled APIs 98 + - `crates/tangled-git` - Git operation helpers 99 + 100 + ## Configuration 101 + 102 + The CLI stores session credentials securely in your system keyring and configuration in: 103 + - Linux: `~/.config/tangled/config.toml` 104 + - macOS: `~/Library/Application Support/tangled/config.toml` 105 + - Windows: `%APPDATA%\tangled\config.toml` 106 + 107 + ### Environment Variables 108 + 109 + - `TANGLED_PDS_BASE` - Override the PDS base URL (default: `https://bsky.social`) 110 + - `TANGLED_API_BASE` - Override the Tangled API base URL (default: `https://tngl.sh`) 111 + - `TANGLED_SPINDLE_BASE` - Override the Spindle base URL (default: `wss://spindle.tangled.sh`) 28 112 29 - ## Next steps 113 + ## Examples 114 + 115 + ### Working with Issues 30 116 31 - - Implement `com.atproto.server.createSession` for auth 32 - - Wire repo list/create endpoints under `sh.tangled.*` 33 - - Persist sessions via keyring and load in CLI 34 - - Add output formatting (table/json) 117 + ```sh 118 + # Create an issue 119 + tangled issue create --repo myrepo --title "Bug: Fix login" --body "Description here" 120 + 121 + # List issues 122 + tangled issue list --repo myrepo 123 + 124 + # Comment on an issue 125 + tangled issue comment <issue-id> --body "I'll fix this" 126 + ``` 127 + 128 + ### Working with Pull Requests 129 + 130 + ```sh 131 + # Create a PR from a branch 132 + tangled pr create --repo myrepo --base main --head feature-branch --title "Add new feature" 133 + 134 + # Review a PR 135 + tangled pr review <pr-id> --approve --comment "LGTM!" 136 + 137 + # Merge a PR 138 + tangled pr merge <pr-id> 139 + ``` 35 140 141 + ### CI/CD with Spindle 142 + 143 + ```sh 144 + # Enable spindle for your repo 145 + tangled spindle config --repo myrepo --enable 146 + 147 + # List pipeline runs 148 + tangled spindle list --repo myrepo 149 + 150 + # Stream logs from a workflow 151 + tangled spindle logs knot:rkey:workflow-name --follow 152 + 153 + # Manage secrets 154 + tangled spindle secret add --repo myrepo --key API_KEY --value "secret-value" 155 + tangled spindle secret list --repo myrepo 156 + ``` 157 + 158 + ## Development 159 + 160 + Run tests: 161 + ```sh 162 + cargo test 163 + ``` 164 + 165 + Run with debug output: 166 + ```sh 167 + cargo run -p tangled-cli -- --verbose <command> 168 + ``` 169 + 170 + Format code: 171 + ```sh 172 + cargo fmt 173 + ``` 174 + 175 + Check for issues: 176 + ```sh 177 + cargo clippy 178 + ``` 179 + 180 + ## Contributing 181 + 182 + Contributions are welcome! Please feel free to submit issues or pull requests. 183 + 184 + ## License 185 + 186 + MIT OR Apache-2.0
+303 -7
docs/getting-started.md
··· 1 - # Getting Started 1 + # Getting Started with Tangled CLI 2 + 3 + This guide will help you get up and running with the Tangled CLI. 4 + 5 + ## Installation 6 + 7 + ### Prerequisites 8 + 9 + - Rust toolchain 1.70 or later 10 + - Git 11 + - A Bluesky/AT Protocol account 12 + 13 + ### Build from Source 14 + 15 + 1. Clone the repository: 16 + ```sh 17 + git clone https://tangled.org/tangled/tangled-cli 18 + cd tangled-cli 19 + ``` 20 + 21 + 2. Build the project: 22 + ```sh 23 + cargo build --release 24 + ``` 25 + 26 + 3. The binary will be available at `target/release/tangled-cli`. Optionally, add it to your PATH or create an alias: 27 + ```sh 28 + alias tangled='./target/release/tangled-cli' 29 + ``` 30 + 31 + ### Install from AUR (Arch Linux) 32 + 33 + If you're on Arch Linux, you can install from the AUR: 34 + 35 + ```sh 36 + yay -S tangled-cli-git 37 + ``` 38 + 39 + ## First Steps 40 + 41 + ### 1. Authenticate 42 + 43 + Login with your AT Protocol credentials (your Bluesky account): 44 + 45 + ```sh 46 + tangled auth login 47 + ``` 48 + 49 + You'll be prompted for your handle (e.g., `alice.bsky.social`) and password. If you're using a custom PDS, specify it with the `--pds` flag: 50 + 51 + ```sh 52 + tangled auth login --pds https://your-pds.example.com 53 + ``` 54 + 55 + Your credentials are stored securely in your system keyring. 56 + 57 + ### 2. Check Your Status 58 + 59 + Verify you're logged in: 60 + 61 + ```sh 62 + tangled auth status 63 + ``` 64 + 65 + ### 3. List Your Repositories 66 + 67 + See all your repositories: 68 + 69 + ```sh 70 + tangled repo list 71 + ``` 72 + 73 + Or view someone else's public repositories: 74 + 75 + ```sh 76 + tangled repo list --user alice.bsky.social 77 + ``` 78 + 79 + ### 4. Create a Repository 80 + 81 + Create a new repository on Tangled: 82 + 83 + ```sh 84 + tangled repo create my-project --description "My awesome project" 85 + ``` 86 + 87 + By default, repositories are created on the default knot (`tngl.sh`). You can specify a different knot: 88 + 89 + ```sh 90 + tangled repo create my-project --knot knot1.tangled.sh 91 + ``` 92 + 93 + ### 5. Clone a Repository 94 + 95 + Clone a repository to start working on it: 96 + 97 + ```sh 98 + tangled repo clone alice/my-project 99 + ``` 100 + 101 + This uses SSH by default. For HTTPS: 102 + 103 + ```sh 104 + tangled repo clone alice/my-project --https 105 + ``` 106 + 107 + ## Working with Issues 108 + 109 + ### Create an Issue 110 + 111 + ```sh 112 + tangled issue create --repo my-project --title "Add new feature" --body "We should add feature X" 113 + ``` 114 + 115 + ### List Issues 116 + 117 + ```sh 118 + tangled issue list --repo my-project 119 + ``` 2 120 3 - This project is a scaffold of a Tangled CLI in Rust. The commands are present as stubs and will be wired to XRPC endpoints iteratively. 121 + ### View Issue Details 4 122 5 - ## Build 123 + ```sh 124 + tangled issue show <issue-id> 125 + ``` 6 126 7 - Requires Rust toolchain and network access to fetch dependencies. 127 + ### Comment on an Issue 8 128 129 + ```sh 130 + tangled issue comment <issue-id> --body "I'm working on this!" 9 131 ``` 10 - cargo build 132 + 133 + ## Working with Pull Requests 134 + 135 + ### Create a Pull Request 136 + 137 + ```sh 138 + tangled pr create --repo my-project --base main --head feature-branch --title "Add feature X" 11 139 ``` 12 140 13 - ## Run 141 + The CLI will use `git format-patch` to create a patch from your branch. 14 142 143 + ### List Pull Requests 144 + 145 + ```sh 146 + tangled pr list --repo my-project 15 147 ``` 16 - cargo run -p tangled-cli -- --help 148 + 149 + ### Review a Pull Request 150 + 151 + ```sh 152 + tangled pr review <pr-id> --approve --comment "Looks good!" 17 153 ``` 18 154 155 + Or request changes: 156 + 157 + ```sh 158 + tangled pr review <pr-id> --request-changes --comment "Please fix the tests" 159 + ``` 160 + 161 + ### Merge a Pull Request 162 + 163 + ```sh 164 + tangled pr merge <pr-id> 165 + ``` 166 + 167 + ## CI/CD with Spindle 168 + 169 + Spindle is Tangled's integrated CI/CD system. 170 + 171 + ### Enable Spindle for Your Repository 172 + 173 + ```sh 174 + tangled spindle config --repo my-project --enable 175 + ``` 176 + 177 + Or use a custom spindle URL: 178 + 179 + ```sh 180 + tangled spindle config --repo my-project --url https://my-spindle.example.com 181 + ``` 182 + 183 + ### View Pipeline Runs 184 + 185 + ```sh 186 + tangled spindle list --repo my-project 187 + ``` 188 + 189 + ### Stream Workflow Logs 190 + 191 + ```sh 192 + tangled spindle logs knot:rkey:workflow-name 193 + ``` 194 + 195 + Add `--follow` to tail the logs in real-time. 196 + 197 + ### Manage Secrets 198 + 199 + Add secrets for your CI/CD workflows: 200 + 201 + ```sh 202 + tangled spindle secret add --repo my-project --key API_KEY --value "my-secret-value" 203 + ``` 204 + 205 + List secrets: 206 + 207 + ```sh 208 + tangled spindle secret list --repo my-project 209 + ``` 210 + 211 + Remove a secret: 212 + 213 + ```sh 214 + tangled spindle secret remove --repo my-project --key API_KEY 215 + ``` 216 + 217 + ## Advanced Topics 218 + 219 + ### Repository Migration 220 + 221 + Move a repository to a different knot: 222 + 223 + ```sh 224 + tangled knot migrate --repo my-project --to knot2.tangled.sh 225 + ``` 226 + 227 + This command must be run from within the repository's working directory, and your working tree must be clean and pushed. 228 + 229 + ### Output Formats 230 + 231 + Most commands support JSON output: 232 + 233 + ```sh 234 + tangled repo list --format json 235 + ``` 236 + 237 + ### Quiet and Verbose Modes 238 + 239 + Reduce output: 240 + 241 + ```sh 242 + tangled --quiet repo list 243 + ``` 244 + 245 + Increase verbosity for debugging: 246 + 247 + ```sh 248 + tangled --verbose repo list 249 + ``` 250 + 251 + ## Configuration 252 + 253 + The CLI stores configuration in: 254 + - Linux: `~/.config/tangled/config.toml` 255 + - macOS: `~/Library/Application Support/tangled/config.toml` 256 + - Windows: `%APPDATA%\tangled\config.toml` 257 + 258 + Session credentials are stored securely in your system keyring (GNOME Keyring, KWallet, macOS Keychain, or Windows Credential Manager). 259 + 260 + ### Environment Variables 261 + 262 + - `TANGLED_PDS_BASE` - Override the default PDS (default: `https://bsky.social`) 263 + - `TANGLED_API_BASE` - Override the Tangled API base (default: `https://tngl.sh`) 264 + - `TANGLED_SPINDLE_BASE` - Override the Spindle base (default: `wss://spindle.tangled.sh`) 265 + 266 + ## Troubleshooting 267 + 268 + ### Keyring Issues on Linux 269 + 270 + If you see keyring errors on Linux, ensure you have a secret service running: 271 + 272 + ```sh 273 + # For GNOME 274 + systemctl --user enable --now gnome-keyring-daemon 275 + 276 + # For KDE 277 + # KWallet should start automatically with Plasma 278 + ``` 279 + 280 + ### Authentication Failures 281 + 282 + If authentication fails with your custom PDS: 283 + 284 + ```sh 285 + tangled auth login --pds https://your-pds.example.com 286 + ``` 287 + 288 + Make sure the PDS URL is correct and accessible. 289 + 290 + ### "Repository not found" Errors 291 + 292 + Verify the repository exists and you have access: 293 + 294 + ```sh 295 + tangled repo info owner/reponame 296 + ``` 297 + 298 + ## Getting Help 299 + 300 + For command-specific help, use the `--help` flag: 301 + 302 + ```sh 303 + tangled --help 304 + tangled repo --help 305 + tangled repo create --help 306 + ``` 307 + 308 + ## Next Steps 309 + 310 + - Explore all available commands with `tangled --help` 311 + - Set up CI/CD workflows with `.tangled.yml` in your repository 312 + - Check out the main README for more examples and advanced usage 313 + 314 + Happy collaborating! 🧶