···
1
-
# Tangled CLI – Agent Handoff (Massive Context)
1
+
# Tangled CLI – Current Implementation Status
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.
5
-
Primary focus for this session: implement authentication (auth login/status/logout) and repository listing (repo list).
5
+
## Implementation Status
7
-
--------------------------------------------------------------------------------
7
+
### ✅ Fully Implemented
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
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`
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
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
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
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
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
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
39
-
- `tangled/crates/tangled-api/src/client.rs` → add XRPC helpers and implement `login_with_password` and `list_repos`.
41
-
--------------------------------------------------------------------------------
43
-
## 2) Current State Snapshot
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.
52
-
Goal: replace CLI stubs with real API calls for auth + repo list.
54
-
--------------------------------------------------------------------------------
56
-
## 3) Endpoints & Data Shapes
58
-
### 3.1 AT Protocol – Create Session
60
-
- Method: `com.atproto.server.createSession`
61
-
- HTTP: `POST /xrpc/com.atproto.server.createSession`
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:...`)
71
-
Persist to keyring using `SessionManager`.
73
-
### 3.2 Tangled – Repo List (tentative)
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, ... }] }`
81
-
If method name or response shape differs, adapt the client code; keep CLI interface stable.
83
-
--------------------------------------------------------------------------------
85
-
## 4) Implementation Plan
87
-
### 4.1 Add XRPC helpers and methods in `tangled-api`
89
-
File: `tangled/crates/tangled-api/src/client.rs`
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.
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: [...] }`.
105
-
Error handling: For non-2xx, read the response body, return `anyhow!("{status}: {body}")`.
61
+
## Architecture Overview
107
-
### 4.2 Wire CLI auth commands
63
+
### Workspace Structure
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)
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})`.
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
125
-
- `SessionManager::default().clear()?`.
126
-
- Print `Logged out` if something was cleared; otherwise `No session found` is acceptable.
79
+
#### Repository Creation Flow
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
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)
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}`
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?`.
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
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`
143
-
## 5) Code Snippets (Copy/Paste Friendly)
101
+
### Session Management
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
148
-
use anyhow::{anyhow, bail, Result};
149
-
use serde::{de::DeserializeOwned, Deserialize, Serialize};
150
-
use tangled_config::session::Session;
152
-
#[derive(Clone, Debug)]
153
-
pub struct TangledClient { pub(crate) base_url: String }
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") }
159
-
fn xrpc_url(&self, method: &str) -> String {
160
-
format!("{}/xrpc/{}", self.base_url.trim_end_matches('/'), method)
163
-
async fn post_json<TReq: Serialize, TRes: DeserializeOwned>(
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));
179
-
Ok(res.json::<TRes>().await?)
182
-
async fn get_json<TRes: DeserializeOwned>(
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(¶ms);
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));
198
-
Ok(res.json::<TRes>().await?)
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() })
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?;
111
+
access_jwt: String,
112
+
refresh_jwt: String,
115
+
pds: Option<String>, // PDS base URL
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 }
227
-
### 5.2 In `tangled-cli/src/commands/auth.rs`
119
+
## Working with tangled-core
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.
235
-
pub async fn run(_cli: &Cli, cmd: AuthCommand) -> Result<()> {
237
-
AuthCommand::Login(args) => login(args).await,
238
-
AuthCommand::Status => status().await,
239
-
AuthCommand::Logout => logout().await,
123
+
### Key Files to Check
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 {
251
-
println!("\x1b[93mIf you're on your own PDS, make sure to pass the --pds flag\x1b[0m");
255
-
SessionManager::default().save(&session)?;
256
-
println!("Logged in as '{}' ({})", session.handle, session.did);
125
+
- **Lexicons**: `../tangled-core/lexicons/**/*.json`
126
+
- Defines XRPC method schemas (NSIDs, parameters, responses)
127
+
- Example: `sh.tangled.repo.create`, `sh.tangled.repo.merge`
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"),
129
+
- **XRPC Routes**: `../tangled-core/knotserver/xrpc/xrpc.go`
130
+
- Shows which endpoints require ServiceAuth
131
+
- Maps NSIDs to handler functions
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"); }
133
+
- **API Handlers**: `../tangled-core/knotserver/xrpc/*.go`
134
+
- Implementation details for server-side operations
135
+
- Example: `create_repo.go`, `merge.go`
276
-
### 5.3 In `tangled-cli/src/commands/repo.rs`
137
+
### Useful Search Commands
279
-
use anyhow::{anyhow, Result};
280
-
use tangled_config::session::SessionManager;
281
-
use crate::cli::{Cli, RepoCommand, RepoListArgs};
140
+
# Find a specific NSID
141
+
rg -n "sh\.tangled\.repo\.create" ../tangled-core
283
-
pub async fn run(_cli: &Cli, cmd: RepoCommand) -> Result<()> {
284
-
match cmd { RepoCommand::List(args) => list(args).await, _ => Ok(println!("not implemented")) }
143
+
# List all lexicons
144
+
ls ../tangled-core/lexicons/repo
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); }
146
+
# Check ServiceAuth usage
147
+
rg -n "ServiceAuth|VerifyServiceAuth" ../tangled-core
300
-
--------------------------------------------------------------------------------
150
+
## Next Steps for Contributors
302
-
## 6) Configuration, Env Vars, and Security
152
+
### Priority: Implement `spindle run`
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:
309
-
--------------------------------------------------------------------------------
156
+
1. **Parse `.tangled.yml`** in the current repository to extract workflow definitions
157
+
- Look for workflow names, triggers, and manual trigger inputs
311
-
## 7) Testing Plan (MVP)
159
+
2. **Create pipeline record** on PDS via `com.atproto.repo.createRecord`:
161
+
collection: "sh.tangled.pipeline"
165
+
repo: { knot, did, repo, defaultBranch },
166
+
manual: { inputs: [...] }
168
+
workflows: [{ name, engine, clone, raw }]
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
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
319
-
## 8) Acceptance Criteria
178
+
5. **Support manual inputs** (if workflow defines them):
179
+
- Prompt for input values or accept via flags
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
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
334
-
## 9) Troubleshooting Notes
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.
340
-
--------------------------------------------------------------------------------
342
-
## 10) Non‑Goals for This Pass
189
+
### Documentation Tasks
344
-
- Refresh token flow, device code, OAuth.
345
-
- PRs, issues, knots, spindle implementation.
346
-
- Advanced formatting, paging, completions.
348
-
--------------------------------------------------------------------------------
191
+
- Add man pages for all commands
192
+
- Create video tutorials for common workflows
193
+
- Add troubleshooting guide for common issues
350
-
## 11) Future Follow‑ups
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.
356
-
--------------------------------------------------------------------------------
358
-
## 12) Quick Operator Commands
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>`
366
-
--------------------------------------------------------------------------------
195
+
## Development Workflow
368
-
End of handoff. Implement auth login and repo list as described, keeping changes focused and testable.
200
+
cargo build # Debug build
201
+
cargo build --release # Release build
371
-
--------------------------------------------------------------------------------
373
-
## 13) Tangled Core (../tangled-core) – Practical Notes
207
+
cargo run -p tangled-cli -- <command>
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.
213
+
cargo test # Run all tests
214
+
cargo test -- --nocapture # Show println output
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`).
393
-
### How To Search Quickly (rg examples)
220
+
cargo fmt # Format code
221
+
cargo clippy # Run linter
222
+
cargo clippy -- -W clippy::all # Strict linting
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
405
-
### Repo Listing (client-side pattern)
227
+
### Keyring Errors on Linux
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:
231
+
systemctl --user enable --now gnome-keyring-daemon
412
-
### Repo Creation (two-step flow)
234
+
### Invalid Token Errors
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
422
-
### Base URLs, DIDs, and Defaults
239
+
### Repository Not Found
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
429
-
### Common Errors and Fixes
245
+
### WebSocket Connection Failures
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
435
-
### Implementation Pointers (CLI)
251
+
## Additional Resources
438
-
- `com.atproto.server.createSession` against the PDS, save `{accessJwt, refreshJwt, did, handle, pds}` in keyring.
440
-
- Use session.handle by default; resolve to DID, then `com.atproto.repo.listRecords` on PDS.
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
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