tmux-ide 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -9
- package/bin/cli.js +1024 -519
- package/bin/cli.ts +63 -6
- package/bunfig.toml +4 -0
- package/package.json +18 -7
- package/packages/contracts/package.json +22 -0
- package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
- package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
- package/packages/contracts/src/actions-contract.ts +310 -0
- package/packages/contracts/src/actions-errors.ts +41 -0
- package/packages/contracts/src/domain.ts +36 -0
- package/packages/contracts/src/ide-config.ts +170 -0
- package/packages/contracts/src/index.ts +24 -0
- package/packages/contracts/src/lib-internal/auth.ts +13 -0
- package/packages/contracts/src/lib-internal/hq.ts +38 -0
- package/packages/contracts/src/terminals.ts +116 -0
- package/packages/contracts/src/tmux.ts +60 -0
- package/packages/contracts/src/workspace.ts +67 -0
- package/packages/daemon/dist/agent-explain.d.ts +8 -1
- package/packages/daemon/dist/agent-explain.js +19 -3
- package/packages/daemon/dist/lib/tui-binary.d.ts +57 -0
- package/packages/daemon/dist/lib/tui-binary.js +130 -0
- package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/config-tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/field-editor.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +1 -1
- package/packages/daemon/src/agent-explain.ts +298 -0
- package/packages/daemon/src/attach.ts +20 -0
- package/packages/daemon/src/bin.ts +4 -0
- package/packages/daemon/src/canonical.ts +7 -0
- package/packages/daemon/src/cli.ts +499 -0
- package/packages/daemon/src/command-center/actions/contract.ts +2 -0
- package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
- package/packages/daemon/src/command-center/actions/errors.ts +105 -0
- package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
- package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
- package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
- package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
- package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
- package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
- package/packages/daemon/src/command-center/actions/registry.ts +149 -0
- package/packages/daemon/src/command-center/discovery.ts +96 -0
- package/packages/daemon/src/command-center/index.ts +31 -0
- package/packages/daemon/src/command-center/schemas.ts +85 -0
- package/packages/daemon/src/command-center/server.ts +1260 -0
- package/packages/daemon/src/command-center/ws-events.ts +316 -0
- package/packages/daemon/src/config.ts +549 -0
- package/packages/daemon/src/detect.ts +248 -0
- package/packages/daemon/src/doctor.ts +242 -0
- package/packages/daemon/src/embed.ts +5 -0
- package/packages/daemon/src/index.ts +12 -0
- package/packages/daemon/src/init.ts +211 -0
- package/packages/daemon/src/inspect.ts +178 -0
- package/packages/daemon/src/js-yaml.d.ts +10 -0
- package/packages/daemon/src/launch.ts +349 -0
- package/packages/daemon/src/lib/active-projects.ts +49 -0
- package/packages/daemon/src/lib/agent-discovery.ts +121 -0
- package/packages/daemon/src/lib/app-config.ts +427 -0
- package/packages/daemon/src/lib/app-settings.ts +53 -0
- package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
- package/packages/daemon/src/lib/auth/middleware.ts +56 -0
- package/packages/daemon/src/lib/auth/types.ts +2 -0
- package/packages/daemon/src/lib/auth-token.ts +5 -0
- package/packages/daemon/src/lib/authorship.ts +280 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
- package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
- package/packages/daemon/src/lib/daemon-embed.ts +782 -0
- package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
- package/packages/daemon/src/lib/daemon.ts +79 -0
- package/packages/daemon/src/lib/dot-path.ts +17 -0
- package/packages/daemon/src/lib/errors.ts +67 -0
- package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
- package/packages/daemon/src/lib/launch-plan.ts +90 -0
- package/packages/daemon/src/lib/log.ts +134 -0
- package/packages/daemon/src/lib/output.ts +76 -0
- package/packages/daemon/src/lib/project-init-runner.ts +150 -0
- package/packages/daemon/src/lib/project-inspect.ts +80 -0
- package/packages/daemon/src/lib/project-onboard.ts +149 -0
- package/packages/daemon/src/lib/project-probe.ts +92 -0
- package/packages/daemon/src/lib/project-registry.ts +296 -0
- package/packages/daemon/src/lib/session-monitor.ts +122 -0
- package/packages/daemon/src/lib/session-options.ts +100 -0
- package/packages/daemon/src/lib/shell.ts +8 -0
- package/packages/daemon/src/lib/sizes.ts +36 -0
- package/packages/daemon/src/lib/skill-sync.ts +155 -0
- package/packages/daemon/src/lib/slugify.ts +10 -0
- package/packages/daemon/src/lib/terminals-store.ts +125 -0
- package/packages/daemon/src/lib/tui-binary.ts +165 -0
- package/packages/daemon/src/lib/update-check.ts +298 -0
- package/packages/daemon/src/lib/update.ts +158 -0
- package/packages/daemon/src/lib/workspace-registry.ts +229 -0
- package/packages/daemon/src/lib/worktree.ts +289 -0
- package/packages/daemon/src/lib/yaml-io.ts +27 -0
- package/packages/daemon/src/ls.ts +40 -0
- package/packages/daemon/src/restart.ts +24 -0
- package/packages/daemon/src/restore.ts +514 -0
- package/packages/daemon/src/schemas/domain.ts +2 -0
- package/packages/daemon/src/schemas/filesystem.ts +34 -0
- package/packages/daemon/src/schemas/ide-config.ts +2 -0
- package/packages/daemon/src/schemas/index.ts +59 -0
- package/packages/daemon/src/schemas/inspect.ts +67 -0
- package/packages/daemon/src/schemas/registry.ts +55 -0
- package/packages/daemon/src/schemas/ws-events.ts +135 -0
- package/packages/daemon/src/send.ts +171 -0
- package/packages/daemon/src/server/README.md +15 -0
- package/packages/daemon/src/server/index.ts +74 -0
- package/packages/daemon/src/server/pty-bridge.ts +532 -0
- package/packages/daemon/src/server/standalone.ts +18 -0
- package/packages/daemon/src/server/ws-route.ts +483 -0
- package/packages/daemon/src/status.ts +59 -0
- package/packages/daemon/src/stop.ts +28 -0
- package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
- package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
- package/packages/daemon/src/terminal/README.md +92 -0
- package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
- package/packages/daemon/src/tui/chrome/chip.ts +30 -0
- package/packages/daemon/src/tui/chrome/events.ts +119 -0
- package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
- package/packages/daemon/src/tui/chrome/menu.ts +289 -0
- package/packages/daemon/src/tui/chrome/notify.ts +382 -0
- package/packages/daemon/src/tui/chrome/panels.ts +111 -0
- package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
- package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
- package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
- package/packages/daemon/src/tui/chrome/updater.ts +510 -0
- package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
- package/packages/daemon/src/tui/compiled.ts +121 -0
- package/packages/daemon/src/tui/detect/classify.ts +208 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +193 -0
- package/packages/daemon/src/tui/detect/manifest.ts +199 -0
- package/packages/daemon/src/tui/detect/manifests.ts +354 -0
- package/packages/daemon/src/tui/detect/process-tree.ts +217 -0
- package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
- package/packages/daemon/src/tui/integrations/claude.ts +176 -0
- package/packages/daemon/src/tui/integrations/offer.ts +145 -0
- package/packages/daemon/src/tui/main.ts +82 -0
- package/packages/daemon/src/tui/mirror/ack-writer.ts +77 -0
- package/packages/daemon/src/tui/mirror/agent-chip.ts +97 -0
- package/packages/daemon/src/tui/mirror/agent-rows.ts +133 -0
- package/packages/daemon/src/tui/mirror/app-state.ts +179 -0
- package/packages/daemon/src/tui/mirror/app.tsx +5265 -0
- package/packages/daemon/src/tui/mirror/blit.ts +186 -0
- package/packages/daemon/src/tui/mirror/control-client.ts +214 -0
- package/packages/daemon/src/tui/mirror/control.ts +97 -0
- package/packages/daemon/src/tui/mirror/dialog-model.ts +298 -0
- package/packages/daemon/src/tui/mirror/dialog-stack.ts +354 -0
- package/packages/daemon/src/tui/mirror/diff-model.ts +112 -0
- package/packages/daemon/src/tui/mirror/editor-buffer.ts +117 -0
- package/packages/daemon/src/tui/mirror/file-tree.ts +97 -0
- package/packages/daemon/src/tui/mirror/focus-border.ts +57 -0
- package/packages/daemon/src/tui/mirror/folder-picker.ts +124 -0
- package/packages/daemon/src/tui/mirror/home-model.ts +174 -0
- package/packages/daemon/src/tui/mirror/input-coalescer.ts +105 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +187 -0
- package/packages/daemon/src/tui/mirror/palette.ts +274 -0
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +561 -0
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +415 -0
- package/packages/daemon/src/tui/mirror/perf-tap.ts +160 -0
- package/packages/daemon/src/tui/mirror/resize-model.ts +85 -0
- package/packages/daemon/src/tui/mirror/scrollbar-model.ts +88 -0
- package/packages/daemon/src/tui/mirror/search-model.ts +70 -0
- package/packages/daemon/src/tui/mirror/selection.ts +262 -0
- package/packages/daemon/src/tui/mirror/session-mirror.ts +443 -0
- package/packages/daemon/src/tui/mirror/settings-model.ts +345 -0
- package/packages/daemon/src/tui/mirror/size-truth.ts +77 -0
- package/packages/daemon/src/tui/mirror/spans.ts +46 -0
- package/packages/daemon/src/tui/mirror/status-grammar.ts +32 -0
- package/packages/daemon/src/tui/team/CONTROL.md +50 -0
- package/packages/daemon/src/tui/team/entry.ts +38 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
- package/packages/daemon/src/tui/team/home.ts +170 -0
- package/packages/daemon/src/tui/team/index.tsx +1521 -0
- package/packages/daemon/src/tui/team/input.ts +34 -0
- package/packages/daemon/src/tui/team/keymap.ts +127 -0
- package/packages/daemon/src/tui/team/mouse.ts +29 -0
- package/packages/daemon/src/tui/team/nav.ts +31 -0
- package/packages/daemon/src/tui/team/preview.ts +34 -0
- package/packages/daemon/src/tui/team/projects.ts +191 -0
- package/packages/daemon/src/tui/team/report.ts +83 -0
- package/packages/daemon/src/tui/team/sessions.ts +433 -0
- package/packages/daemon/src/tui/team/tree.ts +62 -0
- package/packages/daemon/src/types.ts +13 -0
- package/packages/daemon/src/ui/index.ts +32 -0
- package/packages/daemon/src/ui/terminal/index.ts +9 -0
- package/packages/daemon/src/ui/types.ts +91 -0
- package/packages/daemon/src/ui/web/base.css +80 -0
- package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
- package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
- package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
- package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
- package/packages/daemon/src/ui/web/hooks.ts +106 -0
- package/packages/daemon/src/ui/web/index.ts +27 -0
- package/packages/daemon/src/ui/web/render.ts +77 -0
- package/packages/daemon/src/ui/web/utils/color.ts +27 -0
- package/packages/daemon/src/validate.ts +217 -0
- package/packages/daemon/src/widgets/changes/README.md +3 -0
- package/packages/daemon/src/widgets/changes/index.tsx +691 -0
- package/packages/daemon/src/widgets/config/README.md +3 -0
- package/packages/daemon/src/widgets/config/index.tsx +481 -0
- package/packages/daemon/src/widgets/explorer/README.md +3 -0
- package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
- package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
- package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
- package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
- package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
- package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
- package/packages/daemon/src/widgets/lib/files.ts +88 -0
- package/packages/daemon/src/widgets/lib/git.ts +88 -0
- package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
- package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
- package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
- package/packages/daemon/src/widgets/lib/theme.ts +194 -0
- package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
- package/packages/daemon/src/widgets/preview/README.md +3 -0
- package/packages/daemon/src/widgets/preview/index.tsx +416 -0
- package/packages/daemon/src/widgets/resolve.ts +121 -0
- package/packages/daemon/src/widgets/setup/README.md +3 -0
- package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
- package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
- package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
- package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
- package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
- package/packages/daemon/src/widgets/setup/index.tsx +341 -0
- package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
- package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
- package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
- package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
- package/packages/tmux-bridge/package.json +22 -0
- package/packages/tmux-bridge/src/errors.ts +28 -0
- package/packages/tmux-bridge/src/index.ts +31 -0
- package/packages/tmux-bridge/src/monitor.ts +77 -0
- package/packages/tmux-bridge/src/panes.ts +136 -0
- package/packages/tmux-bridge/src/runner.test.ts +501 -0
- package/packages/tmux-bridge/src/runner.ts +91 -0
- package/packages/tmux-bridge/src/sessions.ts +126 -0
- package/packages/tmux-bridge/src/targeting.test.ts +107 -0
- package/packages/tmux-bridge/src/targeting.ts +90 -0
- package/scripts/build-tui.mjs +11 -4
- package/scripts/perf-mirror.mjs +313 -0
- package/scripts/postinstall.js +26 -2
- package/skill/SKILL.md +22 -0
- package/templates/AGENTS.md +14 -7
- package/templates/agent-team-monorepo.yml +8 -0
- package/templates/agent-team-nextjs.yml +8 -0
- package/templates/agent-team.yml +10 -0
- package/templates/convex.yml +2 -0
- package/templates/default.yml +11 -5
- package/templates/go.yml +4 -0
- package/templates/missions.yml +6 -0
- package/templates/nextjs.yml +4 -0
- package/templates/python.yml +4 -0
- package/templates/skills/backend.md +5 -12
- package/templates/skills/frontend.md +5 -12
- package/templates/skills/general-worker.md +5 -12
- package/templates/skills/researcher.md +7 -12
- package/templates/skills/reviewer.md +7 -16
- package/templates/vite.yml +4 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
3
|
+
import { extname, basename, resolve } from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { render, useKeyboard, useTerminalDimensions } from "@opentui/solid";
|
|
6
|
+
import { RGBA, TextAttributes } from "@opentui/core";
|
|
7
|
+
import { createSignal, createMemo, onCleanup, Show, For } from "solid-js";
|
|
8
|
+
import { createTheme, type WidgetTheme } from "../lib/theme.ts";
|
|
9
|
+
import { getAppConfig } from "../../lib/app-config.ts";
|
|
10
|
+
import { getFileDiff } from "../lib/git.ts";
|
|
11
|
+
|
|
12
|
+
const { values } = parseArgs({
|
|
13
|
+
options: {
|
|
14
|
+
session: { type: "string" },
|
|
15
|
+
dir: { type: "string" },
|
|
16
|
+
theme: { type: "string" },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const session = values.session ?? "";
|
|
21
|
+
const dir = values.dir ?? process.cwd();
|
|
22
|
+
const themeConfig = values.theme ? JSON.parse(values.theme) : undefined;
|
|
23
|
+
|
|
24
|
+
const MAX_PREVIEW_LINES = 500;
|
|
25
|
+
const BINARY_EXTENSIONS = new Set([
|
|
26
|
+
".png",
|
|
27
|
+
".jpg",
|
|
28
|
+
".jpeg",
|
|
29
|
+
".gif",
|
|
30
|
+
".bmp",
|
|
31
|
+
".ico",
|
|
32
|
+
".webp",
|
|
33
|
+
".woff",
|
|
34
|
+
".woff2",
|
|
35
|
+
".ttf",
|
|
36
|
+
".otf",
|
|
37
|
+
".eot",
|
|
38
|
+
".mp3",
|
|
39
|
+
".mp4",
|
|
40
|
+
".wav",
|
|
41
|
+
".ogg",
|
|
42
|
+
".webm",
|
|
43
|
+
".zip",
|
|
44
|
+
".tar",
|
|
45
|
+
".gz",
|
|
46
|
+
".bz2",
|
|
47
|
+
".7z",
|
|
48
|
+
".rar",
|
|
49
|
+
".pdf",
|
|
50
|
+
".exe",
|
|
51
|
+
".dll",
|
|
52
|
+
".so",
|
|
53
|
+
".dylib",
|
|
54
|
+
".node",
|
|
55
|
+
".wasm",
|
|
56
|
+
".tgz",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
function toRGBA(c: { r: number; g: number; b: number; a: number }): RGBA {
|
|
60
|
+
return RGBA.fromInts(c.r, c.g, c.b, c.a);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getPreviewFile(): string | null {
|
|
64
|
+
if (!session) return null;
|
|
65
|
+
try {
|
|
66
|
+
return (
|
|
67
|
+
execFileSync("tmux", ["show-option", "-t", session, "-v", "@preview_file"], {
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
70
|
+
}).trim() || null
|
|
71
|
+
);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatSize(bytes: number): string {
|
|
78
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
79
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
80
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const KEYWORD_RE =
|
|
84
|
+
/^(import|export|from|const|let|var|function|return|if|else|for|while|class|interface|type|async|await|try|catch|throw|new|switch|case|default|break|continue|enum|extends|implements|public|private|protected|static|readonly|abstract|override|declare|module|namespace|def|fn|pub|mut|use|mod|struct|impl|trait|match|loop|where|yield)\b/;
|
|
85
|
+
|
|
86
|
+
function getLineColor(
|
|
87
|
+
line: string,
|
|
88
|
+
theme: WidgetTheme,
|
|
89
|
+
): { r: number; g: number; b: number; a: number } {
|
|
90
|
+
const trimmed = line.trim();
|
|
91
|
+
if (!trimmed) return theme.fg;
|
|
92
|
+
|
|
93
|
+
// Comments
|
|
94
|
+
if (
|
|
95
|
+
trimmed.startsWith("//") ||
|
|
96
|
+
trimmed.startsWith("#") ||
|
|
97
|
+
trimmed.startsWith("--") ||
|
|
98
|
+
trimmed.startsWith("/*") ||
|
|
99
|
+
trimmed.startsWith("*")
|
|
100
|
+
) {
|
|
101
|
+
return theme.fgMuted;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Strings
|
|
105
|
+
if (trimmed.startsWith('"') || trimmed.startsWith("'") || trimmed.startsWith("`")) {
|
|
106
|
+
return theme.gitAdded;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Keywords
|
|
110
|
+
if (KEYWORD_RE.test(trimmed)) {
|
|
111
|
+
return theme.accent;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return theme.fg;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface FileData {
|
|
118
|
+
content: string;
|
|
119
|
+
totalLines: number;
|
|
120
|
+
binary: boolean;
|
|
121
|
+
size: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Parse diff to get line-level change info for gutter markers
|
|
125
|
+
function parseDiffLineMap(diff: string): Map<number, "added" | "modified"> {
|
|
126
|
+
const map = new Map<number, "added" | "modified">();
|
|
127
|
+
if (!diff) return map;
|
|
128
|
+
const lines = diff.split("\n");
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
// Parse hunk headers: @@ -oldStart,oldCount +newStart,newCount @@
|
|
131
|
+
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
132
|
+
if (hunkMatch) continue;
|
|
133
|
+
}
|
|
134
|
+
// Simpler approach: track added lines from the diff
|
|
135
|
+
let newLineNum = 0;
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
|
|
138
|
+
if (hunkMatch) {
|
|
139
|
+
newLineNum = parseInt(hunkMatch[1]!, 10);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff")) continue;
|
|
143
|
+
if (line.startsWith("+")) {
|
|
144
|
+
map.set(newLineNum, "added");
|
|
145
|
+
newLineNum++;
|
|
146
|
+
} else if (line.startsWith("-")) {
|
|
147
|
+
// Deleted line — mark the current position as modified
|
|
148
|
+
if (!map.has(newLineNum)) map.set(newLineNum, "modified");
|
|
149
|
+
} else {
|
|
150
|
+
newLineNum++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return map;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function loadFile(filePath: string): FileData | null {
|
|
157
|
+
const fullPath = filePath.startsWith("/") ? filePath : resolve(dir, filePath);
|
|
158
|
+
if (!existsSync(fullPath)) return null;
|
|
159
|
+
|
|
160
|
+
let size: string;
|
|
161
|
+
try {
|
|
162
|
+
size = formatSize(statSync(fullPath).size);
|
|
163
|
+
} catch {
|
|
164
|
+
size = "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (BINARY_EXTENSIONS.has(extname(fullPath).toLowerCase())) {
|
|
168
|
+
return { content: "(binary file)", totalLines: 1, binary: true, size };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const raw = readFileSync(fullPath, "utf-8");
|
|
172
|
+
if (raw.includes("\0")) {
|
|
173
|
+
return { content: "(binary file)", totalLines: 1, binary: true, size };
|
|
174
|
+
}
|
|
175
|
+
const allLines = raw.split("\n");
|
|
176
|
+
return {
|
|
177
|
+
content: allLines.slice(0, MAX_PREVIEW_LINES).join("\n"),
|
|
178
|
+
totalLines: allLines.length,
|
|
179
|
+
binary: false,
|
|
180
|
+
size,
|
|
181
|
+
};
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
render(
|
|
188
|
+
() => {
|
|
189
|
+
const theme = createTheme(themeConfig, getAppConfig().theme);
|
|
190
|
+
const dimensions = useTerminalDimensions();
|
|
191
|
+
const [filePath, setFilePath] = createSignal<string | null>(null);
|
|
192
|
+
const [fileContent, setFileContent] = createSignal<string | null>(null);
|
|
193
|
+
const [totalLines, setTotalLines] = createSignal(0);
|
|
194
|
+
const [fileSize, setFileSize] = createSignal("");
|
|
195
|
+
const [isBinary, setIsBinary] = createSignal(false);
|
|
196
|
+
const [fileDiff, setFileDiff] = createSignal<string | null>(null);
|
|
197
|
+
const [viewMode, setViewMode] = createSignal<"content" | "diff">("content");
|
|
198
|
+
|
|
199
|
+
// Poll tmux session variable for file path changes
|
|
200
|
+
const interval = setInterval(() => {
|
|
201
|
+
const newPath = getPreviewFile();
|
|
202
|
+
if (newPath !== filePath()) {
|
|
203
|
+
setFilePath(newPath);
|
|
204
|
+
setViewMode("content");
|
|
205
|
+
if (newPath) {
|
|
206
|
+
const result = loadFile(newPath);
|
|
207
|
+
if (result) {
|
|
208
|
+
setFileContent(result.content);
|
|
209
|
+
setTotalLines(result.totalLines);
|
|
210
|
+
setFileSize(result.size);
|
|
211
|
+
setIsBinary(result.binary);
|
|
212
|
+
} else {
|
|
213
|
+
setFileContent(null);
|
|
214
|
+
setTotalLines(0);
|
|
215
|
+
setFileSize("");
|
|
216
|
+
setIsBinary(false);
|
|
217
|
+
}
|
|
218
|
+
// Check for git diff
|
|
219
|
+
try {
|
|
220
|
+
const diff = getFileDiff(dir, newPath, false);
|
|
221
|
+
setFileDiff(diff || null);
|
|
222
|
+
} catch {
|
|
223
|
+
setFileDiff(null);
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
setFileContent(null);
|
|
227
|
+
setTotalLines(0);
|
|
228
|
+
setFileSize("");
|
|
229
|
+
setIsBinary(false);
|
|
230
|
+
setFileDiff(null);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}, 200);
|
|
234
|
+
|
|
235
|
+
onCleanup(() => clearInterval(interval));
|
|
236
|
+
|
|
237
|
+
const lines = createMemo(() => {
|
|
238
|
+
const content = fileContent();
|
|
239
|
+
if (!content) return [];
|
|
240
|
+
return content.split("\n");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const fileExt = createMemo(() => {
|
|
244
|
+
const fp = filePath();
|
|
245
|
+
return fp ? extname(fp).toLowerCase() : "";
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const lineNumWidth = createMemo(() => Math.max(3, String(totalLines()).length));
|
|
249
|
+
|
|
250
|
+
const diffLineMap = createMemo(() => parseDiffLineMap(fileDiff() ?? ""));
|
|
251
|
+
|
|
252
|
+
useKeyboard((evt) => {
|
|
253
|
+
if (evt.name === "d") {
|
|
254
|
+
if (fileDiff()) setViewMode((m) => (m === "content" ? "diff" : "content"));
|
|
255
|
+
evt.preventDefault();
|
|
256
|
+
} else if (evt.name === "r" && filePath()) {
|
|
257
|
+
const result = loadFile(filePath()!);
|
|
258
|
+
if (result) {
|
|
259
|
+
setFileContent(result.content);
|
|
260
|
+
setTotalLines(result.totalLines);
|
|
261
|
+
setFileSize(result.size);
|
|
262
|
+
setIsBinary(result.binary);
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const diff = getFileDiff(dir, filePath()!, false);
|
|
266
|
+
setFileDiff(diff || null);
|
|
267
|
+
} catch {
|
|
268
|
+
setFileDiff(null);
|
|
269
|
+
}
|
|
270
|
+
evt.preventDefault();
|
|
271
|
+
} else if (evt.name === "q") {
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<box
|
|
278
|
+
width={dimensions().width}
|
|
279
|
+
height={dimensions().height}
|
|
280
|
+
backgroundColor={toRGBA(theme.bg)}
|
|
281
|
+
>
|
|
282
|
+
{/* Header */}
|
|
283
|
+
<Show
|
|
284
|
+
when={filePath()}
|
|
285
|
+
fallback={
|
|
286
|
+
<box flexGrow={1} paddingLeft={2} paddingTop={2}>
|
|
287
|
+
<text fg={toRGBA(theme.fgMuted)}>Select a file in the explorer</text>
|
|
288
|
+
<text fg={toRGBA(theme.border)} paddingTop={1}>
|
|
289
|
+
Navigate with ↑↓ keys
|
|
290
|
+
</text>
|
|
291
|
+
<text fg={toRGBA(theme.border)}>Preview updates automatically</text>
|
|
292
|
+
</box>
|
|
293
|
+
}
|
|
294
|
+
>
|
|
295
|
+
<box flexShrink={0} paddingLeft={1} flexDirection="row" gap={2}>
|
|
296
|
+
<text fg={toRGBA(theme.accent)} attributes={TextAttributes.BOLD}>
|
|
297
|
+
{basename(filePath()!)}
|
|
298
|
+
</text>
|
|
299
|
+
<Show when={!isBinary()}>
|
|
300
|
+
<text fg={toRGBA(theme.fgMuted)}>{totalLines()} lines</text>
|
|
301
|
+
</Show>
|
|
302
|
+
<text fg={toRGBA(theme.fgMuted)}>{fileSize()}</text>
|
|
303
|
+
<Show when={fileDiff()}>
|
|
304
|
+
{(() => {
|
|
305
|
+
const lines = fileDiff()!.split("\n");
|
|
306
|
+
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
307
|
+
const removed = lines.filter(
|
|
308
|
+
(l) => l.startsWith("-") && !l.startsWith("---"),
|
|
309
|
+
).length;
|
|
310
|
+
return (
|
|
311
|
+
<box flexDirection="row" gap={1}>
|
|
312
|
+
<Show when={added > 0}>
|
|
313
|
+
<text fg={toRGBA(theme.gitAdded)}>+{added}</text>
|
|
314
|
+
</Show>
|
|
315
|
+
<Show when={removed > 0}>
|
|
316
|
+
<text fg={toRGBA(theme.gitDeleted)}>-{removed}</text>
|
|
317
|
+
</Show>
|
|
318
|
+
<text fg={toRGBA(viewMode() === "diff" ? theme.gitModified : theme.fgMuted)}>
|
|
319
|
+
{viewMode() === "diff" ? "[diff]" : "[d:diff]"}
|
|
320
|
+
</text>
|
|
321
|
+
</box>
|
|
322
|
+
);
|
|
323
|
+
})()}
|
|
324
|
+
</Show>
|
|
325
|
+
</box>
|
|
326
|
+
|
|
327
|
+
{/* Separator */}
|
|
328
|
+
<box flexShrink={0} height={1}>
|
|
329
|
+
<text fg={toRGBA(theme.border)} wrapMode="none">
|
|
330
|
+
{"─".repeat(dimensions().width)}
|
|
331
|
+
</text>
|
|
332
|
+
</box>
|
|
333
|
+
|
|
334
|
+
{/* Diff view */}
|
|
335
|
+
<Show when={viewMode() === "diff" && fileDiff()}>
|
|
336
|
+
<scrollbox flexGrow={1}>
|
|
337
|
+
<For each={fileDiff()!.split("\n")}>
|
|
338
|
+
{(line) => {
|
|
339
|
+
const color = line.startsWith("+")
|
|
340
|
+
? theme.diffAdded
|
|
341
|
+
: line.startsWith("-")
|
|
342
|
+
? theme.diffRemoved
|
|
343
|
+
: line.startsWith("@@")
|
|
344
|
+
? theme.diffHunk
|
|
345
|
+
: theme.diffContext;
|
|
346
|
+
const bg = line.startsWith("+")
|
|
347
|
+
? theme.diffAddedBg
|
|
348
|
+
: line.startsWith("-")
|
|
349
|
+
? theme.diffRemovedBg
|
|
350
|
+
: theme.diffContextBg;
|
|
351
|
+
return (
|
|
352
|
+
<box backgroundColor={toRGBA(bg)}>
|
|
353
|
+
<text fg={toRGBA(color)} wrapMode="none">
|
|
354
|
+
{line || " "}
|
|
355
|
+
</text>
|
|
356
|
+
</box>
|
|
357
|
+
);
|
|
358
|
+
}}
|
|
359
|
+
</For>
|
|
360
|
+
</scrollbox>
|
|
361
|
+
</Show>
|
|
362
|
+
|
|
363
|
+
{/* Content view */}
|
|
364
|
+
<Show when={viewMode() === "content" && fileContent()}>
|
|
365
|
+
<scrollbox flexGrow={1}>
|
|
366
|
+
<For each={lines()}>
|
|
367
|
+
{(line, lineNum) => {
|
|
368
|
+
const color = isBinary() ? theme.fgMuted : getLineColor(line, theme);
|
|
369
|
+
const lineNumber = lineNum() + 1;
|
|
370
|
+
const changeType = diffLineMap().get(lineNumber);
|
|
371
|
+
const gutterColor =
|
|
372
|
+
changeType === "added"
|
|
373
|
+
? theme.gitAdded
|
|
374
|
+
: changeType === "modified"
|
|
375
|
+
? theme.gitModified
|
|
376
|
+
: null;
|
|
377
|
+
const gutterChar = gutterColor ? "│" : " ";
|
|
378
|
+
return (
|
|
379
|
+
<box flexDirection="row">
|
|
380
|
+
<Show when={!isBinary()}>
|
|
381
|
+
<text
|
|
382
|
+
fg={toRGBA(gutterColor ?? theme.diffLineNumber)}
|
|
383
|
+
flexShrink={0}
|
|
384
|
+
wrapMode="none"
|
|
385
|
+
>
|
|
386
|
+
{gutterChar}
|
|
387
|
+
{String(lineNumber).padStart(lineNumWidth())}{" "}
|
|
388
|
+
</text>
|
|
389
|
+
</Show>
|
|
390
|
+
<text fg={toRGBA(color)} wrapMode="none">
|
|
391
|
+
{line || " "}
|
|
392
|
+
</text>
|
|
393
|
+
</box>
|
|
394
|
+
);
|
|
395
|
+
}}
|
|
396
|
+
</For>
|
|
397
|
+
</scrollbox>
|
|
398
|
+
</Show>
|
|
399
|
+
|
|
400
|
+
{/* Footer */}
|
|
401
|
+
<box flexShrink={0} paddingLeft={1}>
|
|
402
|
+
<text fg={toRGBA(theme.fgMuted)} wrapMode="none">
|
|
403
|
+
d:diff view r:refresh q:quit
|
|
404
|
+
</text>
|
|
405
|
+
</box>
|
|
406
|
+
</Show>
|
|
407
|
+
</box>
|
|
408
|
+
);
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
targetFps: 30,
|
|
412
|
+
exitOnCtrlC: true,
|
|
413
|
+
useKittyKeyboard: {},
|
|
414
|
+
autoFocus: false,
|
|
415
|
+
},
|
|
416
|
+
);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolve, dirname } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { ThemeConfig } from "../types.ts";
|
|
5
|
+
import { shellEscape } from "../lib/shell.ts";
|
|
6
|
+
import { resolveTuiLaunch, findCompiledTui, isBunAvailable } from "../tui/compiled.ts";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
interface WidgetOptions {
|
|
11
|
+
session: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
target: string | null;
|
|
14
|
+
theme: ThemeConfig | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The .tsx extension is used at runtime; Bun handles JSX via preload plugin.
|
|
18
|
+
// Exactly the widgets that exist — a stale entry here becomes a dead
|
|
19
|
+
// `Module not found` pane at launch time.
|
|
20
|
+
const WIDGET_ENTRY_POINTS: Record<string, string> = {
|
|
21
|
+
explorer: "explorer/index.tsx",
|
|
22
|
+
changes: "changes/index.tsx",
|
|
23
|
+
preview: "preview/index.tsx",
|
|
24
|
+
setup: "setup/index.tsx",
|
|
25
|
+
config: "config/index.tsx",
|
|
26
|
+
sidebar: "sidebar/index.tsx",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a widget entry to an absolute path that works from BOTH runtimes:
|
|
31
|
+
* unbundled (this file lives in packages/daemon/src/widgets — entries are
|
|
32
|
+
* siblings) and the esbuild bundle (import.meta.url collapses to bin/cli.js,
|
|
33
|
+
* so entries live at ../packages/daemon/src/widgets from there). Probing with
|
|
34
|
+
* existsSync keeps one code path honest instead of guessing by environment.
|
|
35
|
+
*/
|
|
36
|
+
function widgetEntryPath(entry: string): string {
|
|
37
|
+
const sibling = resolve(__dirname, entry);
|
|
38
|
+
if (existsSync(sibling)) return sibling;
|
|
39
|
+
return resolve(__dirname, "../packages/daemon/src/widgets", entry);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The tmux-ide repo root — where bunfig.toml (the JSX preload) lives. Widgets
|
|
44
|
+
* must be SPAWNED from here regardless of which project they render (the
|
|
45
|
+
* project dir travels via --dir); cd-ing to the project instead crashes bun
|
|
46
|
+
* with "Cannot find module react/jsx-dev-runtime" outside this repo.
|
|
47
|
+
*/
|
|
48
|
+
const REPO_ROOT = existsSync(resolve(__dirname, "explorer/index.tsx"))
|
|
49
|
+
? resolve(__dirname, "../../../..") // unbundled: src/widgets → repo root
|
|
50
|
+
: resolve(__dirname, ".."); // bundled: bin → repo root
|
|
51
|
+
|
|
52
|
+
function widgetArgs(opts: WidgetOptions): string[] {
|
|
53
|
+
const args = [`--session=${opts.session}`, `--dir=${opts.dir}`];
|
|
54
|
+
if (opts.target) args.push(`--target=${opts.target}`);
|
|
55
|
+
if (opts.theme) args.push(`--theme=${JSON.stringify(opts.theme)}`);
|
|
56
|
+
return args;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveWidgetCommand(type: string, opts: WidgetOptions): string {
|
|
60
|
+
const entry = WIDGET_ENTRY_POINTS[type];
|
|
61
|
+
if (!entry) throw new Error(`Unknown widget type: ${type}`);
|
|
62
|
+
|
|
63
|
+
const scriptPath = widgetEntryPath(entry);
|
|
64
|
+
const launch = resolveTuiLaunch({
|
|
65
|
+
surface: type,
|
|
66
|
+
scriptPath,
|
|
67
|
+
args: widgetArgs(opts),
|
|
68
|
+
checkoutExists: existsSync(scriptPath),
|
|
69
|
+
bunAvailable: isBunAvailable(),
|
|
70
|
+
compiledBinary: findCompiledTui(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (launch.mode === "unavailable") {
|
|
74
|
+
throw new Error(`Cannot launch ${type} widget: ${launch.reasons.join("; ")}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const escapedArgs = launch.argv.map(shellEscape).join(" ");
|
|
78
|
+
if (launch.mode === "bun") {
|
|
79
|
+
// cd to the tmux-ide REPO root (not the project) so bunfig.toml's JSX
|
|
80
|
+
// preload is found; the project dir rides in via --dir.
|
|
81
|
+
return `cd ${shellEscape(REPO_ROOT)} && bun ${escapedArgs}`;
|
|
82
|
+
}
|
|
83
|
+
// Compiled binary: no bunfig to find (JSX + native dylib are baked in). Run
|
|
84
|
+
// from the pane's dir so a stray repo-root bunfig can't hijack the preload.
|
|
85
|
+
return `cd ${shellEscape(opts.dir)} && ${shellEscape(launch.bin)} ${escapedArgs}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface WidgetSpawnSpec {
|
|
89
|
+
cwd: string;
|
|
90
|
+
cmd: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Structured form of `resolveWidgetCommand` for direct PTY spawning. Callers
|
|
95
|
+
* that own the cwd separately (e.g. the /ws/pty bridge accepting cwd in its
|
|
96
|
+
* init frame) avoid the shell hop and get exec-style argv.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveWidgetSpawn(type: string, opts: WidgetOptions): WidgetSpawnSpec {
|
|
99
|
+
const entry = WIDGET_ENTRY_POINTS[type];
|
|
100
|
+
if (!entry) throw new Error(`Unknown widget type: ${type}`);
|
|
101
|
+
|
|
102
|
+
const scriptPath = widgetEntryPath(entry);
|
|
103
|
+
const launch = resolveTuiLaunch({
|
|
104
|
+
surface: type,
|
|
105
|
+
scriptPath,
|
|
106
|
+
args: widgetArgs(opts),
|
|
107
|
+
checkoutExists: existsSync(scriptPath),
|
|
108
|
+
bunAvailable: isBunAvailable(),
|
|
109
|
+
compiledBinary: findCompiledTui(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (launch.mode === "unavailable") {
|
|
113
|
+
throw new Error(`Cannot launch ${type} widget: ${launch.reasons.join("; ")}`);
|
|
114
|
+
}
|
|
115
|
+
// Bun spawns from the repo root (bunfig preload); the binary spawns from the
|
|
116
|
+
// pane's dir (self-contained, avoids a stray bunfig preload).
|
|
117
|
+
const cwd = launch.mode === "bun" ? REPO_ROOT : opts.dir;
|
|
118
|
+
return { cwd, cmd: [launch.bin, ...launch.argv] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const WIDGET_TYPES = Object.keys(WIDGET_ENTRY_POINTS);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createSignal, onMount } from "solid-js";
|
|
2
|
+
import { RGBA, TextAttributes, type InputRenderable } from "@opentui/core";
|
|
3
|
+
import { useKeyboard } from "@opentui/solid";
|
|
4
|
+
import type { IdeConfig } from "../../schemas/ide-config.ts";
|
|
5
|
+
import type { WidgetTheme, RGBA as RGBAType } from "../lib/theme.ts";
|
|
6
|
+
|
|
7
|
+
function toRGBA(c: RGBAType): RGBA {
|
|
8
|
+
return RGBA.fromInts(c.r, c.g, c.b, c.a);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Extract titles of claude panes from an IdeConfig. */
|
|
12
|
+
function extractClaudePaneTitles(config: IdeConfig): string[] {
|
|
13
|
+
const titles: string[] = [];
|
|
14
|
+
for (const row of config.rows) {
|
|
15
|
+
for (const pane of row.panes) {
|
|
16
|
+
if (pane.command === "claude") {
|
|
17
|
+
titles.push(pane.title ?? "Claude");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return titles;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AgentNamingProps {
|
|
25
|
+
config: IdeConfig;
|
|
26
|
+
onContinue: (names: string[]) => void;
|
|
27
|
+
theme: WidgetTheme;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AgentNaming(props: AgentNamingProps) {
|
|
31
|
+
const defaults = extractClaudePaneTitles(props.config);
|
|
32
|
+
const theme = props.theme;
|
|
33
|
+
|
|
34
|
+
// Create a signal per pane name
|
|
35
|
+
const nameSignals = defaults.map((d) => createSignal(d));
|
|
36
|
+
const [activeField, setActiveField] = createSignal(0);
|
|
37
|
+
|
|
38
|
+
const inputRefs: (InputRenderable | undefined)[] = new Array(defaults.length).fill(undefined);
|
|
39
|
+
|
|
40
|
+
onMount(() => {
|
|
41
|
+
setTimeout(() => inputRefs[0]?.focus(), 50);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
useKeyboard((evt) => {
|
|
45
|
+
if (evt.name === "tab") {
|
|
46
|
+
const next = (activeField() + 1) % nameSignals.length;
|
|
47
|
+
setActiveField(next);
|
|
48
|
+
setTimeout(() => inputRefs[next]?.focus(), 10);
|
|
49
|
+
evt.preventDefault();
|
|
50
|
+
} else if (evt.name === "return") {
|
|
51
|
+
const names = nameSignals.map(([getter]) => getter());
|
|
52
|
+
props.onContinue(names);
|
|
53
|
+
evt.preventDefault();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<box paddingLeft={1} paddingRight={1}>
|
|
59
|
+
{/* Header */}
|
|
60
|
+
<box flexShrink={0} paddingBottom={1}>
|
|
61
|
+
<text fg={toRGBA(theme.accent)} attributes={TextAttributes.BOLD}>
|
|
62
|
+
Name Your Agents
|
|
63
|
+
</text>
|
|
64
|
+
<text fg={toRGBA(theme.fgMuted)}>Customize the names for each Claude pane.</text>
|
|
65
|
+
</box>
|
|
66
|
+
|
|
67
|
+
{/* Input fields */}
|
|
68
|
+
{nameSignals.map(([getter, setter], index) => {
|
|
69
|
+
const isActive = () => activeField() === index;
|
|
70
|
+
return (
|
|
71
|
+
<box
|
|
72
|
+
flexShrink={0}
|
|
73
|
+
paddingBottom={1}
|
|
74
|
+
onMouseDown={() => {
|
|
75
|
+
setActiveField(index);
|
|
76
|
+
setTimeout(() => inputRefs[index]?.focus(), 10);
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<text fg={toRGBA(isActive() ? theme.accent : theme.fgMuted)}>Pane {index + 1}</text>
|
|
80
|
+
<input
|
|
81
|
+
value={getter()}
|
|
82
|
+
placeholder={defaults[index] ?? "Agent"}
|
|
83
|
+
onInput={(v: string) => setter(v)}
|
|
84
|
+
focusedBackgroundColor={toRGBA(theme.selected)}
|
|
85
|
+
cursorColor={toRGBA(theme.accent)}
|
|
86
|
+
focusedTextColor={toRGBA(theme.fg)}
|
|
87
|
+
ref={(r: InputRenderable) => {
|
|
88
|
+
inputRefs[index] = r;
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</box>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
|
|
95
|
+
{/* Spacer */}
|
|
96
|
+
<box flexGrow={1} />
|
|
97
|
+
|
|
98
|
+
{/* Footer */}
|
|
99
|
+
<box flexShrink={0}>
|
|
100
|
+
<box flexShrink={0} height={1}>
|
|
101
|
+
<text fg={toRGBA(theme.border)} wrapMode="none">
|
|
102
|
+
{"─".repeat(40)}
|
|
103
|
+
</text>
|
|
104
|
+
</box>
|
|
105
|
+
<box flexDirection="row" gap={2}>
|
|
106
|
+
<text fg={toRGBA(theme.fgMuted)}>Tab:next field</text>
|
|
107
|
+
<text fg={toRGBA(theme.fgMuted)}>Enter:continue</text>
|
|
108
|
+
</box>
|
|
109
|
+
</box>
|
|
110
|
+
</box>
|
|
111
|
+
);
|
|
112
|
+
}
|