feat(claude): account login pane — host claude login in a modal terminal (T-485)

Completes the `login` verb + the UI add/re-login affordances. The
accountActionChannel 'login' action opens ClaudeLoginDialog — a modal hosting a
TerminalPane that runs `CLAUDE_CONFIG_DIR=<dir> claude login`, so the CLI drives
the OAuth browser flow and credentials land in that account's config dir (D-64:
one CLI-initiated browser flow, on explicit action).

TerminalPane gains optional argv/env/cwdOverride (default stays the login
shell), and its pane.spawn carries env when set. The login pane reuses the
TerminalPane *widget* — a code import, not a runtime dependency, since it spawns
through the always-present pane.spawn IPC rather than the terminal extension.

Tests cover the parameterized spawn args and the dialog's host wiring (title,
CLAUDE_CONFIG_DIR, close). Closes T-485.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 00:52:41 +02:00
co-authored by Claude Opus 4.8
parent 222d4ae5e3
commit 996b10d3ec
8 changed files with 199 additions and 8 deletions
+22 -5
View File
@@ -18,7 +18,17 @@ import 'package:clide/src/terminal/terminal.dart';
/// job. The shared widget layer (ClidePtyView, ClidePaneChrome) keeps
/// the two extensions visually consistent without coupling them.
class TerminalPane extends StatefulWidget {
const TerminalPane({super.key});
const TerminalPane({super.key, this.argv, this.env, this.cwdOverride});
/// Command to run instead of the login `$SHELL` — e.g. `['claude', 'login']`
/// for the per-repo account sign-in pane (T-485). Null spawns the shell.
final List<String>? argv;
/// Extra environment for the spawned process (e.g. `CLAUDE_CONFIG_DIR`).
final Map<String, String>? env;
/// Working directory override; defaults to the open workspace.
final String? cwdOverride;
@override
State<TerminalPane> createState() => _TerminalPaneState();
@@ -79,16 +89,23 @@ class _TerminalPaneState extends State<TerminalPane> {
// Windows has no $SHELL convention and no login-shell flag —
// PowerShell 7 first, classic PowerShell as the always-there
// fallback.
// fallback. A caller-supplied argv (e.g. `claude login`, T-485) wins.
final shell = Platform.isWindows ? null : (Platform.environment['SHELL'] ?? '/bin/bash');
final argv = shell != null ? [shell, '-l'] : ['powershell.exe', '-NoLogo'];
final argv = widget.argv ?? (shell != null ? [shell, '-l'] : ['powershell.exe', '-NoLogo']);
// The open workspace, not Directory.current — a desktop launch starts
// in $HOME and a project switch doesn't move the process CWD (T-381).
final cwd = _kernel?.project.current?.path ?? Directory.current.path;
final cwd = widget.cwdOverride ?? _kernel?.project.current?.path ?? Directory.current.path;
final response = await ipc.request(
'pane.spawn',
args: {'argv': argv, 'kind': PaneKind.terminal.wire, 'cwd': cwd, 'cols': _terminal.viewWidth, 'rows': _terminal.viewHeight},
args: {
'argv': argv,
'kind': PaneKind.terminal.wire,
'cwd': cwd,
'cols': _terminal.viewWidth,
'rows': _terminal.viewHeight,
if (widget.env != null) 'env': widget.env,
},
);
if (!mounted) return;
if (!response.ok) {