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,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data layer for the team TUI.
|
|
3
|
+
*
|
|
4
|
+
* Enumerates every live tmux session and rolls each one up to an agent
|
|
5
|
+
* status. Detection is TWO-LAYER: a fresh `@agent_state` pane option —
|
|
6
|
+
* written by a lifecycle-hook integration (`tmux-ide integration install
|
|
7
|
+
* claude`) or any self-reporting agent — is AUTHORITATIVE; only panes
|
|
8
|
+
* without authority fall back to snapshot scraping (pick a manifest by the
|
|
9
|
+
* pane command, capture, classify, fold through the persistent
|
|
10
|
+
* `StatusTracker` so the cross-tick `done` surfaces). Pane statuses roll up
|
|
11
|
+
* to a single session status.
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import {
|
|
15
|
+
classifyInstant,
|
|
16
|
+
parseAuthority,
|
|
17
|
+
parseAuthorityEpoch,
|
|
18
|
+
type AgentStatus,
|
|
19
|
+
type StatusTracker,
|
|
20
|
+
} from "../detect/classify.ts";
|
|
21
|
+
import type { AgentManifest, ManifestConfidence } from "../detect/manifest.ts";
|
|
22
|
+
import { readProcessTable, resolveAgentCommand } from "../detect/process-tree.ts";
|
|
23
|
+
import { readPaneSnapshot } from "../detect/snapshot.ts";
|
|
24
|
+
|
|
25
|
+
export interface TeamSession {
|
|
26
|
+
name: string;
|
|
27
|
+
attached: boolean;
|
|
28
|
+
windows: number;
|
|
29
|
+
panes: number;
|
|
30
|
+
status: AgentStatus;
|
|
31
|
+
/**
|
|
32
|
+
* Per-window (tmux tab) rollup: one entry per window the session owns, in
|
|
33
|
+
* ascending window-index order. The `windows` count above stays for compat;
|
|
34
|
+
* this is the richer breakdown the switcher/cockpit navigate.
|
|
35
|
+
*/
|
|
36
|
+
windowList: TeamWindow[];
|
|
37
|
+
/**
|
|
38
|
+
* Per-pane agent detail — one entry for every pane the two-layer detection
|
|
39
|
+
* classifies as an agent (a manifest resolved and it isn't the `shell`
|
|
40
|
+
* catch-all). A FLAT list across the session's windows; each entry carries
|
|
41
|
+
* its own `windowIndex`, so a consumer that wants a per-window view groups by
|
|
42
|
+
* it. ADDITIVE: the rollup fields above (`status`, `windowList`, `panes`) are
|
|
43
|
+
* unchanged — this only SURFACES per-pane truth the rollup already computes.
|
|
44
|
+
* Optional so pre-existing constructors stay valid; {@link listTeamSessions}
|
|
45
|
+
* always populates it (possibly empty).
|
|
46
|
+
*/
|
|
47
|
+
agents?: PaneAgentEntry[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Per-pane agent detail surfaced alongside a session's rollup. Emitted only for
|
|
52
|
+
* panes that resolve to a real agent (a manifest, excluding the `shell`
|
|
53
|
+
* catch-all). Everything here is already computed during the status rollup —
|
|
54
|
+
* this record just keeps it instead of discarding it.
|
|
55
|
+
*/
|
|
56
|
+
export interface PaneAgentEntry {
|
|
57
|
+
/** tmux pane id, e.g. `%5`. */
|
|
58
|
+
paneId: string;
|
|
59
|
+
/** `window_index` of the window (tab) this pane lives in. */
|
|
60
|
+
windowIndex: number;
|
|
61
|
+
/** Owning session name (repeated per entry so a flattened list stays keyed). */
|
|
62
|
+
session: string;
|
|
63
|
+
/** The resolved agent kind — the manifest id (`claude`, `codex`, …). */
|
|
64
|
+
kind: string;
|
|
65
|
+
/** Final per-pane status (authority when fresh, else scraped/tracked). */
|
|
66
|
+
state: AgentStatus;
|
|
67
|
+
/** The manifest's evidence confidence (`conservative` when the manifest omits it). */
|
|
68
|
+
confidence: ManifestConfidence;
|
|
69
|
+
/**
|
|
70
|
+
* The authority state's epoch stamp when the AUTHORITY layer provided the
|
|
71
|
+
* state (`@agent_state` = `"<state>:<epoch>"`); null for a scraped/tracked
|
|
72
|
+
* pane, which has no authoritative timestamp.
|
|
73
|
+
*/
|
|
74
|
+
since: number | null;
|
|
75
|
+
/** `pane_title`. */
|
|
76
|
+
title: string;
|
|
77
|
+
/** `pane_current_command` — the pane's immediate process (often node/bun/sh). */
|
|
78
|
+
command: string;
|
|
79
|
+
/** `pane_current_path` — the pane's working directory. */
|
|
80
|
+
dir: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build a {@link PaneAgentEntry} from a classified pane — PURE. Returns null
|
|
85
|
+
* for a NON-agent pane (no manifest resolved, or the `shell` catch-all), which
|
|
86
|
+
* gets no entry. `since` is threaded from the caller (the authority epoch when
|
|
87
|
+
* the authority layer supplied the state, else null). No tmux/`ps` access —
|
|
88
|
+
* every input is already resolved by the rollup.
|
|
89
|
+
*/
|
|
90
|
+
export function buildAgentEntry(input: {
|
|
91
|
+
sessionName: string;
|
|
92
|
+
pane: Pick<PaneRecord, "id" | "windowIndex" | "title" | "cmd" | "dir">;
|
|
93
|
+
manifest: AgentManifest | undefined;
|
|
94
|
+
state: AgentStatus;
|
|
95
|
+
since: number | null;
|
|
96
|
+
}): PaneAgentEntry | null {
|
|
97
|
+
const { manifest, pane } = input;
|
|
98
|
+
if (!manifest || manifest.id === "shell") return null;
|
|
99
|
+
return {
|
|
100
|
+
paneId: pane.id,
|
|
101
|
+
windowIndex: pane.windowIndex,
|
|
102
|
+
session: input.sessionName,
|
|
103
|
+
kind: manifest.id,
|
|
104
|
+
state: input.state,
|
|
105
|
+
confidence: manifest.confidence ?? "conservative",
|
|
106
|
+
since: input.since,
|
|
107
|
+
title: pane.title,
|
|
108
|
+
command: pane.cmd,
|
|
109
|
+
dir: pane.dir,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A single tmux window (tab) within a session, with its panes' statuses rolled
|
|
115
|
+
* up to one status (highest severity wins, like a session rollup).
|
|
116
|
+
*/
|
|
117
|
+
export interface TeamWindow {
|
|
118
|
+
/** `window_index` — tmux's own index (may be non-contiguous). */
|
|
119
|
+
index: number;
|
|
120
|
+
/** `window_name`. */
|
|
121
|
+
name: string;
|
|
122
|
+
/** Whether this is the session's active (foreground) window. */
|
|
123
|
+
active: boolean;
|
|
124
|
+
/** How many panes the window owns. */
|
|
125
|
+
panes: number;
|
|
126
|
+
/** Rolled-up status of the window's panes. */
|
|
127
|
+
status: AgentStatus;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A single pane's resolved detail, handed to {@link ListTeamSessionsOpts.onPane}
|
|
132
|
+
* as each pane is classified. `agent` is the resolved agent id, or `null` for a
|
|
133
|
+
* non-agent pane (no manifest, or the `shell` catch-all — those get no chip).
|
|
134
|
+
* `status` is the final status (authority when fresh, else scraped/tracked).
|
|
135
|
+
*/
|
|
136
|
+
export interface PaneDetail {
|
|
137
|
+
sessionName: string;
|
|
138
|
+
paneId: string;
|
|
139
|
+
agent: string | null;
|
|
140
|
+
status: AgentStatus;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface PaneRecord {
|
|
144
|
+
/** tmux pane id, e.g. `%5`. */
|
|
145
|
+
id: string;
|
|
146
|
+
/** `pane_pid` — the pane's immediate process, root of its process tree. */
|
|
147
|
+
pid: number;
|
|
148
|
+
/** `pane_current_command`. */
|
|
149
|
+
cmd: string;
|
|
150
|
+
/** `pane_title`. */
|
|
151
|
+
title: string;
|
|
152
|
+
/** `pane_current_path` — the pane's working directory. */
|
|
153
|
+
dir: string;
|
|
154
|
+
/** Raw `@agent_state` pane option (authority layer), if set. */
|
|
155
|
+
authority: string;
|
|
156
|
+
/** Raw `@agent_hint` pane option — forces a manifest when set. */
|
|
157
|
+
hint: string;
|
|
158
|
+
/** `window_index` — the window (tab) this pane lives in. */
|
|
159
|
+
windowIndex: number;
|
|
160
|
+
/** `window_name`. */
|
|
161
|
+
windowName: string;
|
|
162
|
+
/** `window_active` — whether this pane's window is the session's active one. */
|
|
163
|
+
windowActive: boolean;
|
|
164
|
+
/** `@tmux_ide_sidebar` — set on the app's own nav-column pane. Such panes are
|
|
165
|
+
* chrome, not agents, so they're excluded from the status rollup entirely. */
|
|
166
|
+
sidebar: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The pane option that marks the app's sidebar (nav-column) pane. */
|
|
170
|
+
export const SIDEBAR_PANE_OPTION = "@tmux_ide_sidebar";
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* PURE — drop the app's own sidebar panes from a session's pane list. The
|
|
174
|
+
* sidebar is chrome (it renders the fleet nav column), so it must never fold
|
|
175
|
+
* into the session's agent-status rollup, pane count, or window breakdown.
|
|
176
|
+
*/
|
|
177
|
+
export function excludeSidebarPanes<T extends { sidebar: boolean }>(panes: T[]): T[] {
|
|
178
|
+
return panes.filter((pane) => !pane.sidebar);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Severity order — highest present status wins in a rollup. */
|
|
182
|
+
const SEVERITY: AgentStatus[] = ["blocked", "working", "done", "idle", "unknown"];
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Whether a session should appear in the switcher. Any `_`-prefixed session is
|
|
186
|
+
* internal plumbing (the `_tmux-ide-chrome` updater, scratch sessions, …) and
|
|
187
|
+
* is filtered out so the cockpit never lists — or navigates into — infrastructure.
|
|
188
|
+
*/
|
|
189
|
+
export function isListableSession(name: string): boolean {
|
|
190
|
+
return !name.startsWith("_");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function tmux(args: string[]): string {
|
|
194
|
+
try {
|
|
195
|
+
return execFileSync("tmux", args, {
|
|
196
|
+
encoding: "utf8",
|
|
197
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
198
|
+
}).trim();
|
|
199
|
+
} catch {
|
|
200
|
+
return "";
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Options for {@link listTeamSessions}. */
|
|
205
|
+
export interface ListTeamSessionsOpts {
|
|
206
|
+
/**
|
|
207
|
+
* Name of the currently-attached/viewed session, if any — its panes are
|
|
208
|
+
* marked seen (acknowledging any pending `done`).
|
|
209
|
+
*/
|
|
210
|
+
viewed?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Per-pane sink, invoked once per pane with its resolved agent + final
|
|
213
|
+
* status as it's classified. Lets a caller (the chrome updater) recover the
|
|
214
|
+
* per-pane truth the rollup throws away, WITHOUT a second scan. When absent,
|
|
215
|
+
* the extra manifest resolution for authority panes is skipped — existing
|
|
216
|
+
* callers see zero behavior change.
|
|
217
|
+
*/
|
|
218
|
+
onPane?: (pane: PaneDetail) => void;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* List every live tmux session with a rolled-up agent status.
|
|
223
|
+
*
|
|
224
|
+
* @param tracker Persistent status tracker threaded across refreshes so the
|
|
225
|
+
* cross-tick `done` state can be inferred.
|
|
226
|
+
* @param opts See {@link ListTeamSessionsOpts}.
|
|
227
|
+
*/
|
|
228
|
+
export function listTeamSessions(
|
|
229
|
+
tracker: StatusTracker,
|
|
230
|
+
opts: ListTeamSessionsOpts = {},
|
|
231
|
+
): TeamSession[] {
|
|
232
|
+
const raw = tmux([
|
|
233
|
+
"list-sessions",
|
|
234
|
+
"-F",
|
|
235
|
+
"#{session_name}\t#{session_attached}\t#{session_windows}",
|
|
236
|
+
]);
|
|
237
|
+
if (!raw) return [];
|
|
238
|
+
|
|
239
|
+
const panesBySession = collectPanes();
|
|
240
|
+
// One `ps` read per call — a single ~10-20ms syscall shared across every
|
|
241
|
+
// fallback pane, so the manifest lookup can walk each pane's process tree
|
|
242
|
+
// instead of matching on the (usually generic) pane_current_command.
|
|
243
|
+
const processTable = readProcessTable();
|
|
244
|
+
|
|
245
|
+
return raw
|
|
246
|
+
.split("\n")
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.filter((line) => isListableSession(line.split("\t")[0] ?? ""))
|
|
249
|
+
.map((line) => {
|
|
250
|
+
const [name = "", attached = "", windows = "0"] = line.split("\t");
|
|
251
|
+
// Sidebar panes are chrome, not agents — drop them before any rollup so
|
|
252
|
+
// they never pollute the session status, pane count, or window list.
|
|
253
|
+
const panes = excludeSidebarPanes(panesBySession.get(name) ?? []);
|
|
254
|
+
const seen = opts.viewed === name;
|
|
255
|
+
|
|
256
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
257
|
+
const agents: PaneAgentEntry[] = [];
|
|
258
|
+
const statuses = panes.map((pane) => {
|
|
259
|
+
// AUTHORITY first: a fresh hook-reported state outranks scraping.
|
|
260
|
+
const authority = parseAuthority(pane.authority, nowSec);
|
|
261
|
+
let status: AgentStatus;
|
|
262
|
+
// The agent kind is needed for BOTH the chip (onPane) and the per-pane
|
|
263
|
+
// agent entry, so the manifest is resolved for every pane. It's cheap —
|
|
264
|
+
// the process table is already loaded, and authority panes take NO
|
|
265
|
+
// capture-pane round-trip.
|
|
266
|
+
let manifest: AgentManifest | undefined;
|
|
267
|
+
// The authority state's own timestamp (its "since"); only fresh
|
|
268
|
+
// authority states carry one — scraped panes have no stamp.
|
|
269
|
+
let since: number | null = null;
|
|
270
|
+
if (authority !== null) {
|
|
271
|
+
since = parseAuthorityEpoch(pane.authority);
|
|
272
|
+
if (authority === "done" && seen) {
|
|
273
|
+
// Viewing acknowledges a finished agent — persist the ack so the
|
|
274
|
+
// pane doesn't flip back to done on the next tick.
|
|
275
|
+
ackDone(pane.id, nowSec);
|
|
276
|
+
status = "idle";
|
|
277
|
+
} else {
|
|
278
|
+
status = authority;
|
|
279
|
+
}
|
|
280
|
+
manifest = resolveAgentCommand(pane.cmd, pane.pid, processTable, {
|
|
281
|
+
hint: pane.hint,
|
|
282
|
+
}).manifest;
|
|
283
|
+
} else {
|
|
284
|
+
// FALLBACK: snapshot scraping. Resolve the real agent from the pane's
|
|
285
|
+
// process tree (pane_current_command alone is usually just node/bun/sh).
|
|
286
|
+
// Only capture recognized agent panes; unknown commands stay "unknown"
|
|
287
|
+
// without a capture-pane round-trip.
|
|
288
|
+
manifest = resolveAgentCommand(pane.cmd, pane.pid, processTable, {
|
|
289
|
+
hint: pane.hint,
|
|
290
|
+
}).manifest;
|
|
291
|
+
const instant = manifest
|
|
292
|
+
? classifyInstant({ ...readPaneSnapshot(pane.id), title: pane.title }, manifest)
|
|
293
|
+
: "unknown";
|
|
294
|
+
status = tracker.update(pane.id, instant, { seen });
|
|
295
|
+
}
|
|
296
|
+
// The `shell` catch-all isn't a real agent — a raw shell pane gets no
|
|
297
|
+
// chip (agent null → empty chip → border falls back to the pane title).
|
|
298
|
+
opts.onPane?.({
|
|
299
|
+
sessionName: name,
|
|
300
|
+
paneId: pane.id,
|
|
301
|
+
agent: manifest && manifest.id !== "shell" ? manifest.id : null,
|
|
302
|
+
status,
|
|
303
|
+
});
|
|
304
|
+
// Surface per-pane agent detail (same resolved manifest/status — nothing
|
|
305
|
+
// re-derived). Non-agent panes yield null and are skipped.
|
|
306
|
+
const entry = buildAgentEntry({ sessionName: name, pane, manifest, state: status, since });
|
|
307
|
+
if (entry) agents.push(entry);
|
|
308
|
+
return status;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
name,
|
|
313
|
+
attached: attached === "1",
|
|
314
|
+
windows: Number(windows) || 0,
|
|
315
|
+
panes: panes.length,
|
|
316
|
+
status: rollupStatus(statuses),
|
|
317
|
+
// `panes` and `statuses` are parallel (statuses = panes.map(...)), so
|
|
318
|
+
// the pure rollup can group each pane's window with its resolved status.
|
|
319
|
+
windowList: rollupWindows(panes, statuses),
|
|
320
|
+
agents,
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** One `list-panes -a` call, grouped by session — avoids N tmux calls. */
|
|
326
|
+
function collectPanes(): Map<string, PaneRecord[]> {
|
|
327
|
+
const raw = tmux([
|
|
328
|
+
"list-panes",
|
|
329
|
+
"-a",
|
|
330
|
+
"-F",
|
|
331
|
+
// Window fields + pane_current_path sit before pane_title so the (tab-safe)
|
|
332
|
+
// title stays the trailing catch-all — window names/paths don't contain tabs
|
|
333
|
+
// in practice. pane_current_path rides this SAME list-panes call (no extra
|
|
334
|
+
// tmux round-trip) so per-pane agent entries can carry a working dir.
|
|
335
|
+
`#{session_name}\t#{pane_id}\t#{pane_pid}\t#{pane_current_command}\t#{@agent_state}\t#{@agent_hint}\t#{${SIDEBAR_PANE_OPTION}}\t#{window_index}\t#{window_name}\t#{window_active}\t#{pane_current_path}\t#{pane_title}`,
|
|
336
|
+
]);
|
|
337
|
+
const bySession = new Map<string, PaneRecord[]>();
|
|
338
|
+
for (const line of raw.split("\n").filter(Boolean)) {
|
|
339
|
+
const [
|
|
340
|
+
session = "",
|
|
341
|
+
id = "",
|
|
342
|
+
pid = "",
|
|
343
|
+
cmd = "",
|
|
344
|
+
authority = "",
|
|
345
|
+
hint = "",
|
|
346
|
+
sidebar = "",
|
|
347
|
+
windowIndex = "0",
|
|
348
|
+
windowName = "",
|
|
349
|
+
windowActive = "0",
|
|
350
|
+
dir = "",
|
|
351
|
+
...titleParts
|
|
352
|
+
] = line.split("\t");
|
|
353
|
+
if (!session) continue;
|
|
354
|
+
const list = bySession.get(session) ?? [];
|
|
355
|
+
list.push({
|
|
356
|
+
id,
|
|
357
|
+
pid: Number(pid) || 0,
|
|
358
|
+
cmd,
|
|
359
|
+
authority,
|
|
360
|
+
hint,
|
|
361
|
+
sidebar: sidebar === "1",
|
|
362
|
+
windowIndex: Number(windowIndex) || 0,
|
|
363
|
+
windowName,
|
|
364
|
+
windowActive: windowActive === "1",
|
|
365
|
+
dir,
|
|
366
|
+
title: titleParts.join("\t"),
|
|
367
|
+
});
|
|
368
|
+
bySession.set(session, list);
|
|
369
|
+
}
|
|
370
|
+
return bySession;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Acknowledge an authority-reported `done` for a viewed pane by rewriting its
|
|
375
|
+
* option to idle — otherwise the stamped "done" would resurface every tick.
|
|
376
|
+
*/
|
|
377
|
+
function ackDone(paneId: string, nowSec: number): void {
|
|
378
|
+
tmux(["set-option", "-p", "-t", paneId, "@agent_state", `idle:${nowSec}`]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Roll a session's per-pane statuses up to a single status. The highest
|
|
383
|
+
* present severity wins (blocked > working > done > idle > unknown), so
|
|
384
|
+
* `unknown` only surfaces when nothing else is present. An empty session
|
|
385
|
+
* (no panes) rolls up to `"idle"`.
|
|
386
|
+
*/
|
|
387
|
+
export function rollupStatus(statuses: AgentStatus[]): AgentStatus {
|
|
388
|
+
if (statuses.length === 0) return "idle";
|
|
389
|
+
const present = new Set(statuses);
|
|
390
|
+
for (const status of SEVERITY) {
|
|
391
|
+
if (present.has(status)) return status;
|
|
392
|
+
}
|
|
393
|
+
return "unknown";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** The window fields {@link rollupWindows} needs from each pane — PaneRecord fits. */
|
|
397
|
+
export interface WindowPaneInput {
|
|
398
|
+
windowIndex: number;
|
|
399
|
+
windowName: string;
|
|
400
|
+
windowActive: boolean;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Group a session's panes into per-window rollups — PURE.
|
|
405
|
+
*
|
|
406
|
+
* `panes[i]` and `statuses[i]` are parallel (one status per pane). Panes are
|
|
407
|
+
* bucketed by `windowIndex`; each window's panes roll up to a single status via
|
|
408
|
+
* {@link rollupStatus}, its `name` taken from the first pane seen and `active`
|
|
409
|
+
* true if ANY of its panes report `window_active`. Windows come back in
|
|
410
|
+
* ascending index order. An empty pane list yields an empty window list.
|
|
411
|
+
*/
|
|
412
|
+
export function rollupWindows(panes: WindowPaneInput[], statuses: AgentStatus[]): TeamWindow[] {
|
|
413
|
+
const byIndex = new Map<number, { name: string; active: boolean; statuses: AgentStatus[] }>();
|
|
414
|
+
panes.forEach((pane, i) => {
|
|
415
|
+
let entry = byIndex.get(pane.windowIndex);
|
|
416
|
+
if (!entry) {
|
|
417
|
+
entry = { name: pane.windowName, active: false, statuses: [] };
|
|
418
|
+
byIndex.set(pane.windowIndex, entry);
|
|
419
|
+
}
|
|
420
|
+
if (pane.windowActive) entry.active = true;
|
|
421
|
+
const status = statuses[i];
|
|
422
|
+
if (status) entry.statuses.push(status);
|
|
423
|
+
});
|
|
424
|
+
return [...byIndex.entries()]
|
|
425
|
+
.sort(([a], [b]) => a - b)
|
|
426
|
+
.map(([index, entry]) => ({
|
|
427
|
+
index,
|
|
428
|
+
name: entry.name,
|
|
429
|
+
active: entry.active,
|
|
430
|
+
panes: entry.statuses.length,
|
|
431
|
+
status: rollupStatus(entry.statuses),
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flat row-node union shared by the picker popup and the sidebar column.
|
|
3
|
+
*
|
|
4
|
+
* Both surfaces navigate the same three-tier tree — project → session → window
|
|
5
|
+
* — with a single flat cursor. This module is the PURE builder for that ordered
|
|
6
|
+
* node list so the two callers can't drift: `treeNodes` produces the union for a
|
|
7
|
+
* given cursor/expansion policy, `findCursor` locates a cursor within it.
|
|
8
|
+
*
|
|
9
|
+
* The cursor is the `(pi, si, wi)` triple, where `si:-1` marks a project row and
|
|
10
|
+
* `wi:-1` a session (or project) row — identical to the picker's original
|
|
11
|
+
* inline `pickerNodes`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** A flat cursor position: project index, session index (-1 = project row),
|
|
15
|
+
* window index (-1 = session/project row). */
|
|
16
|
+
export interface TreeCursor {
|
|
17
|
+
pi: number;
|
|
18
|
+
si: number;
|
|
19
|
+
wi: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The minimal project shape the node builder needs — a structural subset of
|
|
23
|
+
* TeamProject so tests can pass plain fixtures. */
|
|
24
|
+
export interface TreeProjectLike {
|
|
25
|
+
sessions: Array<{ windowList: readonly unknown[] }>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PURE — build the ordered flat node union for the tree.
|
|
30
|
+
*
|
|
31
|
+
* For every project a project row is emitted. Its sessions are expanded when the
|
|
32
|
+
* project is the active one, OR always when `expandAllProjects` is set (the
|
|
33
|
+
* sidebar shows the whole fleet; the picker only opens the active project).
|
|
34
|
+
* Window rows are emitted only under the ACTIVE session (`pi===activeProject &&
|
|
35
|
+
* si===activeSession`), so exactly one session's windows are ever expanded.
|
|
36
|
+
*/
|
|
37
|
+
export function treeNodes(
|
|
38
|
+
projects: readonly TreeProjectLike[],
|
|
39
|
+
activeProject: number,
|
|
40
|
+
activeSession: number,
|
|
41
|
+
opts: { expandAllProjects?: boolean } = {},
|
|
42
|
+
): TreeCursor[] {
|
|
43
|
+
const expandAll = opts.expandAllProjects ?? false;
|
|
44
|
+
const nodes: TreeCursor[] = [];
|
|
45
|
+
projects.forEach((proj, pi) => {
|
|
46
|
+
nodes.push({ pi, si: -1, wi: -1 });
|
|
47
|
+
if (!expandAll && pi !== activeProject) return;
|
|
48
|
+
proj.sessions.forEach((sess, si) => {
|
|
49
|
+
nodes.push({ pi, si, wi: -1 });
|
|
50
|
+
if (pi === activeProject && si === activeSession) {
|
|
51
|
+
sess.windowList.forEach((_w, wi) => nodes.push({ pi, si, wi }));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
return nodes;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** PURE — index of `cursor` in `nodes`, or -1 when absent (e.g. after a refresh
|
|
59
|
+
* dropped the row). */
|
|
60
|
+
export function findCursor(nodes: readonly TreeCursor[], cursor: TreeCursor): number {
|
|
61
|
+
return nodes.findIndex((n) => n.pi === cursor.pi && n.si === cursor.si && n.wi === cursor.wi);
|
|
62
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Environment-detecting UI entry point.
|
|
2
|
+
//
|
|
3
|
+
// Detects whether code is running in a browser (window exists) or terminal
|
|
4
|
+
// (Node/Bun) and re-exports the appropriate backend. Widgets import from
|
|
5
|
+
// this module to work in both environments without code changes.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// import { render, useKeyboard, RGBA, TextAttributes } from "../../ui/index.ts";
|
|
9
|
+
//
|
|
10
|
+
// Terminal backend: re-exports @opentui/solid + @opentui/core
|
|
11
|
+
// Web backend: re-exports DOM-based mirror components
|
|
12
|
+
|
|
13
|
+
// Use a compile-time constant so bundlers can tree-shake the unused backend.
|
|
14
|
+
// In browser builds, `typeof window !== "undefined"` is true and the terminal
|
|
15
|
+
// branch is eliminated. In Node/Bun, the web branch is eliminated.
|
|
16
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
17
|
+
|
|
18
|
+
if (IS_BROWSER) {
|
|
19
|
+
// Re-export web backend at module level for static analysis
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// We use conditional re-export via a barrel pattern.
|
|
23
|
+
// Bundlers (Bun, webpack, turbopack) resolve this statically.
|
|
24
|
+
// For runtime detection, we export from web by default (browser-first)
|
|
25
|
+
// and terminal widgets import from ./terminal/index.ts directly.
|
|
26
|
+
//
|
|
27
|
+
// The web backend is safe to import in Node because it only touches
|
|
28
|
+
// the DOM inside render() and hooks (which are never called in terminal mode).
|
|
29
|
+
export * from "./web/index.ts";
|
|
30
|
+
|
|
31
|
+
// Also re-export shared types so they're available regardless of backend
|
|
32
|
+
export type { BoxProps, TextProps, ScrollBoxProps, InputProps } from "./types.ts";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Terminal backend — re-exports @opentui/solid and @opentui/core for terminal rendering.
|
|
2
|
+
// Widgets importing from src/ui/ will get these exports when running in Node/Bun.
|
|
3
|
+
|
|
4
|
+
// Render and hooks
|
|
5
|
+
export { render, useKeyboard, useTerminalDimensions } from "@opentui/solid";
|
|
6
|
+
|
|
7
|
+
// Core primitives and types
|
|
8
|
+
export { RGBA, TextAttributes } from "@opentui/core";
|
|
9
|
+
export type { ScrollBoxRenderable, InputRenderable } from "@opentui/core";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface RGBA {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
a: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function rgbaFromInts(r: number, g: number, b: number, a?: number): RGBA {
|
|
9
|
+
return { r, g, b, a: a ?? 255 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TextAttributes = {
|
|
13
|
+
NONE: 0,
|
|
14
|
+
BOLD: 1,
|
|
15
|
+
DIM: 2,
|
|
16
|
+
ITALIC: 4,
|
|
17
|
+
UNDERLINE: 8,
|
|
18
|
+
BLINK: 16,
|
|
19
|
+
INVERSE: 32,
|
|
20
|
+
HIDDEN: 64,
|
|
21
|
+
STRIKETHROUGH: 128,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export interface BoxProps {
|
|
25
|
+
id?: string;
|
|
26
|
+
flexDirection?: "row" | "column";
|
|
27
|
+
flexGrow?: number;
|
|
28
|
+
flexShrink?: number;
|
|
29
|
+
gap?: number;
|
|
30
|
+
justifyContent?: "flex-start" | "center" | "flex-end" | "space-between";
|
|
31
|
+
alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
|
|
32
|
+
position?: "relative" | "absolute";
|
|
33
|
+
overflow?: "visible" | "hidden";
|
|
34
|
+
width?: number | string;
|
|
35
|
+
height?: number | string;
|
|
36
|
+
maxWidth?: number | string;
|
|
37
|
+
maxHeight?: number | string;
|
|
38
|
+
padding?: number;
|
|
39
|
+
paddingLeft?: number;
|
|
40
|
+
paddingRight?: number;
|
|
41
|
+
paddingTop?: number;
|
|
42
|
+
paddingBottom?: number;
|
|
43
|
+
backgroundColor?: RGBA;
|
|
44
|
+
border?: string[];
|
|
45
|
+
borderColor?: RGBA;
|
|
46
|
+
top?: number;
|
|
47
|
+
left?: number;
|
|
48
|
+
right?: number;
|
|
49
|
+
bottom?: number;
|
|
50
|
+
// Mouse events
|
|
51
|
+
onMouseDown?: (e: unknown) => void;
|
|
52
|
+
onMouseUp?: (e: unknown) => void;
|
|
53
|
+
onMouseMove?: (e: unknown) => void;
|
|
54
|
+
onMouseOver?: (e: unknown) => void;
|
|
55
|
+
// Children
|
|
56
|
+
children?: unknown;
|
|
57
|
+
ref?: (el: unknown) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TextProps {
|
|
61
|
+
fg?: RGBA;
|
|
62
|
+
bg?: RGBA;
|
|
63
|
+
attributes?: number;
|
|
64
|
+
wrapMode?: "none" | "word" | "char";
|
|
65
|
+
width?: number | string;
|
|
66
|
+
height?: number | string;
|
|
67
|
+
flexGrow?: number;
|
|
68
|
+
flexShrink?: number;
|
|
69
|
+
// Mouse events
|
|
70
|
+
onMouseUp?: (e: unknown) => void;
|
|
71
|
+
onMouseDown?: (e: unknown) => void;
|
|
72
|
+
children?: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ScrollBoxProps extends BoxProps {
|
|
76
|
+
verticalScrollbarOptions?: unknown;
|
|
77
|
+
stickyScroll?: boolean;
|
|
78
|
+
stickyStart?: "bottom" | "top";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface InputProps {
|
|
82
|
+
value?: string;
|
|
83
|
+
placeholder?: string;
|
|
84
|
+
onInput?: (value: string) => void;
|
|
85
|
+
focusedBackgroundColor?: RGBA;
|
|
86
|
+
cursorColor?: RGBA;
|
|
87
|
+
focusedTextColor?: RGBA;
|
|
88
|
+
onMouseDown?: (e: unknown) => void;
|
|
89
|
+
ref?: (el: unknown) => void;
|
|
90
|
+
children?: unknown;
|
|
91
|
+
}
|