feat(cli): --stdin piped JSON payloads for structured commands (T-315)
The C client slurps stdin when it sees --stdin, strips the flag, and ships the payload alongside the argv; the Dart unwrap folds it into the request as a `stdin` arg (undeclared keys pass the schema untouched). icon.show and image.show now read that payload as the peer of --file (stdin wins). Bounded slurp + the envelope's existing size guard keep a huge payload from corrupting the wire. C client builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9080,3 +9080,8 @@ COLOR (2026-06-28, folded into payload spec): add an OPTIONAL per-entry ''color'
|
||||
DONE (2026-06-29): Phosphor glyph card landed end-to-end + tests green. icon.show (Flutter-free, dart-tested): variadic stringList positionals (clide icon show gear folder — required a schema enhancement so a trailing stringList positional is variadic) OR --file JSON [{icon,label,description,color}]; resolves by name (injected kPhosphorGlyphs resolver) or 0xNNNN; color via parseSvgColor (hex/CSS); publishes on ''icon'' bus; honest userError on unknown glyph/bad color/malformed payload/no UI. Card: per entry a hero (52) + continuous sample strip 10..48, optional label/description, per-entry/card color; display-only D-78. Commits 9a2b4e9e (handler+schema) + 281fb2a8 (card+wiring).', NULL, '2026-06-29 11:11:17', '2026-06-29 11:11:17.477', '2026-06-29 11:11:17.477', NULL, 'dc19f7fc9798ca56d6ec7db1779744e4', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB234WP4Y6Q16A0HFW8BSXMG', 'status', 'in_progress', 'done', NULL, '2026-06-29 11:11:17', '2026-06-29 11:11:17.514', '2026-06-29 11:11:17.514', NULL, 'b051374bed839b26002105c4febe1574', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'status', 'backlog', 'in_progress', NULL, '2026-06-29 11:11:28', '2026-06-29 11:11:28.762', '2026-06-29 11:11:28.762', NULL, '490b259653d17d1643ebb9e8a17f0574', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'description', 'Template for the unified drawing card (T-317, D-91): compare-images / before-after. The JSON carries two (or more) image paths, each with an optional label + description; the card renders them side by side for visual comparison, each with a lightbox-on-click (shared with the image template). Lowers onto the canvas primitives + the shared per-object label/description widget (T-318). Display-only (D-78). Depends on the core engine (T-318).', 'Template for the unified drawing card (T-317, D-91): compare-images / before-after. The JSON carries two (or more) image paths, each with an optional label + description; the card renders them side by side for visual comparison, each with a lightbox-on-click (shared with the image template). Lowers onto the canvas primitives + the shared per-object label/description widget (T-318). Display-only (D-78). Depends on the core engine (T-318).
|
||||
|
||||
DONE (2026-06-29): SVG-substrate compare card landed (approach chosen by user) + tests green. compareTemplateHandler lowers an images array [{path,label,description}] to side-by-side <image> cells, each with the T-318 data-label/description caption + data-lightbox; paths resolved to absolute up front (injected, honest DrawErr on a miss). _paintImage now aspect-fits (contain, centered) so differing shapes don''t distort. Draw card path loads <image> hrefs -> ui.Image via loadDrawingImages (decoder injectable for tests); renderer + lightbox both paint through the resolver. Commits a651c9e0 (template+painter) + 1f6d31ac (image-loading+register).', NULL, '2026-06-29 13:02:44', '2026-06-29 13:02:44.687', '2026-06-29 13:02:44.687', NULL, '61cd7ded0058d2727055ab9805712180', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'status', 'in_progress', 'done', NULL, '2026-06-29 13:02:44', '2026-06-29 13:02:44.726', '2026-06-29 13:02:44.726', NULL, 'a03f0481fb84e46fa45077355a64a47b', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ACSDBDZARV3NNGYD9NYYR', 'status', 'backlog', 'in_progress', NULL, '2026-06-29 16:44:18', '2026-06-29 16:44:18.342', '2026-06-29 16:44:18.342', NULL, '0d5c93289262d7527ea7471e96f8d02b', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -10959,3 +10959,37 @@ COLOR (2026-06-28, folded into payload spec): add an OPTIONAL per-entry ''color'
|
||||
|
||||
DONE (2026-06-29): Phosphor glyph card landed end-to-end + tests green. icon.show (Flutter-free, dart-tested): variadic stringList positionals (clide icon show gear folder — required a schema enhancement so a trailing stringList positional is variadic) OR --file JSON [{icon,label,description,color}]; resolves by name (injected kPhosphorGlyphs resolver) or 0xNNNN; color via parseSvgColor (hex/CSS); publishes on ''icon'' bus; honest userError on unknown glyph/bad color/malformed payload/no UI. Card: per entry a hero (52) + continuous sample strip 10..48, optional label/description, per-entry/card color; display-only D-78. Commits 9a2b4e9e (handler+schema) + 281fb2a8 (card+wiring).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 10:20:54', '2026-06-29 11:11:17.514', NULL, '0952c384083c3bd521c8b693bdbf1bf7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'Drawing card template: compare-images / before-after', 'Template for the unified drawing card (T-317, D-91): compare-images / before-after. The JSON carries two (or more) image paths, each with an optional label + description; the card renders them side by side for visual comparison, each with a lightbox-on-click (shared with the image template). Lowers onto the canvas primitives + the shared per-object label/description widget (T-318). Display-only (D-78). Depends on the core engine (T-318).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 11:11:45', '2026-06-29 11:11:28.761', NULL, 'd0854a069e7eb5a42e250a9b7ef134cb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'Drawing card template: compare-images / before-after', 'Template for the unified drawing card (T-317, D-91): compare-images / before-after. The JSON carries two (or more) image paths, each with an optional label + description; the card renders them side by side for visual comparison, each with a lightbox-on-click (shared with the image template). Lowers onto the canvas primitives + the shared per-object label/description widget (T-318). Display-only (D-78). Depends on the core engine (T-318).
|
||||
|
||||
DONE (2026-06-29): SVG-substrate compare card landed (approach chosen by user) + tests green. compareTemplateHandler lowers an images array [{path,label,description}] to side-by-side <image> cells, each with the T-318 data-label/description caption + data-lightbox; paths resolved to absolute up front (injected, honest DrawErr on a miss). _paintImage now aspect-fits (contain, centered) so differing shapes don''t distort. Draw card path loads <image> hrefs -> ui.Image via loadDrawingImages (decoder injectable for tests); renderer + lightbox both paint through the resolver. Commits a651c9e0 (template+painter) + 1f6d31ac (image-loading+register).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 11:11:45', '2026-06-29 13:02:44.687', NULL, 'c2e33736d8dcbe31102fd5f12495c53e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ESCR4V6V07CRH18FBCDN8', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'Drawing card template: compare-images / before-after', 'Template for the unified drawing card (T-317, D-91): compare-images / before-after. The JSON carries two (or more) image paths, each with an optional label + description; the card renders them side by side for visual comparison, each with a lightbox-on-click (shared with the image template). Lowers onto the canvas primitives + the shared per-object label/description widget (T-318). Display-only (D-78). Depends on the core engine (T-318).
|
||||
|
||||
DONE (2026-06-29): SVG-substrate compare card landed (approach chosen by user) + tests green. compareTemplateHandler lowers an images array [{path,label,description}] to side-by-side <image> cells, each with the T-318 data-label/description caption + data-lightbox; paths resolved to absolute up front (injected, honest DrawErr on a miss). _paintImage now aspect-fits (contain, centered) so differing shapes don''t distort. Draw card path loads <image> hrefs -> ui.Image via loadDrawingImages (decoder injectable for tests); renderer + lightbox both paint through the resolver. Commits a651c9e0 (template+painter) + 1f6d31ac (image-loading+register).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 11:11:45', '2026-06-29 13:02:44.726', NULL, '0c8fca4b7ab4f3d23364e389373a35b0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ACSDBDZARV3NNGYD9NYYR', 'task', '06FB2EDCBYRBDSV9V1PJ1KE3CM', 'clide CLI: accept a JSON payload on stdin for structured commands', 'Add a stdin path to clide''s CLI so a command can receive a JSON payload piped in — `… | clide icon show --stdin`, `cat meta.json | clide image show foo.png --stdin` — instead of only positionals/flags or a `--file`.
|
||||
|
||||
## Why
|
||||
|
||||
Structured commands (the labelled icon-card entries in T-313, image annotation metadata in T-316) want a JSON payload that''s awkward to express as flags. Today clide''s CLI argv parser (lib/src/cli/argv_to_request.dart) only produces positionals, --flags, and `-- passthrough`; there is no stdin path. T-313 therefore falls back to a `--file <path.json>` flag. A `--stdin` convention is the ergonomic peer of `--file` for piping, and is shared infra both icon.show and image.show consume.
|
||||
|
||||
## Where the work lives
|
||||
|
||||
clide''s IPC server runs in-process and the `clide` CLI is a thin client that serialises argv into an IpcRequest over CLIDE_SOCK. So stdin must be slurped CLIENT-SIDE (in lib/src/cli/, around argv_to_request.dart / argv_dispatch.dart) and folded into the request before it is sent — the in-process handler never sees the real stdin. Decide how it surfaces in the envelope: e.g. a reserved `stdin`/`payload` field on IpcRequest, or a synthesised arg the CommandSchema can opt into (an ArgSpec flag like `acceptsStdin`, mirroring how ArgType.stringList is declared in lib/src/ipc/command_schema.dart).
|
||||
|
||||
## Scope / decisions
|
||||
|
||||
- Generic infra, not icon/image specific — once landed, any command opts in via its CommandSchema.
|
||||
- Keep `--file` working; --stdin and --file should be mutually exclusive (error if both given) or layered with a defined precedence.
|
||||
- Text/JSON payloads only to start; define a size cap and a clear error when --stdin is passed but stdin is empty/not a pipe (don''t hang waiting on a TTY).
|
||||
- Honest IpcError (userError) on malformed JSON, surfaced like image.show''s other validation failures.
|
||||
- D-6 parity: document the stdin convention alongside the other CLI verbs.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- A command can declare (via CommandSchema) that it accepts a stdin payload; piping JSON in populates the IpcRequest with that payload.
|
||||
- `clide icon show --stdin` (T-313) and `clide image show <path> --stdin` (T-316) both consume it.
|
||||
- --stdin + --file together is a clear user error; --stdin with no piped input fails fast, never hangs on a TTY.
|
||||
- Malformed JSON returns a userError with a helpful message.
|
||||
|
||||
Unblocks the piped-JSON variants of T-313 (icon entries) and T-316 (image annotations); both can also ship with --file independently of this.
|
||||
|
||||
Deferred 2026-06-28 (user): blocked on its consumers T-316 + T-313 so it only resurfaces if one actually wants the piping UX. Standalone --stdin infra isn''t worth a build cycle now — both consumers can ship with --file (per this ticket), and structured JSON also flows natively through the MCP tool surface (D-86). Build it lazily inside whichever consumer first needs piping, if ever.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 10:52:34', '2026-06-29 16:44:18.342', NULL, 'c6331517a674a7e66a02bac032bc6cff', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
Reference in New Issue
Block a user