commits
Issue list now displays repo as "dunkirk.sh/thistle" instead of
"at://did:plc:.../sh.tangled.repo/rkey" for better readability.
Uses a cache to avoid repeated API calls for the same repo, making
the command fast even with many issues from the same repository.
Implementation:
- Add get_repo_by_rkey() to fetch repo record by DID and rkey
- Add resolve_did_to_handle() to convert DID to handle
- Cache repo AT-URI to formatted name mappings
- Parse AT-URI to extract DID and rkey, then resolve to handle/name
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Add support for filtering issues by state (open/closed) using the
--state flag on `tangled issue list`.
Implementation:
- Add list_issue_states() API method to fetch state records
- Add IssueState struct for sh.tangled.repo.issue.state records
- Filter issues client-side by fetching state records and matching
- Default to "open" state for issues without explicit state records
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Some issue records in the wild include optional fields that weren't
in the Issue struct, causing deserialization failures:
- createdAt: not present in all records
- $type: AT Protocol record type field
- owner: legacy field on some issues
- issueId: numeric ID on some issues
- cid: content identifier in listRecords response
This makes all these fields optional so the CLI can deserialize
any issue record regardless of which optional fields are present.
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
The --profile flag was defined in the CLI but never implemented or used.
馃挊 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Set validate=false for all Tangled custom collections (sh.tangled.*)
when creating records via com.atproto.repo.createRecord. PDSs don't
have the Tangled lexicon schemas, so validation would fail with
"Lexicon not found" errors.
Fixes issue creation, PR creation, comments, stars, and issue state changes.
馃挊 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
- Use apple-native keyring feature on macOS instead of linux-only sync-secret-service
- Use windows-native keyring feature on Windows
- Move config directory to ~/.config/tangled on all platforms for consistency
- Improve keychain error messages
馃挊 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
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
Issue list now displays repo as "dunkirk.sh/thistle" instead of
"at://did:plc:.../sh.tangled.repo/rkey" for better readability.
Uses a cache to avoid repeated API calls for the same repo, making
the command fast even with many issues from the same repository.
Implementation:
- Add get_repo_by_rkey() to fetch repo record by DID and rkey
- Add resolve_did_to_handle() to convert DID to handle
- Cache repo AT-URI to formatted name mappings
- Parse AT-URI to extract DID and rkey, then resolve to handle/name
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Add support for filtering issues by state (open/closed) using the
--state flag on `tangled issue list`.
Implementation:
- Add list_issue_states() API method to fetch state records
- Add IssueState struct for sh.tangled.repo.issue.state records
- Filter issues client-side by fetching state records and matching
- Default to "open" state for issues without explicit state records
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Some issue records in the wild include optional fields that weren't
in the Issue struct, causing deserialization failures:
- createdAt: not present in all records
- $type: AT Protocol record type field
- owner: legacy field on some issues
- issueId: numeric ID on some issues
- cid: content identifier in listRecords response
This makes all these fields optional so the CLI can deserialize
any issue record regardless of which optional fields are present.
馃挅 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
Set validate=false for all Tangled custom collections (sh.tangled.*)
when creating records via com.atproto.repo.createRecord. PDSs don't
have the Tangled lexicon schemas, so validation would fail with
"Lexicon not found" errors.
Fixes issue creation, PR creation, comments, stars, and issue state changes.
馃挊 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
- Use apple-native keyring feature on macOS instead of linux-only sync-secret-service
- Use windows-native keyring feature on Windows
- Move config directory to ~/.config/tangled on all platforms for consistency
- Improve keychain error messages
馃挊 Generated with Crush
Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>
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)