feat: card external links (client, CLI, MCP) + card view/get_card include (#21) - #23
Merged
Conversation
`ExternalLink { id, uid, url, description, created, updated }` and the
`external_links()` facade over `/cards/{id}/external-links`. Shapes taken
from the live API: updates are PATCH with only the given fields (PUT
answers 404), `description` is optional and stored as null when absent.
The API validates neither the url nor duplicates; the client passes
requests through as they are.
#21) `card external-link add --url … [--description …]`, `edit <link> --url/--description` (PATCH with only the given fields), `list` (table or --json), `rm`. `--url` must be an absolute http(s) URL without credentials — Kaiten stores anything, the CLI refuses obvious garbage before sending; the value is never echoed in the error. `card view --include external_links,comments` (repeatable or comma-separated) fetches extra sections; the value names match the MCP `get_card` `include` values. `--comments` keeps working but is marked deprecated in --help and prints one warning line on stderr. Plain `card view` output and the `--comments --json` shape are unchanged.
Four tools — list_card_external_links, add_card_external_link, update_card_external_link, remove_card_external_link — over the card external links resource; `url` is checked to be an absolute http(s) URL before any request (Kaiten stores anything) and never echoed in the error. `get_card` gains an optional `include: ["external_links", "comments"]` (names shared with the CLI `card view --include`): each section is one extra request and one extra key in the result; without `include` the tool stays a single request with the same shape as before. 36 → 40 tools; the stdio contract test covers `include` and rejects an unknown section before any request.
…mments deprecated
…eview follow-ups (#21) The WHATWG parser strips control characters and repairs `http:x`, `http:/x` and `http:///x` into `http://x/`, while Kaiten stores the raw text — so the url check now requires an `http(s)://` prefix followed by a host on the trimmed text and no control characters, before the parsed host/credentials checks. Also from review: `card view` prints the section as `External links:`; `--description` gets help text; `try_args!` keeps its doc comment (the url helper moved below the macro); the server instructions mention external links; `Card` doc says why `include` costs a request; tests pin `edit --url` at every layer, the `{card, comments}` JSON shape of the deprecated `--comments`, single fetch for `--comments --include comments`, the repeated `--include` form and the exact `IncludeSection` schema.
A `coverage` job on ubuntu runs the workspace tests instrumented (cargo-llvm-cov also counts the `kaiten` binary the integration tests spawn — 84% lines on master today) and uploads lcov to Codecov. The upload token lives in the CODECOV_TOKEN Actions and Dependabot secrets; PRs from forks have neither, so an upload failure fails the job only for pushes and same-repo PRs.
dsociative
force-pushed
the
feat/21-external-links
branch
from
August 27, 2026 14:17
ea52715 to
6dc81ee
Compare
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
…s on stdout by design The query models `println!` as a log file and any identifier matching user(name|id)/uid/account as sensitive, so every `card view`, `card member` and `auth status` line naming a user is an alert (8 open, all false positives). The only real log sinks are the `tracing` calls, which print host, path and status only. The rest of the default suite stays on.
12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
Card external links (
Links (common links)in Kaiten) at every layer, plus an opt-in way to fetch related sections together with a card. Strictly additive; nothing existing changes shape.kaiten-client
ExternalLink { id, uid, url, description, created, updated }andclient.external_links()withlist / add / update / removeover/cards/{id}/external-links. Shapes captured from the live API: updates arePATCHwith only the given fields (PUTanswers 404),descriptionis optional and stored asnullwhen absent.cargo semver-checksagainst v0.4.1: no breaking changes (additive only).CLI
card external-link list | add --url … [--description …] | edit <link> --url/--description | rm.--urlmust be an absolute http(s) URL without embedded credentials; checked before any request, the value is never echoed in the error.card view --include external_links,comments(repeatable or comma-separated). The value names are the same tokens the MCPget_cardincludetakes.--commentskeeps working, is markedDeprecated: use --include commentsin--helpand prints one warning line on stderr. Plaincard viewoutput and the--comments --jsonshape ({card, comments}) are unchanged.MCP
list_card_external_links,add_card_external_link,update_card_external_link,remove_card_external_link(36 → 40 tools; pinned in the unit test and the stdio contract test).get_cardgains an optionalinclude: ["external_links", "comments"]: each section is one extra request and one extra key. Withoutincludethe tool is a single request with the same result shape as before. An unknown section is a parameter error before any request, naming the accepted values.Tests (written first)
external_links_test.rs(request bodies viabody_json, PATCH partial body, DELETE path).card_external_link_test.rs(table/json, bad url → exit 1 with zero requests, edit-with-nothing, 403),card_view_test.rs(--includesecond request, both sections, deprecation warning on stderr with stdout intact, json key, unknown section → exit 2 with zero requests).get_cardinclude(plain call makes exactly one request), schema check, stdio wire-contract test forincludeand the unknown-section error.Live smoke (dstest, card 67089469)
CLI: add → list →
view --include external_links,comments→--comments(warning on stderr only) →--json→ edit description and url → rm → bad url / credentials → exit 1 without a request → edit with nothing → rm unknown →API error 403. MCP stdio: 40 tools listed, add →get_card include(both sections; plainget_cardhas neither key) → update → list → bad url / nothing-to-change / unknown include → tool-level errors → remove → 403 on unknown. The card was left without links.Compatibility
No existing CLI flag, output, config key, env var, MCP tool name/param/result shape or
kaiten-clientpublic item changed. New functionality → 0.5.0 after merge.CI
New
coveragejob (cargo-llvm-cov → Codecov via theCODECOV_TOKENActions + Dependabot secrets; the instrumented run includes the spawnedkaitenbinary — 84% lines on master) and a Codecov badge in the README. The badge starts showing after the first run on master.CodeQL
rust/cleartext-loggingis excluded via.github/codeql/codeql-config.yml: it modelsprintln!as a log file and any identifier matchinguser(name|id)/uid/accountas sensitive, so every CLI line that names a user was an alert (8 open on master, all false positives, no per-identifier allowlist and no inline suppression for Rust). Thetracingcalls — the only real log sinks — print host, path and status only.