Files
clide/lib/builtin/cli_install/src/extension.dart
T
jpmschweitzerandClaude Opus 4.8 51cfa15c78 distinguish a dev-tree clide from a packaged install (T-256)
CliInstaller.inspect() gains a devTree state: when `clide` on PATH resolves
to a dev-tree build artifact (native/<plat>/clide, the Makefile's
CLIDE_CLI_BIN output) it's classified separately from a packaged install
rather than silently treated as "installed". needsInstall stays false for a
dev build (it's intentional on a checkout, not a reinstall prompt), and the
launch-time check surfaces it as an info note. install() flags fromDevTree +
notes it in the result message when the copied source is a dev build.

Closes the last open child of the T-208 "give Claude hands" initiative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:53:01 +02:00

91 lines
3.3 KiB
Dart

import 'dart:io';
import 'package:clide/clide.dart';
import 'package:clide/extension/extension.dart';
import 'package:clide/kernel/src/cli_install.dart';
/// VS Code-style "Install 'clide' command in PATH" affordance (T-212).
///
/// Registers a palette/CLI command that copies the bundled C client into a
/// PATH dir, and on activation proactively warns when `clide` is missing from
/// PATH or — the dogfood footgun — resolves to a stale symlink into the
/// Flutter GUI bundle instead of the C client. Detection and the copy live in
/// the Flutter-free [CliInstaller]; this extension only wires it to the
/// command and notification surfaces.
class CliInstallExtension extends ClideExtension {
CliInstallExtension({CliInstaller? installer}) : _installer = installer;
CliInstaller? _installer;
ClideExtensionContext? _ctx;
CliInstaller get _resolved => _installer ??= CliInstaller(resolvedExecutable: Platform.resolvedExecutable);
@override
String get id => 'builtin.cli-install';
@override
String get title => 'CLI Install';
@override
String get version => '0.1.0';
@override
Future<void> activate(ClideExtensionContext ctx) async {
_ctx = ctx;
// Proactive launch-time detection (desktop only). Non-modal: we notify
// and point at the command rather than auto-installing — no surprise
// filesystem writes (interaction-zone discipline, D-78).
if (!(Platform.isLinux || Platform.isMacOS)) return;
switch (_resolved.inspect().state) {
case CliInstallState.missing:
ctx.notify.warn(
'The `clide` command is not on your PATH. Run "clide: Install '
'command in PATH" from the command palette to reach it from a shell.',
title: 'clide CLI not installed',
);
case CliInstallState.staleGui:
ctx.notify.warn(
'`clide` on your PATH points at the GUI app, not the CLI client — '
'a bare `clide` launches a second app. Run "clide: Install command '
'in PATH" to replace it.',
title: 'clide CLI is stale',
);
case CliInstallState.devTree:
ctx.notify.info(
'`clide` on your PATH is the dev-tree build, not a packaged install '
'— fine for development; rebuild it with `make clide-cli`.',
title: 'clide CLI: dev build',
);
case CliInstallState.installed:
break;
}
}
@override
List<ContributionPoint> get contributions => [
CommandContribution(
id: 'clide.installCli',
command: 'clide.installCli',
title: "clide: Install 'clide' command in PATH",
run: (_) async {
final r = _resolved.install();
final ctx = _ctx;
if (r.ok) {
ctx?.notify.success(r.message, title: 'clide CLI installed');
return IpcResponse.ok(id: '', data: {
'installed': r.installedPath,
'onPath': r.onPath,
});
}
ctx?.notify.error(r.message, title: 'clide CLI install failed');
return IpcResponse.err(
id: '',
error: IpcError(
code: IpcExitCode.toolError,
kind: IpcErrorKind.toolError,
message: r.message,
),
);
},
),
];
}