commits
Three bugs fixed:
1. parse_target_repo_info and parse_record_id expected 4 parts in a split
AT-URI but valid AT-URIs only have 3 parts after stripping the at://
prefix (in both pr.rs and issue.rs).
2. PR merge sent requests to the default tngl.sh gateway instead of the
actual knot hosting the repo, causing service auth token verification
to fail.
3. merge_pull used post_json but the merge endpoint returns an empty body
on success, causing a JSON parse error.
The parse_target_repo_info function expected 4 parts in a split AT-URI
but valid AT-URIs only have 3 parts after stripping the at:// prefix.
This caused all PR merges to fail with 'Invalid target repo AT-URI'.
Fixed the length check from < 4 to < 3 and the rkey index from 3 to 2.
Add shared parse_repo_ref, parse_remote_url, resolve_repo_from_remote,
and require_repo utilities to util.rs. Commands now auto-detect the
repository from the git origin remote (for tangled.org/*.tangled.sh
hosts) when --repo is not provided, removing the need to specify it
explicitly inside a cloned tangled repo directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PDS doesn't have custom tangled lexicons registered, causing
400 "Lexicon not found" errors on record creation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The keyring crate has no default features, so platform backends must be
explicitly enabled. Only sync-secret-service (Linux) was configured,
causing credentials to be lost between process runs on macOS/Windows.
Also stop swallowing keyring errors in session load so misconfigurations
surface instead of silently appearing as "not logged in".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add automatic detection and merging of stacked pull requests with
comprehensive conflict checking and user confirmation. When merging
a PR that's part of a stack, the CLI now:
- Auto-detects stack membership via stack_id field
- Displays preview of all PRs to be merged (current + below)
- Checks cumulative patches for conflicts before merging
- Prompts user for confirmation before executing merge
- Provides clear error messages for conflicts
API changes:
- Add stack fields to Pull: stack_id, change_id, parent_change_id
- Add MergeCheckRequest/Response types for conflict detection
- Add merge_check() method to TangledClient
CLI changes:
- Refactor merge() to detect and handle stacked PRs
- Add helper functions for stack ordering and conflict checking
- Maintain backward compatibility for non-stacked PRs
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
Three bugs fixed:
1. parse_target_repo_info and parse_record_id expected 4 parts in a split
AT-URI but valid AT-URIs only have 3 parts after stripping the at://
prefix (in both pr.rs and issue.rs).
2. PR merge sent requests to the default tngl.sh gateway instead of the
actual knot hosting the repo, causing service auth token verification
to fail.
3. merge_pull used post_json but the merge endpoint returns an empty body
on success, causing a JSON parse error.
Add shared parse_repo_ref, parse_remote_url, resolve_repo_from_remote,
and require_repo utilities to util.rs. Commands now auto-detect the
repository from the git origin remote (for tangled.org/*.tangled.sh
hosts) when --repo is not provided, removing the need to specify it
explicitly inside a cloned tangled repo directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The keyring crate has no default features, so platform backends must be
explicitly enabled. Only sync-secret-service (Linux) was configured,
causing credentials to be lost between process runs on macOS/Windows.
Also stop swallowing keyring errors in session load so misconfigurations
surface instead of silently appearing as "not logged in".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add automatic detection and merging of stacked pull requests with
comprehensive conflict checking and user confirmation. When merging
a PR that's part of a stack, the CLI now:
- Auto-detects stack membership via stack_id field
- Displays preview of all PRs to be merged (current + below)
- Checks cumulative patches for conflicts before merging
- Prompts user for confirmation before executing merge
- Provides clear error messages for conflicts
API changes:
- Add stack fields to Pull: stack_id, change_id, parent_change_id
- Add MergeCheckRequest/Response types for conflict detection
- Add merge_check() method to TangledClient
CLI changes:
- Refactor merge() to detect and handle stacked PRs
- Add helper functions for stack ordering and conflict checking
- Maintain backward compatibility for non-stacked PRs
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)