make CLI argv args reach typed command handlers (T-232)

Parameterized subsystem commands were unreachable from the CLI: the
argv translator emits {positional, flags} but the handlers read named
top-level keys (args['path'], args['id'], ...), and nothing mapped
between them -- so 'clide editor open <path>' returned 'path is
required'. The fix needed no new mechanism: D-74's CommandSchema.normalize
already folds the argv shape into named args by a declared positional
ordering; these commands just never registered a schema.

Adopts it for the navigation/drive surface -- editor.open/activate/read/
save/close, files.read/ls, pane.close/focus/resize/write -- with
non-required positional schemas, so the only effect is positional->named
mapping plus numeric coercion of line/cols/rows. Handlers unchanged;
missing-arg errors unchanged. Edit-mutation verbs, pane.spawn, and git
arg verbs are deferred (noted on the ticket).

Takes effect on app restart (the dispatcher is built once at boot).

Closes T-232 (under T-208 'Give Claude hands').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:47:34 +02:00
co-authored by Claude Opus 4.8
parent 4e5d7b1347
commit a08f75ae1c
9 changed files with 103 additions and 11 deletions
+13 -5
View File
@@ -13,6 +13,7 @@ import 'dart:io' show FileSystemException;
import '../editor/buffer.dart' show Selection;
import '../editor/registry.dart';
import '../ipc/command_schema.dart';
import '../ipc/envelope.dart';
import '../ipc/errno_mapping.dart';
import '../ipc/schema_v1.dart';
@@ -20,18 +21,25 @@ import 'dispatcher.dart';
export '../editor/buffer.dart' show Selection;
// Positional schemas so the CLI binds `clide editor <verb> <args…>` to the
// named keys the handlers read (T-232, via the D-74 normalize hook). Args are
// declared non-required — the handlers keep their own presence checks — so the
// only effect is positional→named mapping plus numeric coercion of `line`.
const _idArg = CommandSchema(positional: ['id'], args: {'id': ArgSpec()});
void registerEditorCommands(DaemonDispatcher d, EditorRegistry registry) {
d.register('editor.open', (req) => _open(req, registry));
d.register('editor.open', (req) => _open(req, registry),
schema: const CommandSchema(positional: ['path', 'line'], args: {'path': ArgSpec(), 'line': ArgSpec(type: ArgType.number)}));
d.register('editor.active', (req) => _active(req, registry));
d.register('editor.activate', (req) => _activate(req, registry));
d.register('editor.activate', (req) => _activate(req, registry), schema: _idArg);
d.register('editor.list', (req) => _list(req, registry));
d.register('editor.read', (req) => _read(req, registry));
d.register('editor.read', (req) => _read(req, registry), schema: _idArg);
d.register('editor.insert', (req) => _insert(req, registry));
d.register('editor.replace-selection', (req) => _replace(req, registry));
d.register('editor.set-selection', (req) => _setSelection(req, registry));
d.register('editor.set-content', (req) => _setContent(req, registry));
d.register('editor.save', (req) => _save(req, registry));
d.register('editor.close', (req) => _close(req, registry));
d.register('editor.save', (req) => _save(req, registry), schema: _idArg);
d.register('editor.close', (req) => _close(req, registry), schema: _idArg);
}
IpcResponse _userErr(String id, String msg, {String? hint}) => IpcResponse.err(
+8 -2
View File
@@ -9,11 +9,17 @@ import '../files/listing.dart';
import '../files/path_safety.dart';
import '../files/pql_config.dart';
import '../files/watcher.dart';
import '../ipc/command_schema.dart';
import '../ipc/envelope.dart';
import '../ipc/schema_v1.dart';
import '../panes/event_sink.dart';
import 'dispatcher.dart';
/// Positional schema binding `clide files <verb> <path>` to args['path']
/// (T-232, via D-74 normalize). Non-required — the handlers keep their own
/// path checks — so the only effect is the positional→named mapping.
const _pathArg = CommandSchema(positional: ['path'], args: {'path': ArgSpec()});
/// Cap on `files.read` response size. UI doesn't render multi-MB
/// blobs usefully and a single uncapped call can OOM. Range/stream
/// reads will land as a separate command (T-104 follow-up).
@@ -112,7 +118,7 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) {
}
final content = file.readAsStringSync();
return IpcResponse.ok(id: req.id, data: {'path': path, 'content': content});
});
}, schema: _pathArg);
d.register('files.ls', (req) async {
final dir = (req.args['path'] as String?) ?? '';
@@ -135,7 +141,7 @@ void registerFilesCommands(DaemonDispatcher d, FilesService files) {
'entries': [for (final e in entries) e.toJson()],
},
);
});
}, schema: _pathArg);
d.register('files.walk', (req) async {
final result = await walkFiles(root: files.root, ignore: files.ignore);
+12 -4
View File
@@ -11,6 +11,7 @@ library;
import 'dart:convert';
import '../ipc/command_schema.dart';
import '../ipc/envelope.dart';
import '../ipc/errno_mapping.dart';
import '../ipc/schema_v1.dart';
@@ -27,12 +28,19 @@ import 'dispatcher.dart';
typedef ViewPaneSource = List<ViewPane> Function();
void registerPaneCommands(DaemonDispatcher d, PaneRegistry registry, {ViewPaneSource? viewPanes}) {
// Positional schemas bind `clide pane <verb> <args…>` to the named keys the
// handlers read (T-232, via D-74 normalize). Non-required — handlers keep
// their presence checks — so the effect is positional→named mapping plus
// numeric coercion of cols/rows.
const idArg = CommandSchema(positional: ['id'], args: {'id': ArgSpec()});
d.register('pane.spawn', (req) => _spawn(req, registry));
d.register('pane.list', (req) => _list(req, registry, viewPanes));
d.register('pane.close', (req) => _close(req, registry));
d.register('pane.write', (req) => _write(req, registry));
d.register('pane.resize', (req) => _resize(req, registry));
d.register('pane.focus', (req) => _focus(req, registry));
d.register('pane.close', (req) => _close(req, registry), schema: idArg);
d.register('pane.write', (req) => _write(req, registry), schema: const CommandSchema(positional: ['id', 'text'], args: {'id': ArgSpec(), 'text': ArgSpec()}));
d.register('pane.resize', (req) => _resize(req, registry),
schema: const CommandSchema(
positional: ['id', 'cols', 'rows'], args: {'id': ArgSpec(), 'cols': ArgSpec(type: ArgType.number), 'rows': ArgSpec(type: ArgType.number)}));
d.register('pane.focus', (req) => _focus(req, registry), schema: idArg);
d.register('pane.tail', (req) => _tail(req, registry));
}