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:
@@ -36,7 +36,21 @@ ArgvParseResult unwrapArgvRequest(IpcRequest outer) {
|
||||
),
|
||||
);
|
||||
}
|
||||
return parseArgv(raw.cast<String>(), requestId: outer.id);
|
||||
final result = parseArgv(raw.cast<String>(), requestId: outer.id);
|
||||
// A piped `--stdin` payload (T-315): the C client slurps stdin and ships it
|
||||
// alongside the argv. Fold it into the inner request as a `stdin` flag so it
|
||||
// surfaces as a named arg (undeclared keys pass the schema untouched) — the
|
||||
// handler reads it as the structured payload, the piped peer of `--file`.
|
||||
final stdin = outer.args['stdin'];
|
||||
if (result is ArgvParsed && stdin is String) {
|
||||
final req = result.request;
|
||||
final args = Map<String, Object?>.from(req.args);
|
||||
final flags = Map<String, Object?>.from((args['flags'] as Map?) ?? const {});
|
||||
flags['stdin'] = stdin;
|
||||
args['flags'] = flags;
|
||||
return ArgvParsed(IpcRequest(id: req.id, cmd: req.cmd, args: args));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Wire the `_argv` sentinel handler onto [dispatcher]. The handler
|
||||
|
||||
@@ -62,11 +62,14 @@ Future<IpcResponse> _show(IpcRequest req, MessagePublisher? Function() publisher
|
||||
}
|
||||
|
||||
final entries = <Map<String, Object?>>[];
|
||||
// The entry payload comes from a piped --stdin (T-315) or a --file; --stdin
|
||||
// wins. Either is a JSON array of {icon,label,description,color}.
|
||||
final stdin = _str(req.args['stdin']);
|
||||
final file = _str(req.args['file']);
|
||||
|
||||
if (file != null) {
|
||||
final raw = readFile == null ? null : await readFile(file);
|
||||
if (raw == null) {
|
||||
String? payload = stdin;
|
||||
if (payload == null && file != null) {
|
||||
payload = readFile == null ? null : await readFile(file);
|
||||
if (payload == null) {
|
||||
return IpcResponse.err(
|
||||
id: req.id,
|
||||
error: IpcError(
|
||||
@@ -77,11 +80,14 @@ Future<IpcResponse> _show(IpcRequest req, MessagePublisher? Function() publisher
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (payload != null) {
|
||||
Object? decoded;
|
||||
try {
|
||||
decoded = jsonDecode(raw);
|
||||
decoded = jsonDecode(payload);
|
||||
} on FormatException catch (e) {
|
||||
return _userErr(req.id, 'invalid JSON in $file: ${e.message}');
|
||||
return _userErr(req.id, 'invalid JSON in the icon payload: ${e.message}');
|
||||
}
|
||||
if (decoded is! List) return _userErr(req.id, 'icon metadata must be a JSON array of entries');
|
||||
for (final item in decoded) {
|
||||
|
||||
@@ -68,13 +68,15 @@ Future<IpcResponse> _show(IpcRequest req, MessagePublisher? Function() publisher
|
||||
String? label, description;
|
||||
String? caption = req.args['caption'] as String?;
|
||||
|
||||
// --file <json>: an annotation payload {path,label,description,caption}
|
||||
// (T-316). Additive — the bare `image show <path> [--caption]` form is
|
||||
// unchanged; label/description are the new richer metadata.
|
||||
final file = req.args['file'] as String?;
|
||||
if (file != null && file.trim().isNotEmpty) {
|
||||
final raw = readFile == null ? null : await readFile(file);
|
||||
if (raw == null) {
|
||||
// An annotation payload {path,label,description,caption} from a piped --stdin
|
||||
// (T-315) or a --file (T-316); --stdin wins. Additive — the bare
|
||||
// `image show <path> [--caption]` form is unchanged.
|
||||
final stdin = _str(req.args['stdin']);
|
||||
final file = _str(req.args['file']);
|
||||
String? payload = stdin;
|
||||
if (payload == null && file != null) {
|
||||
payload = readFile == null ? null : await readFile(file);
|
||||
if (payload == null) {
|
||||
return IpcResponse.err(
|
||||
id: req.id,
|
||||
error: IpcError(
|
||||
@@ -85,11 +87,13 @@ Future<IpcResponse> _show(IpcRequest req, MessagePublisher? Function() publisher
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (payload != null) {
|
||||
Object? decoded;
|
||||
try {
|
||||
decoded = jsonDecode(raw);
|
||||
decoded = jsonDecode(payload);
|
||||
} on FormatException catch (e) {
|
||||
return _userErr(req.id, 'invalid JSON in $file: ${e.message}');
|
||||
return _userErr(req.id, 'invalid JSON in the image payload: ${e.message}');
|
||||
}
|
||||
if (decoded is! Map) return _userErr(req.id, 'image metadata must be a JSON object');
|
||||
path = _str(decoded['path']) ?? path;
|
||||
|
||||
Reference in New Issue
Block a user