Commits
Route writes through a non-logging PTY without argv secrets, chunk long encoded values, and redact malformed credential failures so OAuth login cannot expose user input.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Route credential access through Apple’s stable security helper so Homebrew Node updates no longer invalidate Keychain authorization, while keeping secrets encrypted and out of process arguments.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Make repository resolution, JSON contracts, destructive workflows, and Spindle CI reliable enough for humans and agents, then add reproducible package and binary release paths.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Rebase onto main kept the write-path fix (resolveRepoDid) and main's
local-scan aliases for legacy AT-URI `.repo` values. Wire label.ts the
same way, and update issue tests so listRecords mocks and listIssues
expectations match the merged API.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cameron maintains this as a separate, independent fork and does not want
it tied to the upstream project in any way.
- package.json: name -> 'tangled-cli' (unscoped, no npm-registry claim),
marked private (drops publishConfig entirely), repository.url points
at cameron.stream/tangled-cli, author updated. LICENSE keeps Mark
Bennett's original MIT copyright (required) and adds a second line for
Cameron's changes in this fork.
- Removed the 'source' field from Cameron's own sh.tangled.repo record
for this repo (rkey 'tangled-cli') via putRecord — this is the field
Tangled uses to track a fork's upstream parent; it pointed at
markbennett.ca's original repo. Formal disconnect on the Tangled side,
not just cosmetic.
- resolveRepoDid: fixed a real bug this surfaced — matching a repo by
name only fails for records (like this one, and misaligned) that never
set a 'name' field and are identified by their rkey instead. Now falls
back to matching by rkey. Without this, 'tang pr create' inside this
repo's own directory failed outright ('Repository tangled-cli not
found').
- Doc-comment example handles ('markbennett.ca') swapped for a neutral
placeholder in src/ and tests/; only test fixture data changed, no
behavior.
Also cleaned up on this machine (not part of this commit): the global
tang binary no longer resolves through any @markbennett-scoped install —
removed the bun-linked symlink, uninstalled a separate npm-global copy
that had been shadowing it under an nvm Node version, and cleared bun's
download cache for the package. A single symlink at ~/.bun/bin/tang now
points directly at this repo's build.
Checks: bun run typecheck, bun run test (330 passing), bun run lint:fix,
bun run build all clean.
Refreshed the vendored lexicons from tangled.org/tangled.org/core (bun run
update-lexicons && bun run codegen). This turned up two real bugs, both
of which meant records created by this CLI were invisible on tangled.org
even though they wrote successfully and this CLI's own read paths (via
constellation) could see them fine:
- sh.tangled.repo.pull's target.repo/source.repo and sh.tangled.repo.issue's
repo are all lexicon format "did" — a bare DID, never an AT-URI. Every
record this CLI wrote populated them with an AT-URI instead (built via
the old buildRepoAtUri, which for owner/name remotes resolved a full
at://did/sh.tangled.repo/rkey and for the common Tangled stable-DID-remote
case fetched https://tangled.org/<did> and regex-scraped an AT-URI out of
the page HTML — fragile, and observed to sometimes pick up an unrelated
AT-URI embedded on the page, e.g. the viewer's own account).
- sh.tangled.repo.pull replaced its flat patchBlob field with a required
rounds: [{createdAt, patchBlob}] array at some point; this CLI still
wrote the old flat shape.
Fixes, in src/utils/at-uri.ts and src/lib/{pulls,issues}-api.ts:
- buildRepoAtUri -> resolveRepoDid: for stable-DID remotes, the DID in the
remote URL already *is* the repo's own DID, returned directly with no
network call; for owner/name remotes, resolves owner to a DID, lists
their sh.tangled.repo records, and prefers that record's own repoDid
field ("DID of the repo itself, if assigned") over the owner DID.
Deletes the HTML-scraping fallback entirely.
- createPull writes rounds: [{createdAt, patchBlob}] and target.repo as
the resolved DID; also sets target.repoDid (not in the published
lexicon, but present on every appview-indexed record observed in the
wild — the appview's ingestion pipeline appears to key off it).
- source.repo/source.sha dropped from what gets written: source.repo is
for cross-repo (fork) pulls this CLI doesn't create; sha was never in
the lexicon and wasn't read back anywhere.
- issues-api.ts's repo field gets the same bare-DID fix.
Also adds `tang pr delete <pr-id>`: there was no way to remove a botched
pull request record via the CLI at all, which is exactly what surfaced
this bug (bad records had to be deleted and recreated to verify the fix).
Author-only, confirms unless --yes.
Checks: bun run typecheck, bun run test (329 passing), bun run lint:fix,
bun run build all clean.
A correctly-linked sibling was hiding older bare-DID issues from
tang issue list, so open decision-made issues looked closed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Agents need defs/list/add/remove for decision-required and
decision-made. Also fall back to a local PDS scan when constellation
misses issues that store a bare repo DID in .repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add resolveStableRepoDidToAtUri() to resolve bare repo DID remotes
(git@tangled.org:did:plc:...) to canonical sh.tangled.repo AT-URIs
by fetching the Tangled page and extracting the repo AT-URI from markup.
Wire buildRepoAtUri() to use this resolution when owner===name (the
bare DID case) instead of trying to list sh.tangled.repo records
under the repo DID as if it were an owner account.
# Conflicts:
# README.md
# package-lock.json
Document Cameron fork installation, implemented issue and pull request commands, repo-context behavior, JSON output, and development checks so the README matches the current CLI instead of inherited upstream plans.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Expose create, list, and view commands for Tangled pull requests so the CLI can manage branch patches from the terminal while keeping the renamed tang package shape intact.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Accept bare Tangled repo DID Git remotes so repositories cloned or pushed with git@tangled.org:<repo-did> resolve in tang context.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Switch the development workflow and CI to Bun, add AT Protocol OAuth as the default login flow, and keep app-password login as a fallback.
Written by Cameron ◯ Letta Code
getIssueState() now queries constellation.microcosm.blue for all state
records that reference an issue, fetches each one via getRecord, and
sorts by rkey (TID) to find the most recent. This allows collaborators
to close/reopen issues from their own PDS, fixing the old approach that
only ever saw the issue author's state records.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
listIssues() now queries constellation.microcosm.blue for all issue
records referencing a repo, then fetches each one via getRecord. This
fixes multi-collaborator scenarios where team members host issues on
different PDSs — the old listRecords approach only saw the repo owner's
issues.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Introduces getBacklinks() which queries constellation.microcosm.blue to
find AT Protocol records that reference a given URI, across all PDSs.
This is the foundation for fixing multi-collaborator issue queries.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The current-session-metadata keychain entry was getting wiped by transient
errors (e.g. network failure on wake) in the resumeSession() catch block,
forcing users to re-authenticate unnecessarily.
Changes:
- Store session metadata in ~/.config/tangled/session.json (not keychain)
so it survives sleep/wake cycles and keychain lock events
- Don't clear metadata on transient agent.resumeSession() failures — only
clear when loadSession() definitively returns null (no stored credentials)
- Update session.test.ts to mock node:fs/promises with in-memory storage
- Update api-client.test.ts to reflect new non-clearing behavior
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Export KeychainAccessError from session.ts; thrown when getPassword() fails
(platform error like locked keychain), not when an entry is missing (undefined)
- resumeSession() now rethrows KeychainAccessError without clearing metadata,
so temporarily locked keychains no longer wipe stored credentials
- Add ensureAuthenticated(client) to auth-helpers.ts: on KeychainAccessError,
attempts to unlock the keychain via `security unlock-keychain` (macOS only),
retries once, then falls back to a clear error message with manual instructions
- Replace 7 repeated inline auth-check blocks in issue.ts with ensureAuthenticated()
- Add/update tests for KeychainAccessError propagation and ensureAuthenticated behavior
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
outputJson previously required Record<string, unknown>, which forced
callers to either use untyped objects or add a catch-all index signature
to typed interfaces like IssueData. Using a generic T extends object
allows any typed object to be passed while keeping the Record<string,
unknown> cast scoped to pickFields where dynamic key access is actually
needed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
All issue commands now return the full common field set (number, title,
body, state, author, createdAt, uri, cid) in --json mode:
- create: adds state:'open' (always open on creation)
- view: adds number via getCompleteIssueData (replaces separate getIssue
+ getIssueState calls)
- edit: adds number + state via parallel resolveSequentialNumber +
getIssueState after updateIssue (avoids re-fetching the updated record)
- close/reopen: adds body, author, createdAt via getCompleteIssueData with
stateOverride; replaces separate getIssue + resolveSequentialNumber calls
- delete: adds body, author, createdAt, state via getCompleteIssueData;
replaces scattered local variables
Update command tests to mock getCompleteIssueData and assert on the full
field set.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Move resolveSequentialNumber out of the commands layer into issues-api.ts
where it belongs alongside the other issue data functions. Add IssueData
interface as the canonical JSON shape for a single issue, and
getCompleteIssueData which fetches issue record, sequential number, and
state in one call (with optional state override for mutation commands that
already know the new state). Add tests for both new exports.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Addresses issues #8, #13, and #14:
- issue create now shows #N (sequential number) instead of the AT Protocol
TID rkey in human output, and includes `number` in --json output
- issue close, reopen, and delete now show the issue title in success output
- issue close, reopen, and delete now support --json with field filtering,
returning number, title, uri, state (close/reopen), and cid
- New resolveSequentialNumber() helper extracts number from displayId fast
path or falls back to listIssues lookup for rkey inputs
- 16 new tests added (281 total)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds GitHub CLI-style --json [fields] option to issue list, view,
create, and edit commands. When --json is passed, outputs machine-
readable JSON instead of human-readable text. An optional comma-
separated field list filters the output to only the requested fields.
Adds outputJson() utility to src/utils/formatting.ts for reuse
across future commands.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces the skipped stdin placeholder with three real tests covering:
- reading a single data chunk from stdin
- concatenating multiple chunks
- propagating stdin error events
process.stdin is non-configurable on the real process object, so the
tests mock the entire node:process module using a Proxy that intercepts
the stdin property and returns a local EventEmitter fixture while
forwarding all other property accesses to the real module.
The readFromStdin Promise constructor registers handlers synchronously,
so events can be emitted immediately after readBodyInput returns its
pending Promise without needing setImmediate.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements the remaining issue management subcommands with full
lifecycle support:
- view: displays issue details including state (open/closed), author,
creation date, body; supports numeric (#1) and rkey identifiers
- edit: updates title and/or body via --title/-t, --body/-b, --body-file/-F
- close/reopen: toggle issue state; idempotent by design
- delete: permanently removes an issue; requires confirmation unless
--force is passed; cancel path uses process.exit(0) outside try/catch
to correctly propagate clean exit
Also:
- resolveIssueUri helper: on-demand numeric resolution by sorting issues
by createdAt (oldest = #1), no persistent cache needed
- list command updated to show numbered issues with state badges
- formatIssueState added to src/utils/formatting.ts
- All existing list tests updated for new output format; new test suites
added for all five commands covering auth, context, numeric/rkey
resolution, option validation, and confirmation flow
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extends the issues API layer with two new functions needed for full
issue lifecycle management:
- getIssueState: queries sh.tangled.repo.issue.state records to
determine open/closed status, defaulting to 'open' when no records
exist. Filters by issue URI and uses the most recent state record.
- reopenIssue: mirrors closeIssue by creating a state record with
state 'sh.tangled.repo.issue.state.open'
Includes tests covering state filtering, multi-record precedence,
unauthenticated error paths, and correct record creation.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enable erasableSyntaxOnly and isolatedDeclarations for:
- Faster builds with parallel declaration generation
- Better compatibility with fast transpilers (esbuild, SWC)
- More explicit public API types
Add explicit Zod type annotations to validation schemas
to satisfy isolatedDeclarations requirements.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix issue where test files were excluded from TypeScript compilation,
causing VS Code to catch type errors that npm run typecheck missed.
Changes:
- Add "tests/**/*" to include array in tsconfig.json
- Remove "tests/**/*" from exclude array
- Tests are now type-checked by both build and typecheck scripts
- Tests are emitted to dist/tests/ (harmless locally, can exclude from npm publish later)
This ensures type errors in tests are caught by:
- npm run typecheck (local development)
- npm run build (before commits)
- CI pipeline (prevents broken tests from being merged)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add ability to list all issues for the current repository.
Features:
- Lists issues with rkey (issue number) and title
- Shows creation date with human-readable formatting
- Supports --limit option (1-100, default 50)
- Displays "No issues found" message when list is empty
New utilities:
- Add formatDate() in src/utils/formatting.ts
- Converts dates to human-readable format (today, yesterday, X days/weeks/months ago, or locale date)
- Comprehensive test coverage with 9 tests
Implementation:
- Add createListCommand() with limit validation
- Import formatDate from utils/formatting.ts
- Add 9 comprehensive command tests
Tests:
- List issues successfully
- Custom limit handling
- Empty list handling
- Authentication required
- Context required
- Invalid limit validation (too low/high/non-numeric)
- API error handling
- Date formatting tests (9 tests covering all ranges)
All 232 tests passing ✅
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Issue records were not appearing on tangled.org because the repo field
used an incorrect AT-URI format with the repository name as the rkey
instead of the actual record key.
Changes:
- Update buildRepoAtUri() to query PDS for sh.tangled.repo records
- Find matching repository by name field in record value
- Return record's URI which contains the correct rkey
- Add comprehensive tests for new query-based resolution
Before: at://did:plc:xxx/sh.tangled.repo/tangled-cli
After: at://did:plc:xxx/sh.tangled.repo/3mef23waqwq22
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Import and register createIssueCommand in main CLI index
- Makes 'tangled issue create' command available to users
The issue command is now fully functional and ready for testing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Define IssueRecord interface locally in issues-api.ts
- Based on sh.tangled.repo.issue lexicon schema
- Includes required index signature for AT Protocol compatibility
- Documents lexicon source for reference
- Avoids TypeScript compilation issues with generated lexicon types
- Maintains full type safety while working around codegen limitations
This approach is recommended in src/lexicon/README.md and provides
a stable interface that won't break when lexicons are regenerated.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update tsconfig.json:
- Exclude src/lexicon/** from compilation (generated code has compatibility issues)
- Exclude tests/**/* from build output (tests are type-checked separately)
- Only include src/**/* in build
- Regenerate lexicons with post-processing fixes applied:
- All multiformats/cid imports now use main package export
- All relative imports now have .js extensions
- Add comprehensive src/lexicon/README.md documenting:
- Code generation workflow
- Dependencies and multiformats usage
- Recommended usage patterns (custom interfaces)
- Known issues and workarounds
- Troubleshooting guide
This allows TypeScript to compile cleanly while still providing type
information from generated lexicons for IDE support.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add multiformats@9.9.0 as direct dependency for CID type support
- Create scripts/fix-lexicon-imports.js to post-process generated lexicons:
- Adds .js extensions to relative imports for ES module compatibility
- Rewrites 'multiformats/cid' imports to 'multiformats' main export
(fixes TypeScript NodeNext module resolution issues)
- Update codegen script to run post-processor automatically
This resolves TypeScript compilation errors with generated AT Protocol
lexicon code while maintaining strict type checking in application code.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Auto-format source files with Biome formatter (import organization,
spacing, etc.). Generated lexicon files are now excluded from
formatting.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add src/lexicon to biome ignore list since these files are
auto-generated by @atproto/lex-cli and should not be linted
or formatted.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Route credential access through Apple’s stable security helper so Homebrew Node updates no longer invalidate Keychain authorization, while keeping secrets encrypted and out of process arguments.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Cameron maintains this as a separate, independent fork and does not want
it tied to the upstream project in any way.
- package.json: name -> 'tangled-cli' (unscoped, no npm-registry claim),
marked private (drops publishConfig entirely), repository.url points
at cameron.stream/tangled-cli, author updated. LICENSE keeps Mark
Bennett's original MIT copyright (required) and adds a second line for
Cameron's changes in this fork.
- Removed the 'source' field from Cameron's own sh.tangled.repo record
for this repo (rkey 'tangled-cli') via putRecord — this is the field
Tangled uses to track a fork's upstream parent; it pointed at
markbennett.ca's original repo. Formal disconnect on the Tangled side,
not just cosmetic.
- resolveRepoDid: fixed a real bug this surfaced — matching a repo by
name only fails for records (like this one, and misaligned) that never
set a 'name' field and are identified by their rkey instead. Now falls
back to matching by rkey. Without this, 'tang pr create' inside this
repo's own directory failed outright ('Repository tangled-cli not
found').
- Doc-comment example handles ('markbennett.ca') swapped for a neutral
placeholder in src/ and tests/; only test fixture data changed, no
behavior.
Also cleaned up on this machine (not part of this commit): the global
tang binary no longer resolves through any @markbennett-scoped install —
removed the bun-linked symlink, uninstalled a separate npm-global copy
that had been shadowing it under an nvm Node version, and cleared bun's
download cache for the package. A single symlink at ~/.bun/bin/tang now
points directly at this repo's build.
Checks: bun run typecheck, bun run test (330 passing), bun run lint:fix,
bun run build all clean.
Refreshed the vendored lexicons from tangled.org/tangled.org/core (bun run
update-lexicons && bun run codegen). This turned up two real bugs, both
of which meant records created by this CLI were invisible on tangled.org
even though they wrote successfully and this CLI's own read paths (via
constellation) could see them fine:
- sh.tangled.repo.pull's target.repo/source.repo and sh.tangled.repo.issue's
repo are all lexicon format "did" — a bare DID, never an AT-URI. Every
record this CLI wrote populated them with an AT-URI instead (built via
the old buildRepoAtUri, which for owner/name remotes resolved a full
at://did/sh.tangled.repo/rkey and for the common Tangled stable-DID-remote
case fetched https://tangled.org/<did> and regex-scraped an AT-URI out of
the page HTML — fragile, and observed to sometimes pick up an unrelated
AT-URI embedded on the page, e.g. the viewer's own account).
- sh.tangled.repo.pull replaced its flat patchBlob field with a required
rounds: [{createdAt, patchBlob}] array at some point; this CLI still
wrote the old flat shape.
Fixes, in src/utils/at-uri.ts and src/lib/{pulls,issues}-api.ts:
- buildRepoAtUri -> resolveRepoDid: for stable-DID remotes, the DID in the
remote URL already *is* the repo's own DID, returned directly with no
network call; for owner/name remotes, resolves owner to a DID, lists
their sh.tangled.repo records, and prefers that record's own repoDid
field ("DID of the repo itself, if assigned") over the owner DID.
Deletes the HTML-scraping fallback entirely.
- createPull writes rounds: [{createdAt, patchBlob}] and target.repo as
the resolved DID; also sets target.repoDid (not in the published
lexicon, but present on every appview-indexed record observed in the
wild — the appview's ingestion pipeline appears to key off it).
- source.repo/source.sha dropped from what gets written: source.repo is
for cross-repo (fork) pulls this CLI doesn't create; sha was never in
the lexicon and wasn't read back anywhere.
- issues-api.ts's repo field gets the same bare-DID fix.
Also adds `tang pr delete <pr-id>`: there was no way to remove a botched
pull request record via the CLI at all, which is exactly what surfaced
this bug (bad records had to be deleted and recreated to verify the fix).
Author-only, confirms unless --yes.
Checks: bun run typecheck, bun run test (329 passing), bun run lint:fix,
bun run build all clean.
Add resolveStableRepoDidToAtUri() to resolve bare repo DID remotes
(git@tangled.org:did:plc:...) to canonical sh.tangled.repo AT-URIs
by fetching the Tangled page and extracting the repo AT-URI from markup.
Wire buildRepoAtUri() to use this resolution when owner===name (the
bare DID case) instead of trying to list sh.tangled.repo records
under the repo DID as if it were an owner account.
Document Cameron fork installation, implemented issue and pull request commands, repo-context behavior, JSON output, and development checks so the README matches the current CLI instead of inherited upstream plans.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
getIssueState() now queries constellation.microcosm.blue for all state
records that reference an issue, fetches each one via getRecord, and
sorts by rkey (TID) to find the most recent. This allows collaborators
to close/reopen issues from their own PDS, fixing the old approach that
only ever saw the issue author's state records.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
listIssues() now queries constellation.microcosm.blue for all issue
records referencing a repo, then fetches each one via getRecord. This
fixes multi-collaborator scenarios where team members host issues on
different PDSs — the old listRecords approach only saw the repo owner's
issues.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The current-session-metadata keychain entry was getting wiped by transient
errors (e.g. network failure on wake) in the resumeSession() catch block,
forcing users to re-authenticate unnecessarily.
Changes:
- Store session metadata in ~/.config/tangled/session.json (not keychain)
so it survives sleep/wake cycles and keychain lock events
- Don't clear metadata on transient agent.resumeSession() failures — only
clear when loadSession() definitively returns null (no stored credentials)
- Update session.test.ts to mock node:fs/promises with in-memory storage
- Update api-client.test.ts to reflect new non-clearing behavior
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Export KeychainAccessError from session.ts; thrown when getPassword() fails
(platform error like locked keychain), not when an entry is missing (undefined)
- resumeSession() now rethrows KeychainAccessError without clearing metadata,
so temporarily locked keychains no longer wipe stored credentials
- Add ensureAuthenticated(client) to auth-helpers.ts: on KeychainAccessError,
attempts to unlock the keychain via `security unlock-keychain` (macOS only),
retries once, then falls back to a clear error message with manual instructions
- Replace 7 repeated inline auth-check blocks in issue.ts with ensureAuthenticated()
- Add/update tests for KeychainAccessError propagation and ensureAuthenticated behavior
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
outputJson previously required Record<string, unknown>, which forced
callers to either use untyped objects or add a catch-all index signature
to typed interfaces like IssueData. Using a generic T extends object
allows any typed object to be passed while keeping the Record<string,
unknown> cast scoped to pickFields where dynamic key access is actually
needed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
All issue commands now return the full common field set (number, title,
body, state, author, createdAt, uri, cid) in --json mode:
- create: adds state:'open' (always open on creation)
- view: adds number via getCompleteIssueData (replaces separate getIssue
+ getIssueState calls)
- edit: adds number + state via parallel resolveSequentialNumber +
getIssueState after updateIssue (avoids re-fetching the updated record)
- close/reopen: adds body, author, createdAt via getCompleteIssueData with
stateOverride; replaces separate getIssue + resolveSequentialNumber calls
- delete: adds body, author, createdAt, state via getCompleteIssueData;
replaces scattered local variables
Update command tests to mock getCompleteIssueData and assert on the full
field set.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Move resolveSequentialNumber out of the commands layer into issues-api.ts
where it belongs alongside the other issue data functions. Add IssueData
interface as the canonical JSON shape for a single issue, and
getCompleteIssueData which fetches issue record, sequential number, and
state in one call (with optional state override for mutation commands that
already know the new state). Add tests for both new exports.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Addresses issues #8, #13, and #14:
- issue create now shows #N (sequential number) instead of the AT Protocol
TID rkey in human output, and includes `number` in --json output
- issue close, reopen, and delete now show the issue title in success output
- issue close, reopen, and delete now support --json with field filtering,
returning number, title, uri, state (close/reopen), and cid
- New resolveSequentialNumber() helper extracts number from displayId fast
path or falls back to listIssues lookup for rkey inputs
- 16 new tests added (281 total)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds GitHub CLI-style --json [fields] option to issue list, view,
create, and edit commands. When --json is passed, outputs machine-
readable JSON instead of human-readable text. An optional comma-
separated field list filters the output to only the requested fields.
Adds outputJson() utility to src/utils/formatting.ts for reuse
across future commands.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces the skipped stdin placeholder with three real tests covering:
- reading a single data chunk from stdin
- concatenating multiple chunks
- propagating stdin error events
process.stdin is non-configurable on the real process object, so the
tests mock the entire node:process module using a Proxy that intercepts
the stdin property and returns a local EventEmitter fixture while
forwarding all other property accesses to the real module.
The readFromStdin Promise constructor registers handlers synchronously,
so events can be emitted immediately after readBodyInput returns its
pending Promise without needing setImmediate.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements the remaining issue management subcommands with full
lifecycle support:
- view: displays issue details including state (open/closed), author,
creation date, body; supports numeric (#1) and rkey identifiers
- edit: updates title and/or body via --title/-t, --body/-b, --body-file/-F
- close/reopen: toggle issue state; idempotent by design
- delete: permanently removes an issue; requires confirmation unless
--force is passed; cancel path uses process.exit(0) outside try/catch
to correctly propagate clean exit
Also:
- resolveIssueUri helper: on-demand numeric resolution by sorting issues
by createdAt (oldest = #1), no persistent cache needed
- list command updated to show numbered issues with state badges
- formatIssueState added to src/utils/formatting.ts
- All existing list tests updated for new output format; new test suites
added for all five commands covering auth, context, numeric/rkey
resolution, option validation, and confirmation flow
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extends the issues API layer with two new functions needed for full
issue lifecycle management:
- getIssueState: queries sh.tangled.repo.issue.state records to
determine open/closed status, defaulting to 'open' when no records
exist. Filters by issue URI and uses the most recent state record.
- reopenIssue: mirrors closeIssue by creating a state record with
state 'sh.tangled.repo.issue.state.open'
Includes tests covering state filtering, multi-record precedence,
unauthenticated error paths, and correct record creation.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enable erasableSyntaxOnly and isolatedDeclarations for:
- Faster builds with parallel declaration generation
- Better compatibility with fast transpilers (esbuild, SWC)
- More explicit public API types
Add explicit Zod type annotations to validation schemas
to satisfy isolatedDeclarations requirements.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix issue where test files were excluded from TypeScript compilation,
causing VS Code to catch type errors that npm run typecheck missed.
Changes:
- Add "tests/**/*" to include array in tsconfig.json
- Remove "tests/**/*" from exclude array
- Tests are now type-checked by both build and typecheck scripts
- Tests are emitted to dist/tests/ (harmless locally, can exclude from npm publish later)
This ensures type errors in tests are caught by:
- npm run typecheck (local development)
- npm run build (before commits)
- CI pipeline (prevents broken tests from being merged)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add ability to list all issues for the current repository.
Features:
- Lists issues with rkey (issue number) and title
- Shows creation date with human-readable formatting
- Supports --limit option (1-100, default 50)
- Displays "No issues found" message when list is empty
New utilities:
- Add formatDate() in src/utils/formatting.ts
- Converts dates to human-readable format (today, yesterday, X days/weeks/months ago, or locale date)
- Comprehensive test coverage with 9 tests
Implementation:
- Add createListCommand() with limit validation
- Import formatDate from utils/formatting.ts
- Add 9 comprehensive command tests
Tests:
- List issues successfully
- Custom limit handling
- Empty list handling
- Authentication required
- Context required
- Invalid limit validation (too low/high/non-numeric)
- API error handling
- Date formatting tests (9 tests covering all ranges)
All 232 tests passing ✅
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Issue records were not appearing on tangled.org because the repo field
used an incorrect AT-URI format with the repository name as the rkey
instead of the actual record key.
Changes:
- Update buildRepoAtUri() to query PDS for sh.tangled.repo records
- Find matching repository by name field in record value
- Return record's URI which contains the correct rkey
- Add comprehensive tests for new query-based resolution
Before: at://did:plc:xxx/sh.tangled.repo/tangled-cli
After: at://did:plc:xxx/sh.tangled.repo/3mef23waqwq22
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Define IssueRecord interface locally in issues-api.ts
- Based on sh.tangled.repo.issue lexicon schema
- Includes required index signature for AT Protocol compatibility
- Documents lexicon source for reference
- Avoids TypeScript compilation issues with generated lexicon types
- Maintains full type safety while working around codegen limitations
This approach is recommended in src/lexicon/README.md and provides
a stable interface that won't break when lexicons are regenerated.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update tsconfig.json:
- Exclude src/lexicon/** from compilation (generated code has compatibility issues)
- Exclude tests/**/* from build output (tests are type-checked separately)
- Only include src/**/* in build
- Regenerate lexicons with post-processing fixes applied:
- All multiformats/cid imports now use main package export
- All relative imports now have .js extensions
- Add comprehensive src/lexicon/README.md documenting:
- Code generation workflow
- Dependencies and multiformats usage
- Recommended usage patterns (custom interfaces)
- Known issues and workarounds
- Troubleshooting guide
This allows TypeScript to compile cleanly while still providing type
information from generated lexicons for IDE support.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add multiformats@9.9.0 as direct dependency for CID type support
- Create scripts/fix-lexicon-imports.js to post-process generated lexicons:
- Adds .js extensions to relative imports for ES module compatibility
- Rewrites 'multiformats/cid' imports to 'multiformats' main export
(fixes TypeScript NodeNext module resolution issues)
- Update codegen script to run post-processor automatically
This resolves TypeScript compilation errors with generated AT Protocol
lexicon code while maintaining strict type checking in application code.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>