commits
Implements client-side support for the new listPulls XRPC endpoint that
allows querying all PRs (from any author) targeting a specific repository.
Changes:
- Add list_repo_pulls() method to query PRs for a specific repo
- Add RepoPull struct for repo PR listing response
- Update pr list command to support --repo flag for listing all PRs
- Add PullSource struct to support branch-based PRs
- Make Pull.patch optional for compatibility with branch-based PRs
- Prevent merging branch-based PRs via CLI (requires web interface)
- Add TODO.md to track remaining technical debt
The pr list command now supports two modes:
- Without --repo: shows only PRs created by the authenticated user (legacy)
- With --repo: shows all PRs targeting the specified repository (new)
Support reading secret values from stdin or files to handle
multiline content like SSH keys, certificates, and config files.
New value patterns:
- `--value -` reads from stdin
- `--value @<path>` reads from file
- `--value <text>` uses literal value (backward compatible)
File path handling:
- Supports tilde expansion for home directory (~/)
- Provides clear error messages if file cannot be read
Examples:
# From stdin
cat ~/.ssh/id_ed25519 | tangled spindle secret add \
--repo myrepo --key SSH_KEY --value -
# From file
tangled spindle secret add --repo myrepo \
--key SSH_KEY --value @~/.ssh/id_ed25519
# Literal value (existing behavior)
tangled spindle secret add --repo myrepo \
--key API_KEY --value "my-secret-key"
Fixes issue where multiline values were split into multiple
arguments by the shell, causing clap parsing errors.
Extend automatic OAuth token refresh to all command modules:
Updated commands:
- repo: list, create, clone, info, delete, star, unstar (7 functions)
- issue: list, create, show, edit, comment (5 functions)
- pr: list, create, show, review, merge (5 functions)
- knot: migrate (1 function)
Changes per module:
- Remove SessionManager import (no longer needed)
- Replace SessionManager::load() with util::load_session_with_refresh()
- Handles both pattern variations:
- match mgr.load()? { Some(s) => s, None => ... }
- mgr.load()?.ok_or_else(...)
All authenticated commands now automatically refresh expired tokens
without requiring manual re-authentication, providing a seamless
user experience across the entire CLI.
Add automatic token refresh to gracefully handle expired access tokens:
Token refresh implementation:
- Add refresh_session() method to TangledClient
- Calls com.atproto.server.refreshSession with refresh token
- Returns new Session with updated access and refresh tokens
- Create util module with session management helpers
- load_session(): Basic session loading
- refresh_session(): Refresh using refresh token and save
- load_session_with_refresh(): Smart loading with auto-refresh
Auto-refresh strategy:
- Check session age on every command invocation
- If session is older than 30 minutes, proactively refresh
- Falls back to old session if refresh fails (might still work)
- Preserves PDS URL and updates created_at timestamp
Update all spindle commands to use auto-refresh:
- Replace SessionManager::load() with util::load_session_with_refresh()
- Applies to: config, list, logs, and all secret operations
- Remove now-unused SessionManager imports
Dependencies:
- Add chrono to tangled-cli for timestamp comparison
Fixes ExpiredToken errors that occurred when access tokens expired
after initial login, requiring manual re-authentication.
ServiceAuth token fixes:
- Reduce expiration from 600 to 60 seconds for method-less tokens
per AT Protocol spec (fixes BadExpiration error)
Spindle URL handling:
- Support URLs with or without protocol prefix (e.g., "spindle.vitorpy.com")
- Add protocol (https://) automatically in xrpc_url() when missing
- Extract host correctly for ServiceAuth audience DID in both cases
- Read spindle URL from repo's spindle field for secret operations
- Fall back to TANGLED_SPINDLE_BASE env var or default
Secret operations fixes:
- Add new post() method for endpoints that return empty responses
- Update add_repo_secret() and remove_repo_secret() to use post()
instead of post_json() (fixes JSON parsing error on empty response)
- All secret operations now connect to correct spindle instance
Other improvements:
- Add spindle field to RepoRecord struct
- Display spindle URL in repo info output
- Ensure all three secret operations (list, add, remove) use the
repo's configured spindle instance
- 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.
- Add spindle field to Repository struct
- Implement update_repo_spindle() to enable/disable CI for repos
- Implement list_pipelines() to fetch pipeline records from PDS
- Add Pipeline, TriggerMetadata, TriggerRepo, and Workflow structs
- Implement spindle config command:
- Enable/disable spindle for a repository
- Support custom spindle URL via --url flag
- Update repo record's spindle field
- Implement spindle list command:
- List all pipeline runs for a repository
- Display trigger kind, repo, and workflows
- Implement spindle logs command:
- Stream logs from a workflow execution via WebSocket
- Support both full job_id format (knot:rkey:name) and short format (name)
- Add --lines and --follow flags for log control
- Add WebSocket dependencies: tokio-tungstenite and futures-util
- Keep spindle run as stub (to be implemented later)
- Add merge_pull() to API client using sh.tangled.repo.merge
- Implement 'pr merge' CLI command with ServiceAuth flow
- Remove stub knot commands (list, add, verify, set-default, remove)
- Keep only 'knot migrate' which is fully implemented
Implements client-side support for the new listPulls XRPC endpoint that
allows querying all PRs (from any author) targeting a specific repository.
Changes:
- Add list_repo_pulls() method to query PRs for a specific repo
- Add RepoPull struct for repo PR listing response
- Update pr list command to support --repo flag for listing all PRs
- Add PullSource struct to support branch-based PRs
- Make Pull.patch optional for compatibility with branch-based PRs
- Prevent merging branch-based PRs via CLI (requires web interface)
- Add TODO.md to track remaining technical debt
The pr list command now supports two modes:
- Without --repo: shows only PRs created by the authenticated user (legacy)
- With --repo: shows all PRs targeting the specified repository (new)
Support reading secret values from stdin or files to handle
multiline content like SSH keys, certificates, and config files.
New value patterns:
- `--value -` reads from stdin
- `--value @<path>` reads from file
- `--value <text>` uses literal value (backward compatible)
File path handling:
- Supports tilde expansion for home directory (~/)
- Provides clear error messages if file cannot be read
Examples:
# From stdin
cat ~/.ssh/id_ed25519 | tangled spindle secret add \
--repo myrepo --key SSH_KEY --value -
# From file
tangled spindle secret add --repo myrepo \
--key SSH_KEY --value @~/.ssh/id_ed25519
# Literal value (existing behavior)
tangled spindle secret add --repo myrepo \
--key API_KEY --value "my-secret-key"
Fixes issue where multiline values were split into multiple
arguments by the shell, causing clap parsing errors.
Extend automatic OAuth token refresh to all command modules:
Updated commands:
- repo: list, create, clone, info, delete, star, unstar (7 functions)
- issue: list, create, show, edit, comment (5 functions)
- pr: list, create, show, review, merge (5 functions)
- knot: migrate (1 function)
Changes per module:
- Remove SessionManager import (no longer needed)
- Replace SessionManager::load() with util::load_session_with_refresh()
- Handles both pattern variations:
- match mgr.load()? { Some(s) => s, None => ... }
- mgr.load()?.ok_or_else(...)
All authenticated commands now automatically refresh expired tokens
without requiring manual re-authentication, providing a seamless
user experience across the entire CLI.
Add automatic token refresh to gracefully handle expired access tokens:
Token refresh implementation:
- Add refresh_session() method to TangledClient
- Calls com.atproto.server.refreshSession with refresh token
- Returns new Session with updated access and refresh tokens
- Create util module with session management helpers
- load_session(): Basic session loading
- refresh_session(): Refresh using refresh token and save
- load_session_with_refresh(): Smart loading with auto-refresh
Auto-refresh strategy:
- Check session age on every command invocation
- If session is older than 30 minutes, proactively refresh
- Falls back to old session if refresh fails (might still work)
- Preserves PDS URL and updates created_at timestamp
Update all spindle commands to use auto-refresh:
- Replace SessionManager::load() with util::load_session_with_refresh()
- Applies to: config, list, logs, and all secret operations
- Remove now-unused SessionManager imports
Dependencies:
- Add chrono to tangled-cli for timestamp comparison
Fixes ExpiredToken errors that occurred when access tokens expired
after initial login, requiring manual re-authentication.
ServiceAuth token fixes:
- Reduce expiration from 600 to 60 seconds for method-less tokens
per AT Protocol spec (fixes BadExpiration error)
Spindle URL handling:
- Support URLs with or without protocol prefix (e.g., "spindle.vitorpy.com")
- Add protocol (https://) automatically in xrpc_url() when missing
- Extract host correctly for ServiceAuth audience DID in both cases
- Read spindle URL from repo's spindle field for secret operations
- Fall back to TANGLED_SPINDLE_BASE env var or default
Secret operations fixes:
- Add new post() method for endpoints that return empty responses
- Update add_repo_secret() and remove_repo_secret() to use post()
instead of post_json() (fixes JSON parsing error on empty response)
- All secret operations now connect to correct spindle instance
Other improvements:
- Add spindle field to RepoRecord struct
- Display spindle URL in repo info output
- Ensure all three secret operations (list, add, remove) use the
repo's configured spindle instance
- 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.
- Add spindle field to Repository struct
- Implement update_repo_spindle() to enable/disable CI for repos
- Implement list_pipelines() to fetch pipeline records from PDS
- Add Pipeline, TriggerMetadata, TriggerRepo, and Workflow structs
- Implement spindle config command:
- Enable/disable spindle for a repository
- Support custom spindle URL via --url flag
- Update repo record's spindle field
- Implement spindle list command:
- List all pipeline runs for a repository
- Display trigger kind, repo, and workflows
- Implement spindle logs command:
- Stream logs from a workflow execution via WebSocket
- Support both full job_id format (knot:rkey:name) and short format (name)
- Add --lines and --follow flags for log control
- Add WebSocket dependencies: tokio-tungstenite and futures-util
- Keep spindle run as stub (to be implemented later)