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,57 @@
|
|
|
1
|
+
/** The `<os>-<arch>` tags we publish a prebuilt TUI binary for. */
|
|
2
|
+
export type TuiPlatformTag = "darwin-arm64" | "darwin-x64" | "linux-x64" | "linux-arm64";
|
|
3
|
+
/** The GitHub repo the release assets live under. */
|
|
4
|
+
export declare const RELEASE_REPO = "wavyrai/tmux-ide";
|
|
5
|
+
/** A downloaded binary smaller than this is treated as corrupt/truncated. */
|
|
6
|
+
export declare const MIN_TUI_BINARY_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* PURE — map a Node `process.platform`/`process.arch` pair to the release tag,
|
|
9
|
+
* or null when we don't publish a binary for it (e.g. windows, freebsd) so the
|
|
10
|
+
* caller can fall back to the "install bun" message.
|
|
11
|
+
*/
|
|
12
|
+
export declare function tuiPlatformTag(platform?: NodeJS.Platform, arch?: string): TuiPlatformTag | null;
|
|
13
|
+
/** PURE — the `bun build --compile --target` flag for a tag (`bun-<tag>`). */
|
|
14
|
+
export declare function bunTargetForTag(tag: TuiPlatformTag): string;
|
|
15
|
+
/** PURE — the release asset filename for a tag (gzip-compressed binary). */
|
|
16
|
+
export declare function releaseAssetName(tag: TuiPlatformTag): string;
|
|
17
|
+
/** PURE — strip a leading `v` from a version string; `2.6.1` and `v2.6.1` both → `2.6.1`. */
|
|
18
|
+
export declare function normalizeVersion(version: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* PURE — the GitHub download URL for a platform's asset at a given version:
|
|
21
|
+
* `https://github.com/<repo>/releases/download/v<version>/tmux-ide-tui-<tag>.gz`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function releaseAssetUrl(version: string, tag: TuiPlatformTag): string;
|
|
24
|
+
/**
|
|
25
|
+
* PURE — where a downloaded binary lives: `<home>/bin/tmux-ide-tui-<tag>-<version>`.
|
|
26
|
+
* The version is stamped INTO the name so a `tmux-ide update` to a new version
|
|
27
|
+
* misses the old download and re-fetches (rather than launching a stale binary).
|
|
28
|
+
*/
|
|
29
|
+
export declare function downloadedTuiPath(home: string, tag: TuiPlatformTag, version: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* io — the tmux-ide state home (`TMUX_IDE_HOME` override, else `~/.tmux-ide`),
|
|
32
|
+
* the same resolution the update-check cache and welcome marker use.
|
|
33
|
+
*/
|
|
34
|
+
export declare function tuiStateHome(): string;
|
|
35
|
+
/**
|
|
36
|
+
* io — locate a previously downloaded per-platform binary for THIS version and
|
|
37
|
+
* platform, or null. Feeds the resolution order in `tui/compiled.ts` after the
|
|
38
|
+
* shipped/local compiled binary and before the honest "unavailable" error.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findDownloadedTui(version?: string): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* io — download, verify, and install the per-platform TUI binary for the running
|
|
43
|
+
* version. Fetches the gzip asset, inflates it, rejects anything under
|
|
44
|
+
* {@link MIN_TUI_BINARY_BYTES} (a truncated download or an HTML error page),
|
|
45
|
+
* writes it `0o755`, and atomically renames it into place (temp-in-same-dir →
|
|
46
|
+
* rename, so a crashed download never leaves a half-written executable).
|
|
47
|
+
*
|
|
48
|
+
* Throws with an actionable message on an unsupported platform or a failed
|
|
49
|
+
* fetch. Returns the installed path and byte size.
|
|
50
|
+
*/
|
|
51
|
+
export declare function downloadTuiBinary(opts?: {
|
|
52
|
+
version?: string;
|
|
53
|
+
log?: (msg: string) => void;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
path: string;
|
|
56
|
+
bytes: number;
|
|
57
|
+
}>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-platform TUI binary: the runtime-download fallback that lets a clean
|
|
3
|
+
* `npm i -g tmux-ide` run the full OpenTUI/Solid cockpit WITHOUT `bun`.
|
|
4
|
+
*
|
|
5
|
+
* The dev checkout runs the `.tsx` surfaces via bun; an npm install with no bun
|
|
6
|
+
* needs a self-contained binary. We do NOT bundle that ~70MB blob in the npm
|
|
7
|
+
* tarball (a surprise on every install) — instead the release workflow
|
|
8
|
+
* (`.github/workflows/release-binaries.yml`) cross-compiles one per platform and
|
|
9
|
+
* uploads them as GitHub release assets, and this module downloads the right one
|
|
10
|
+
* on demand (explicit `tmux-ide update --tui-binary`, or a consented first run).
|
|
11
|
+
*
|
|
12
|
+
* The mapping/URL/path helpers are PURE (unit-tested); {@link downloadTuiBinary}
|
|
13
|
+
* and {@link findDownloadedTui} are the thin io that fetch and probe.
|
|
14
|
+
*/
|
|
15
|
+
import { chmodSync, existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { gunzipSync } from "node:zlib";
|
|
19
|
+
import { getCurrentVersion } from "./update-check.js";
|
|
20
|
+
/** The GitHub repo the release assets live under. */
|
|
21
|
+
export const RELEASE_REPO = "wavyrai/tmux-ide";
|
|
22
|
+
/** A downloaded binary smaller than this is treated as corrupt/truncated. */
|
|
23
|
+
export const MIN_TUI_BINARY_BYTES = 10 * 1024 * 1024;
|
|
24
|
+
const SUPPORTED = {
|
|
25
|
+
"darwin-arm64": "darwin-arm64",
|
|
26
|
+
"darwin-x64": "darwin-x64",
|
|
27
|
+
"linux-x64": "linux-x64",
|
|
28
|
+
"linux-arm64": "linux-arm64",
|
|
29
|
+
};
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Pure
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
/**
|
|
34
|
+
* PURE — map a Node `process.platform`/`process.arch` pair to the release tag,
|
|
35
|
+
* or null when we don't publish a binary for it (e.g. windows, freebsd) so the
|
|
36
|
+
* caller can fall back to the "install bun" message.
|
|
37
|
+
*/
|
|
38
|
+
export function tuiPlatformTag(platform = process.platform, arch = process.arch) {
|
|
39
|
+
return SUPPORTED[`${platform}-${arch}`] ?? null;
|
|
40
|
+
}
|
|
41
|
+
/** PURE — the `bun build --compile --target` flag for a tag (`bun-<tag>`). */
|
|
42
|
+
export function bunTargetForTag(tag) {
|
|
43
|
+
return `bun-${tag}`;
|
|
44
|
+
}
|
|
45
|
+
/** PURE — the release asset filename for a tag (gzip-compressed binary). */
|
|
46
|
+
export function releaseAssetName(tag) {
|
|
47
|
+
return `tmux-ide-tui-${tag}.gz`;
|
|
48
|
+
}
|
|
49
|
+
/** PURE — strip a leading `v` from a version string; `2.6.1` and `v2.6.1` both → `2.6.1`. */
|
|
50
|
+
export function normalizeVersion(version) {
|
|
51
|
+
return version.startsWith("v") ? version.slice(1) : version;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* PURE — the GitHub download URL for a platform's asset at a given version:
|
|
55
|
+
* `https://github.com/<repo>/releases/download/v<version>/tmux-ide-tui-<tag>.gz`.
|
|
56
|
+
*/
|
|
57
|
+
export function releaseAssetUrl(version, tag) {
|
|
58
|
+
return `https://github.com/${RELEASE_REPO}/releases/download/v${normalizeVersion(version)}/${releaseAssetName(tag)}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* PURE — where a downloaded binary lives: `<home>/bin/tmux-ide-tui-<tag>-<version>`.
|
|
62
|
+
* The version is stamped INTO the name so a `tmux-ide update` to a new version
|
|
63
|
+
* misses the old download and re-fetches (rather than launching a stale binary).
|
|
64
|
+
*/
|
|
65
|
+
export function downloadedTuiPath(home, tag, version) {
|
|
66
|
+
return join(home, "bin", `tmux-ide-tui-${tag}-${normalizeVersion(version)}`);
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// io
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* io — the tmux-ide state home (`TMUX_IDE_HOME` override, else `~/.tmux-ide`),
|
|
73
|
+
* the same resolution the update-check cache and welcome marker use.
|
|
74
|
+
*/
|
|
75
|
+
export function tuiStateHome() {
|
|
76
|
+
return process.env.TMUX_IDE_HOME ?? join(homedir(), ".tmux-ide");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* io — locate a previously downloaded per-platform binary for THIS version and
|
|
80
|
+
* platform, or null. Feeds the resolution order in `tui/compiled.ts` after the
|
|
81
|
+
* shipped/local compiled binary and before the honest "unavailable" error.
|
|
82
|
+
*/
|
|
83
|
+
export function findDownloadedTui(version = getCurrentVersion()) {
|
|
84
|
+
const tag = tuiPlatformTag();
|
|
85
|
+
if (!tag)
|
|
86
|
+
return null;
|
|
87
|
+
const path = downloadedTuiPath(tuiStateHome(), tag, version);
|
|
88
|
+
return existsSync(path) ? path : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* io — download, verify, and install the per-platform TUI binary for the running
|
|
92
|
+
* version. Fetches the gzip asset, inflates it, rejects anything under
|
|
93
|
+
* {@link MIN_TUI_BINARY_BYTES} (a truncated download or an HTML error page),
|
|
94
|
+
* writes it `0o755`, and atomically renames it into place (temp-in-same-dir →
|
|
95
|
+
* rename, so a crashed download never leaves a half-written executable).
|
|
96
|
+
*
|
|
97
|
+
* Throws with an actionable message on an unsupported platform or a failed
|
|
98
|
+
* fetch. Returns the installed path and byte size.
|
|
99
|
+
*/
|
|
100
|
+
export async function downloadTuiBinary(opts = {}) {
|
|
101
|
+
const log = opts.log ?? (() => { });
|
|
102
|
+
const version = normalizeVersion(opts.version ?? getCurrentVersion());
|
|
103
|
+
const tag = tuiPlatformTag();
|
|
104
|
+
if (!tag) {
|
|
105
|
+
throw new Error(`no prebuilt TUI binary is published for ${process.platform}-${process.arch} — ` +
|
|
106
|
+
`install bun (https://bun.sh) to run the TUI surfaces from source instead`);
|
|
107
|
+
}
|
|
108
|
+
const url = releaseAssetUrl(version, tag);
|
|
109
|
+
const dest = downloadedTuiPath(tuiStateHome(), tag, version);
|
|
110
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
111
|
+
log(`downloading ${url}`);
|
|
112
|
+
const res = await fetch(url);
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
throw new Error(`could not download the TUI binary (${url} → HTTP ${res.status} ${res.statusText}). ` +
|
|
115
|
+
`Check that release v${version} exists and published its assets.`);
|
|
116
|
+
}
|
|
117
|
+
const gz = Buffer.from(await res.arrayBuffer());
|
|
118
|
+
const bin = gunzipSync(gz);
|
|
119
|
+
if (bin.byteLength < MIN_TUI_BINARY_BYTES) {
|
|
120
|
+
throw new Error(`the downloaded TUI binary is only ${bin.byteLength} bytes (expected >10MB) — ` +
|
|
121
|
+
`treating it as corrupt and leaving the previous binary (if any) in place`);
|
|
122
|
+
}
|
|
123
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
124
|
+
writeFileSync(tmp, bin, { mode: 0o755 });
|
|
125
|
+
chmodSync(tmp, 0o755);
|
|
126
|
+
renameSync(tmp, dest);
|
|
127
|
+
const mb = (bin.byteLength / 1024 / 1024).toFixed(1);
|
|
128
|
+
log(`installed ${dest} (${mb} MB)`);
|
|
129
|
+
return { path: dest, bytes: bin.byteLength };
|
|
130
|
+
}
|
|
@@ -6,5 +6,5 @@ interface BreadcrumbsProps {
|
|
|
6
6
|
theme: WidgetTheme;
|
|
7
7
|
onNavigate: (absolutePath: string) => void;
|
|
8
8
|
}
|
|
9
|
-
export declare function Breadcrumbs(props: BreadcrumbsProps):
|
|
9
|
+
export declare function Breadcrumbs(props: BreadcrumbsProps): import("solid-js").JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -9,5 +9,5 @@ interface FileTreeProps {
|
|
|
9
9
|
onActivate: (node: TreeNode) => void;
|
|
10
10
|
onInputModeChange: (mode: "keyboard" | "mouse") => void;
|
|
11
11
|
}
|
|
12
|
-
export declare function FileTree(props: FileTreeProps):
|
|
12
|
+
export declare function FileTree(props: FileTreeProps): import("solid-js").JSX.Element;
|
|
13
13
|
export {};
|
|
@@ -16,4 +16,4 @@ export interface HelpOverlayProps {
|
|
|
16
16
|
* the surface's own keys. Replaces the surface body while `?` is held open;
|
|
17
17
|
* `esc`/`q`/`?` close it.
|
|
18
18
|
*/
|
|
19
|
-
export declare function HelpOverlay(props: HelpOverlayProps):
|
|
19
|
+
export declare function HelpOverlay(props: HelpOverlayProps): import("solid-js").JSX.Element;
|
|
@@ -5,5 +5,5 @@ interface AgentNamingProps {
|
|
|
5
5
|
onContinue: (names: string[]) => void;
|
|
6
6
|
theme: WidgetTheme;
|
|
7
7
|
}
|
|
8
|
-
export declare function AgentNaming(props: AgentNamingProps):
|
|
8
|
+
export declare function AgentNaming(props: AgentNamingProps): import("solid-js").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -9,5 +9,5 @@ interface ConfigTreeProps {
|
|
|
9
9
|
onConfigChange: (config: IdeConfig) => void;
|
|
10
10
|
theme: WidgetTheme;
|
|
11
11
|
}
|
|
12
|
-
export declare function ConfigTree(props: ConfigTreeProps):
|
|
12
|
+
export declare function ConfigTree(props: ConfigTreeProps): import("solid-js").JSX.Element;
|
|
13
13
|
export {};
|
|
@@ -5,5 +5,5 @@ interface LayoutPickerProps {
|
|
|
5
5
|
onSelect: (preset: LayoutPreset) => void;
|
|
6
6
|
theme: WidgetTheme;
|
|
7
7
|
}
|
|
8
|
-
export declare function LayoutPicker(props: LayoutPickerProps):
|
|
8
|
+
export declare function LayoutPicker(props: LayoutPickerProps): import("solid-js").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tmux-ide agent explain <pane>` — the detection debugger.
|
|
3
|
+
*
|
|
4
|
+
* For a single pane it prints EVERYTHING the fleet detector reasons over:
|
|
5
|
+
* - the pane's command + pid,
|
|
6
|
+
* - the `@agent_state` authority option (raw + parsed verdict + staleness),
|
|
7
|
+
* - the `@agent_hint` override option,
|
|
8
|
+
* - which manifest resolved and via which path (hint/fast/tree),
|
|
9
|
+
* - each state's rule → matched or not (via `explain()`),
|
|
10
|
+
* - the winning instantaneous classification, and
|
|
11
|
+
* - the bottom 5 snapshot lines it judged.
|
|
12
|
+
*
|
|
13
|
+
* This is READ-ONLY: it captures and inspects, it never sends keys or mutates
|
|
14
|
+
* the target pane.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import { explain, type DetectedState, type ManifestConfidence } from "./tui/detect/manifest.ts";
|
|
18
|
+
import {
|
|
19
|
+
classifyInstant,
|
|
20
|
+
parseAuthority,
|
|
21
|
+
type AgentStatus,
|
|
22
|
+
type InstantState,
|
|
23
|
+
} from "./tui/detect/classify.ts";
|
|
24
|
+
import { getManifests } from "./tui/detect/manifest-loader.ts";
|
|
25
|
+
import {
|
|
26
|
+
describeSubtree,
|
|
27
|
+
readProcessTable,
|
|
28
|
+
resolveAgentCommand,
|
|
29
|
+
} from "./tui/detect/process-tree.ts";
|
|
30
|
+
import { readPaneSnapshot } from "./tui/detect/snapshot.ts";
|
|
31
|
+
import { IdeError } from "./lib/errors.ts";
|
|
32
|
+
|
|
33
|
+
/** Working/blocked authority reports older than this are stale (mirrors classify.ts). */
|
|
34
|
+
const AUTHORITY_STALE_SECONDS = 600;
|
|
35
|
+
|
|
36
|
+
interface PaneInfo {
|
|
37
|
+
id: string;
|
|
38
|
+
pid: number;
|
|
39
|
+
cmd: string;
|
|
40
|
+
title: string;
|
|
41
|
+
authorityRaw: string;
|
|
42
|
+
hintRaw: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ExplainReport {
|
|
46
|
+
pane: { id: string; cmd: string; pid: number; title: string };
|
|
47
|
+
authority: {
|
|
48
|
+
raw: string | null;
|
|
49
|
+
state: string | null;
|
|
50
|
+
epoch: number | null;
|
|
51
|
+
ageSeconds: number | null;
|
|
52
|
+
stale: boolean;
|
|
53
|
+
verdict: AgentStatus | null;
|
|
54
|
+
};
|
|
55
|
+
hint: { raw: string | null; applied: boolean };
|
|
56
|
+
resolution: {
|
|
57
|
+
manifestId: string | null;
|
|
58
|
+
matchedCommand: string;
|
|
59
|
+
source: "hint" | "fast" | "tree" | "none";
|
|
60
|
+
/** The matched manifest's evidence confidence (null when nothing resolved). */
|
|
61
|
+
confidence: ManifestConfidence | null;
|
|
62
|
+
/**
|
|
63
|
+
* Executable-ish tokens seen in the pane's process subtree. Populated for
|
|
64
|
+
* the "no manifest matched" case so the report can say what it DID find.
|
|
65
|
+
*/
|
|
66
|
+
subtree: string[];
|
|
67
|
+
};
|
|
68
|
+
states: Array<{ state: DetectedState; matched: boolean }>;
|
|
69
|
+
winner: DetectedState | null;
|
|
70
|
+
instant: InstantState;
|
|
71
|
+
classification: AgentStatus;
|
|
72
|
+
bottomLines: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function tmux(args: string[]): string {
|
|
76
|
+
try {
|
|
77
|
+
return execFileSync("tmux", args, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
80
|
+
}).trim();
|
|
81
|
+
} catch {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a target to a concrete pane's fields. `%N` is a pane id; anything
|
|
88
|
+
* else is treated as a session (or any tmux target) whose ACTIVE pane is used.
|
|
89
|
+
*/
|
|
90
|
+
function readPaneInfo(target: string): PaneInfo | null {
|
|
91
|
+
const fmt =
|
|
92
|
+
"#{pane_id}\t#{pane_pid}\t#{pane_current_command}\t#{@agent_state}\t#{@agent_hint}\t#{pane_title}";
|
|
93
|
+
const raw = tmux(["display-message", "-p", "-t", target, "-F", fmt]);
|
|
94
|
+
if (!raw) return null;
|
|
95
|
+
const [id = "", pid = "", cmd = "", authorityRaw = "", hintRaw = "", ...titleParts] =
|
|
96
|
+
raw.split("\t");
|
|
97
|
+
if (!id) return null;
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
pid: Number(pid) || 0,
|
|
101
|
+
cmd,
|
|
102
|
+
authorityRaw,
|
|
103
|
+
hintRaw,
|
|
104
|
+
title: titleParts.join("\t"),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Build the full report for a pane. Does all the io (tmux + ps + capture). */
|
|
109
|
+
export function buildReport(target: string): ExplainReport {
|
|
110
|
+
const info = readPaneInfo(target);
|
|
111
|
+
if (!info) {
|
|
112
|
+
throw new IdeError(
|
|
113
|
+
`No pane found for "${target}". Pass a pane id (%N) or a live session name.`,
|
|
114
|
+
{ code: "USAGE", exitCode: 1 },
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
119
|
+
|
|
120
|
+
// Authority: parse richly for the report (parseAuthority collapses to a
|
|
121
|
+
// verdict; here we also expose the raw state/epoch/staleness).
|
|
122
|
+
const authRaw = info.authorityRaw || null;
|
|
123
|
+
let authState: string | null = null;
|
|
124
|
+
let authEpoch: number | null = null;
|
|
125
|
+
let ageSeconds: number | null = null;
|
|
126
|
+
let stale = false;
|
|
127
|
+
if (authRaw) {
|
|
128
|
+
const sep = authRaw.lastIndexOf(":");
|
|
129
|
+
if (sep !== -1) {
|
|
130
|
+
authState = authRaw.slice(0, sep);
|
|
131
|
+
const epoch = Number(authRaw.slice(sep + 1));
|
|
132
|
+
if (Number.isFinite(epoch)) {
|
|
133
|
+
authEpoch = epoch;
|
|
134
|
+
ageSeconds = nowSec - epoch;
|
|
135
|
+
stale =
|
|
136
|
+
(authState === "working" || authState === "blocked") &&
|
|
137
|
+
ageSeconds > AUTHORITY_STALE_SECONDS;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const verdict = parseAuthority(info.authorityRaw, nowSec);
|
|
142
|
+
|
|
143
|
+
// Resolution: hint > fast > tree, over the loaded (bundled + override) set.
|
|
144
|
+
const manifests = getManifests();
|
|
145
|
+
const table = readProcessTable();
|
|
146
|
+
const resolved = resolveAgentCommand(info.cmd, info.pid, table, {
|
|
147
|
+
manifests,
|
|
148
|
+
hint: info.hintRaw,
|
|
149
|
+
});
|
|
150
|
+
const manifest = resolved.manifest;
|
|
151
|
+
// When nothing resolved, record what the process-tree DID see so `explain`
|
|
152
|
+
// can point the user at an `@agent_hint` target instead of a dead end.
|
|
153
|
+
const subtree = manifest ? [] : describeSubtree(table, info.pid);
|
|
154
|
+
|
|
155
|
+
// Snapshot + per-state explanation.
|
|
156
|
+
const snapshot = { ...readPaneSnapshot(info.id), title: info.title };
|
|
157
|
+
const explained = manifest
|
|
158
|
+
? explain(snapshot, manifest)
|
|
159
|
+
: {
|
|
160
|
+
state: null as DetectedState | null,
|
|
161
|
+
checked: [] as Array<{ state: DetectedState; matched: boolean }>,
|
|
162
|
+
};
|
|
163
|
+
const instant = classifyInstant(snapshot, manifest);
|
|
164
|
+
|
|
165
|
+
// Final classification mirrors sessions.ts: a fresh authority verdict wins;
|
|
166
|
+
// otherwise the instantaneous manifest classification. (The cross-tick `done`
|
|
167
|
+
// is not derivable from a single snapshot, so this is instant-only.)
|
|
168
|
+
const classification: AgentStatus = verdict ?? instant;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
pane: { id: info.id, cmd: info.cmd, pid: info.pid, title: info.title },
|
|
172
|
+
authority: {
|
|
173
|
+
raw: authRaw,
|
|
174
|
+
state: authState,
|
|
175
|
+
epoch: authEpoch,
|
|
176
|
+
ageSeconds,
|
|
177
|
+
stale,
|
|
178
|
+
verdict,
|
|
179
|
+
},
|
|
180
|
+
hint: { raw: info.hintRaw || null, applied: resolved.source === "hint" },
|
|
181
|
+
resolution: {
|
|
182
|
+
manifestId: manifest?.id ?? null,
|
|
183
|
+
matchedCommand: resolved.matchedCommand,
|
|
184
|
+
source: resolved.source,
|
|
185
|
+
confidence: manifest ? (manifest.confidence ?? "conservative") : null,
|
|
186
|
+
subtree,
|
|
187
|
+
},
|
|
188
|
+
states: explained.checked,
|
|
189
|
+
winner: explained.state,
|
|
190
|
+
instant,
|
|
191
|
+
classification,
|
|
192
|
+
bottomLines: snapshot.bottomNonEmpty.slice(-5),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const STATUS_COLOR: Record<AgentStatus, string> = {
|
|
197
|
+
blocked: "\x1b[31m", // red
|
|
198
|
+
working: "\x1b[33m", // yellow
|
|
199
|
+
done: "\x1b[32m", // green
|
|
200
|
+
idle: "\x1b[36m", // cyan
|
|
201
|
+
unknown: "\x1b[90m", // grey
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Render the report as colored human-readable text. */
|
|
205
|
+
export function renderReport(r: ExplainReport, opts: { color?: boolean } = {}): string {
|
|
206
|
+
const color = opts.color ?? !("NO_COLOR" in process.env);
|
|
207
|
+
const c = (code: string, s: string) => (color ? `${code}${s}\x1b[0m` : s);
|
|
208
|
+
const bold = (s: string) => c("\x1b[1m", s);
|
|
209
|
+
const dim = (s: string) => c("\x1b[2m", s);
|
|
210
|
+
const label = (s: string) => c("\x1b[36m", s);
|
|
211
|
+
const status = (s: AgentStatus) => c(STATUS_COLOR[s] ?? "", s);
|
|
212
|
+
const yesno = (v: boolean) => (v ? c("\x1b[32m", "yes") : dim("no"));
|
|
213
|
+
|
|
214
|
+
const out: string[] = [];
|
|
215
|
+
out.push(bold(`agent explain — ${r.pane.id}`));
|
|
216
|
+
out.push(` ${label("command")} ${r.pane.cmd} ${dim(`(pid ${r.pane.pid})`)}`);
|
|
217
|
+
if (r.pane.title) out.push(` ${label("title")} ${r.pane.title}`);
|
|
218
|
+
|
|
219
|
+
// Authority
|
|
220
|
+
if (r.authority.raw) {
|
|
221
|
+
const age =
|
|
222
|
+
r.authority.ageSeconds !== null ? ` ${dim(`(${r.authority.ageSeconds}s ago)`)}` : "";
|
|
223
|
+
const staleTag = r.authority.stale ? " " + c("\x1b[31m", "[STALE → ignored]") : "";
|
|
224
|
+
const verdict = r.authority.verdict
|
|
225
|
+
? status(r.authority.verdict)
|
|
226
|
+
: dim("none (stale/malformed)");
|
|
227
|
+
out.push(` ${label("authority")} ${r.authority.raw}${age}${staleTag} → ${verdict}`);
|
|
228
|
+
} else {
|
|
229
|
+
out.push(` ${label("authority")} ${dim("(unset — falling back to scraping)")}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Hint
|
|
233
|
+
if (r.hint.raw) {
|
|
234
|
+
out.push(
|
|
235
|
+
` ${label("hint")} @agent_hint=${r.hint.raw} → ${yesno(r.hint.applied)} applied`,
|
|
236
|
+
);
|
|
237
|
+
} else {
|
|
238
|
+
out.push(` ${label("hint")} ${dim("(unset)")}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Resolution
|
|
242
|
+
if (r.resolution.manifestId) {
|
|
243
|
+
const conf =
|
|
244
|
+
r.resolution.confidence === "tuned"
|
|
245
|
+
? c("\x1b[32m", "tuned")
|
|
246
|
+
: dim(r.resolution.confidence ?? "conservative");
|
|
247
|
+
out.push(
|
|
248
|
+
` ${label("manifest")} ${r.resolution.manifestId} ${dim(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))} [${conf}]`,
|
|
249
|
+
);
|
|
250
|
+
} else {
|
|
251
|
+
// No manifest matched — say so, and report what the process-tree DID see so
|
|
252
|
+
// the user can set an `@agent_hint` or add a manifest.
|
|
253
|
+
const saw =
|
|
254
|
+
r.resolution.subtree.length > 0 ? r.resolution.subtree.join(", ") : r.pane.cmd || "(nothing)";
|
|
255
|
+
out.push(` ${label("manifest")} ${dim("none matched")} — ${dim(`process-tree saw: ${saw}`)}`);
|
|
256
|
+
out.push(` ${dim("set `tmux set-option -p @agent_hint <agent>` to force one")}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Per-state rule results
|
|
260
|
+
out.push("");
|
|
261
|
+
out.push(bold(" state rules"));
|
|
262
|
+
if (r.states.length === 0) {
|
|
263
|
+
out.push(` ${dim("(no manifest resolved — nothing to evaluate)")}`);
|
|
264
|
+
} else {
|
|
265
|
+
for (const s of r.states) {
|
|
266
|
+
const mark = s.matched ? c("\x1b[32m", "✓ matched") : dim("· no match");
|
|
267
|
+
const win = r.winner === s.state ? " " + c("\x1b[1m", "← winner") : "";
|
|
268
|
+
out.push(` ${s.state.padEnd(8)} ${mark}${win}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Final classification
|
|
273
|
+
out.push("");
|
|
274
|
+
out.push(
|
|
275
|
+
` ${bold("classification")} ${status(r.classification)} ${dim(`(instant: ${r.instant})`)}`,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Snapshot tail
|
|
279
|
+
out.push("");
|
|
280
|
+
out.push(bold(" bottom 5 lines judged"));
|
|
281
|
+
if (r.bottomLines.length === 0) {
|
|
282
|
+
out.push(` ${dim("(empty capture)")}`);
|
|
283
|
+
} else {
|
|
284
|
+
for (const line of r.bottomLines) out.push(` ${dim("│")} ${line}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return out.join("\n");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** CLI entry: build + print the report (human or JSON). */
|
|
291
|
+
export function agentExplain(target: string, opts: { json?: boolean } = {}): void {
|
|
292
|
+
const report = buildReport(target);
|
|
293
|
+
if (opts.json) {
|
|
294
|
+
console.log(JSON.stringify(report, null, 2));
|
|
295
|
+
} else {
|
|
296
|
+
console.log(renderReport(report));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { getSessionName } from "./lib/yaml-io.ts";
|
|
3
|
+
import { outputError } from "./lib/output.ts";
|
|
4
|
+
import { attachSession, getSessionState } from "@tmux-ide/tmux-bridge";
|
|
5
|
+
|
|
6
|
+
export async function attach(
|
|
7
|
+
targetDir: string | undefined,
|
|
8
|
+
{ json: _json }: { json?: boolean } = {},
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const dir = resolve(targetDir ?? ".");
|
|
11
|
+
const { name: session } = getSessionName(dir);
|
|
12
|
+
const state = getSessionState(session);
|
|
13
|
+
|
|
14
|
+
if (!state.running) {
|
|
15
|
+
outputError(`Session "${session}" is not running. Start it with: tmux-ide`, "NOT_RUNNING");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
attachSession(session);
|
|
20
|
+
}
|