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,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed sync of the bundled Claude Code skill → `~/.claude/skills/tmux-ide`.
|
|
3
|
+
*
|
|
4
|
+
* `skill/SKILL.md` is the manual a coding agent loads to drive tmux-ide. It ships
|
|
5
|
+
* IN the package, but the copy that agents actually read lives in the user's
|
|
6
|
+
* `~/.claude/skills/` — so every install AND every update has to keep that copy
|
|
7
|
+
* fresh, or agents drive an old CLI. This module is that refresh: it copies the
|
|
8
|
+
* bundled SKILL.md into the tmux-ide skill dir with the version marker rewritten
|
|
9
|
+
* to the installed package version, and reports whether that was an install, an
|
|
10
|
+
* update, or a no-op.
|
|
11
|
+
*
|
|
12
|
+
* The dir is FULLY MANAGED: overwriting our own `~/.claude/skills/tmux-ide` is
|
|
13
|
+
* correct, and this NEVER touches anything outside it (user-authored skills in
|
|
14
|
+
* sibling dirs are safe). The version marker is line 2 of SKILL.md, an HTML
|
|
15
|
+
* comment ({@link VERSION_MARKER_RE}); {@link parseSkillVersion} reads it and
|
|
16
|
+
* {@link rewriteVersionMarker} substitutes it at copy time.
|
|
17
|
+
*
|
|
18
|
+
* The parse/render helpers are PURE and tested against fixtures; {@link syncSkill}
|
|
19
|
+
* is the thin io wrapper (tested with tmp dirs). Wired from three places — the
|
|
20
|
+
* npm `postinstall` (which re-implements the marker rewrite in plain JS since it
|
|
21
|
+
* can't import TS — see scripts/postinstall.js), `tmux-ide skill-sync` / the dev
|
|
22
|
+
* path of `tmux-ide update`, and `tmux-ide integration install claude`.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { getCurrentVersion } from "./update-check.ts";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Paths
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Claude Code's config dir: `TMUX_IDE_CLAUDE_DIR` when set (tests / per-run
|
|
36
|
+
* overrides), else `~/.claude`. Mirrors the {@link ../tui/integrations/claude.ts}
|
|
37
|
+
* settings override so both halves of the agent setup honor the same scratch dir.
|
|
38
|
+
*/
|
|
39
|
+
export function claudeDir(): string {
|
|
40
|
+
return process.env.TMUX_IDE_CLAUDE_DIR ?? join(homedir(), ".claude");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The fully-managed tmux-ide skill dir: `<claudeDir>/skills/tmux-ide`. */
|
|
44
|
+
export function skillTargetDir(): string {
|
|
45
|
+
return join(claudeDir(), "skills", "tmux-ide");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The installed SKILL.md path (inside {@link skillTargetDir}). */
|
|
49
|
+
export function skillTargetFile(): string {
|
|
50
|
+
return join(skillTargetDir(), "SKILL.md");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* io — the bundled SKILL.md that ships in the package. Resolved relative to this
|
|
55
|
+
* module, handling BOTH the esbuild-bundled CLI (this file inlines into
|
|
56
|
+
* `bin/cli.js`, so `here` is `bin/` and the skill is one level up) AND the dev
|
|
57
|
+
* source tree (`packages/daemon/src/lib/`, four levels below the repo root) — the
|
|
58
|
+
* same dual-candidate trick as {@link getCurrentVersion}. Returns the first that
|
|
59
|
+
* exists, else the bundled-layout guess.
|
|
60
|
+
*/
|
|
61
|
+
export function defaultSkillSource(): string {
|
|
62
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
63
|
+
const candidates = [
|
|
64
|
+
join(here, "../skill/SKILL.md"), // bundled bin/cli.js → repo root
|
|
65
|
+
join(here, "../../../../skill/SKILL.md"), // dev src/lib → repo root
|
|
66
|
+
];
|
|
67
|
+
return candidates.find((c) => existsSync(c)) ?? candidates[0]!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Pure — version marker
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The version marker line (an HTML comment on line 2 of SKILL.md). The sync
|
|
76
|
+
* machinery reads it to know which version an installed copy is, and rewrites it
|
|
77
|
+
* to the installed package version at copy time.
|
|
78
|
+
*/
|
|
79
|
+
export const VERSION_MARKER_RE = /<!--\s*tmux-ide-skill-version:\s*([^\s]+)\s*-->/;
|
|
80
|
+
|
|
81
|
+
/** PURE — render the marker line for a given version. */
|
|
82
|
+
export function versionMarker(version: string): string {
|
|
83
|
+
return `<!-- tmux-ide-skill-version: ${version} -->`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** PURE — the version in a SKILL.md body, or null when the marker is absent. */
|
|
87
|
+
export function parseSkillVersion(content: string): string | null {
|
|
88
|
+
const match = content.match(VERSION_MARKER_RE);
|
|
89
|
+
return match ? match[1]! : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* PURE — return `content` with its version marker rewritten to `version`. When no
|
|
94
|
+
* marker is present the content is returned unchanged (the bundled SKILL.md
|
|
95
|
+
* always carries one; this just never corrupts a hand-stripped file).
|
|
96
|
+
*/
|
|
97
|
+
export function rewriteVersionMarker(content: string, version: string): string {
|
|
98
|
+
if (!VERSION_MARKER_RE.test(content)) return content;
|
|
99
|
+
return content.replace(VERSION_MARKER_RE, versionMarker(version));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// io
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/** The version marker read off an installed SKILL.md, or null if absent/unreadable. */
|
|
107
|
+
export function installedSkillVersion(dir: string = skillTargetDir()): string | null {
|
|
108
|
+
const file = join(dir, "SKILL.md");
|
|
109
|
+
if (!existsSync(file)) return null;
|
|
110
|
+
try {
|
|
111
|
+
return parseSkillVersion(readFileSync(file, "utf-8"));
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** What a {@link syncSkill} did. */
|
|
118
|
+
export interface SyncResult {
|
|
119
|
+
/** `installed` = target was absent; `updated` = content changed; `unchanged` = no-op. */
|
|
120
|
+
action: "installed" | "updated" | "unchanged";
|
|
121
|
+
/** The written SKILL.md path. */
|
|
122
|
+
path: string;
|
|
123
|
+
/** The installed version before an `updated` (the marker we replaced), if any. */
|
|
124
|
+
from?: string | null;
|
|
125
|
+
/** The version written into the copy's marker. */
|
|
126
|
+
to: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* io — copy the bundled SKILL.md into the managed skill dir with its version
|
|
131
|
+
* marker rewritten to `version`. Content-driven and idempotent: the RENDERED
|
|
132
|
+
* source (marker substituted) is compared byte-for-byte against the installed
|
|
133
|
+
* copy — identical → `unchanged` (no write), absent → `installed`, otherwise
|
|
134
|
+
* `updated`. Only ever writes inside {@link skillTargetDir}; nothing outside it
|
|
135
|
+
* is read or touched.
|
|
136
|
+
*/
|
|
137
|
+
export function syncSkill({
|
|
138
|
+
source = defaultSkillSource(),
|
|
139
|
+
version = getCurrentVersion(),
|
|
140
|
+
}: { source?: string; version?: string } = {}): SyncResult {
|
|
141
|
+
const rendered = rewriteVersionMarker(readFileSync(source, "utf-8"), version);
|
|
142
|
+
const dir = skillTargetDir();
|
|
143
|
+
const target = join(dir, "SKILL.md");
|
|
144
|
+
|
|
145
|
+
const existing = existsSync(target) ? readFileSync(target, "utf-8") : null;
|
|
146
|
+
if (existing === rendered) {
|
|
147
|
+
return { action: "unchanged", path: target, to: version };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
mkdirSync(dir, { recursive: true });
|
|
151
|
+
writeFileSync(target, rendered, "utf-8");
|
|
152
|
+
|
|
153
|
+
if (existing === null) return { action: "installed", path: target, to: version };
|
|
154
|
+
return { action: "updated", path: target, from: parseSkillVersion(existing), to: version };
|
|
155
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminals registry (G20-P1).
|
|
3
|
+
*
|
|
4
|
+
* JSON-backed persistence for tab-strip metadata — names, scopes, and
|
|
5
|
+
* kind. The actual PTY process lives in `PtyBridgeRegistry`; this
|
|
6
|
+
* store only persists what the UI needs to restore tab labels across
|
|
7
|
+
* reloads. Atomic writes (temp + rename) so a crash mid-write leaves
|
|
8
|
+
* the previous list intact.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import type { Terminal, TerminalKind } from "@tmux-ide/contracts";
|
|
14
|
+
|
|
15
|
+
const TERMINALS_FILE = ".tmux-ide/terminals.json";
|
|
16
|
+
const SAFE_ID = /^[A-Za-z0-9_-]+$/u;
|
|
17
|
+
|
|
18
|
+
function path(dir: string): string {
|
|
19
|
+
return join(dir, TERMINALS_FILE);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureDir(dir: string): void {
|
|
23
|
+
mkdirSync(dirname(path(dir)), { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Load the full list for a session. Missing file → empty list. */
|
|
27
|
+
export function loadTerminals(dir: string): Terminal[] {
|
|
28
|
+
const file = path(dir);
|
|
29
|
+
if (!existsSync(file)) return [];
|
|
30
|
+
try {
|
|
31
|
+
const body = readFileSync(file, "utf-8");
|
|
32
|
+
const parsed = JSON.parse(body) as { terminals?: unknown };
|
|
33
|
+
if (!parsed.terminals || !Array.isArray(parsed.terminals)) return [];
|
|
34
|
+
return parsed.terminals.filter((t): t is Terminal => isTerminal(t)).map((t) => ({ ...t }));
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isTerminal(value: unknown): value is Terminal {
|
|
41
|
+
if (!value || typeof value !== "object") return false;
|
|
42
|
+
const v = value as Record<string, unknown>;
|
|
43
|
+
return (
|
|
44
|
+
typeof v.id === "string" &&
|
|
45
|
+
SAFE_ID.test(v.id) &&
|
|
46
|
+
typeof v.projectId === "string" &&
|
|
47
|
+
typeof v.scopeId === "string" &&
|
|
48
|
+
typeof v.name === "string" &&
|
|
49
|
+
(v.kind === "shell" || v.kind === "setup" || v.kind === "run" || v.kind === "teardown") &&
|
|
50
|
+
typeof v.createdAt === "string" &&
|
|
51
|
+
typeof v.updatedAt === "string"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeAtomic(dir: string, terminals: Terminal[]): void {
|
|
56
|
+
ensureDir(dir);
|
|
57
|
+
const file = path(dir);
|
|
58
|
+
const tmp = `${file}.tmp`;
|
|
59
|
+
writeFileSync(tmp, JSON.stringify({ terminals }, null, 2) + "\n");
|
|
60
|
+
renameSync(tmp, file);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CreateInput {
|
|
64
|
+
id: string;
|
|
65
|
+
projectId: string;
|
|
66
|
+
scopeId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
kind: TerminalKind;
|
|
69
|
+
scripted?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Insert OR replace a terminal record by id. Idempotent — calling
|
|
73
|
+
* `createTerminal` twice with the same deterministic id is a no-op
|
|
74
|
+
* on the canonical fields and updates `updatedAt`. */
|
|
75
|
+
export function upsertTerminal(dir: string, input: CreateInput): Terminal {
|
|
76
|
+
if (!SAFE_ID.test(input.id)) {
|
|
77
|
+
throw new Error(`invalid terminal id "${input.id}"`);
|
|
78
|
+
}
|
|
79
|
+
const existing = loadTerminals(dir);
|
|
80
|
+
const idx = existing.findIndex((t) => t.id === input.id);
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
const next: Terminal = {
|
|
83
|
+
id: input.id,
|
|
84
|
+
projectId: input.projectId,
|
|
85
|
+
scopeId: input.scopeId,
|
|
86
|
+
name: input.name,
|
|
87
|
+
kind: input.kind,
|
|
88
|
+
createdAt: existing[idx]?.createdAt ?? now,
|
|
89
|
+
updatedAt: now,
|
|
90
|
+
...(input.scripted ? { scripted: true } : {}),
|
|
91
|
+
};
|
|
92
|
+
if (idx === -1) existing.push(next);
|
|
93
|
+
else existing[idx] = next;
|
|
94
|
+
writeAtomic(dir, existing);
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function renameTerminal(dir: string, id: string, name: string): Terminal | null {
|
|
99
|
+
const all = loadTerminals(dir);
|
|
100
|
+
const idx = all.findIndex((t) => t.id === id);
|
|
101
|
+
if (idx === -1) return null;
|
|
102
|
+
const trimmed = name.trim();
|
|
103
|
+
if (!trimmed) throw new Error("name required");
|
|
104
|
+
const next: Terminal = {
|
|
105
|
+
...all[idx]!,
|
|
106
|
+
name: trimmed,
|
|
107
|
+
updatedAt: new Date().toISOString(),
|
|
108
|
+
};
|
|
109
|
+
all[idx] = next;
|
|
110
|
+
writeAtomic(dir, all);
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function deleteTerminal(dir: string, id: string): boolean {
|
|
115
|
+
const all = loadTerminals(dir);
|
|
116
|
+
const next = all.filter((t) => t.id !== id);
|
|
117
|
+
if (next.length === all.length) return false;
|
|
118
|
+
writeAtomic(dir, next);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function findTerminal(dir: string, id: string): Terminal | null {
|
|
123
|
+
const all = loadTerminals(dir);
|
|
124
|
+
return all.find((t) => t.id === id) ?? null;
|
|
125
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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.ts";
|
|
20
|
+
|
|
21
|
+
/** The `<os>-<arch>` tags we publish a prebuilt TUI binary for. */
|
|
22
|
+
export type TuiPlatformTag = "darwin-arm64" | "darwin-x64" | "linux-x64" | "linux-arm64";
|
|
23
|
+
|
|
24
|
+
/** The GitHub repo the release assets live under. */
|
|
25
|
+
export const RELEASE_REPO = "wavyrai/tmux-ide";
|
|
26
|
+
|
|
27
|
+
/** A downloaded binary smaller than this is treated as corrupt/truncated. */
|
|
28
|
+
export const MIN_TUI_BINARY_BYTES = 10 * 1024 * 1024;
|
|
29
|
+
|
|
30
|
+
const SUPPORTED: Record<string, TuiPlatformTag> = {
|
|
31
|
+
"darwin-arm64": "darwin-arm64",
|
|
32
|
+
"darwin-x64": "darwin-x64",
|
|
33
|
+
"linux-x64": "linux-x64",
|
|
34
|
+
"linux-arm64": "linux-arm64",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Pure
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* PURE — map a Node `process.platform`/`process.arch` pair to the release tag,
|
|
43
|
+
* or null when we don't publish a binary for it (e.g. windows, freebsd) so the
|
|
44
|
+
* caller can fall back to the "install bun" message.
|
|
45
|
+
*/
|
|
46
|
+
export function tuiPlatformTag(
|
|
47
|
+
platform: NodeJS.Platform = process.platform,
|
|
48
|
+
arch: string = process.arch,
|
|
49
|
+
): TuiPlatformTag | null {
|
|
50
|
+
return SUPPORTED[`${platform}-${arch}`] ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** PURE — the `bun build --compile --target` flag for a tag (`bun-<tag>`). */
|
|
54
|
+
export function bunTargetForTag(tag: TuiPlatformTag): string {
|
|
55
|
+
return `bun-${tag}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** PURE — the release asset filename for a tag (gzip-compressed binary). */
|
|
59
|
+
export function releaseAssetName(tag: TuiPlatformTag): string {
|
|
60
|
+
return `tmux-ide-tui-${tag}.gz`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** PURE — strip a leading `v` from a version string; `2.6.1` and `v2.6.1` both → `2.6.1`. */
|
|
64
|
+
export function normalizeVersion(version: string): string {
|
|
65
|
+
return version.startsWith("v") ? version.slice(1) : version;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* PURE — the GitHub download URL for a platform's asset at a given version:
|
|
70
|
+
* `https://github.com/<repo>/releases/download/v<version>/tmux-ide-tui-<tag>.gz`.
|
|
71
|
+
*/
|
|
72
|
+
export function releaseAssetUrl(version: string, tag: TuiPlatformTag): string {
|
|
73
|
+
return `https://github.com/${RELEASE_REPO}/releases/download/v${normalizeVersion(version)}/${releaseAssetName(tag)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* PURE — where a downloaded binary lives: `<home>/bin/tmux-ide-tui-<tag>-<version>`.
|
|
78
|
+
* The version is stamped INTO the name so a `tmux-ide update` to a new version
|
|
79
|
+
* misses the old download and re-fetches (rather than launching a stale binary).
|
|
80
|
+
*/
|
|
81
|
+
export function downloadedTuiPath(home: string, tag: TuiPlatformTag, version: string): string {
|
|
82
|
+
return join(home, "bin", `tmux-ide-tui-${tag}-${normalizeVersion(version)}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// io
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* io — the tmux-ide state home (`TMUX_IDE_HOME` override, else `~/.tmux-ide`),
|
|
91
|
+
* the same resolution the update-check cache and welcome marker use.
|
|
92
|
+
*/
|
|
93
|
+
export function tuiStateHome(): string {
|
|
94
|
+
return process.env.TMUX_IDE_HOME ?? join(homedir(), ".tmux-ide");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* io — locate a previously downloaded per-platform binary for THIS version and
|
|
99
|
+
* platform, or null. Feeds the resolution order in `tui/compiled.ts` after the
|
|
100
|
+
* shipped/local compiled binary and before the honest "unavailable" error.
|
|
101
|
+
*/
|
|
102
|
+
export function findDownloadedTui(version: string = getCurrentVersion()): string | null {
|
|
103
|
+
const tag = tuiPlatformTag();
|
|
104
|
+
if (!tag) return null;
|
|
105
|
+
const path = downloadedTuiPath(tuiStateHome(), tag, version);
|
|
106
|
+
return existsSync(path) ? path : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* io — download, verify, and install the per-platform TUI binary for the running
|
|
111
|
+
* version. Fetches the gzip asset, inflates it, rejects anything under
|
|
112
|
+
* {@link MIN_TUI_BINARY_BYTES} (a truncated download or an HTML error page),
|
|
113
|
+
* writes it `0o755`, and atomically renames it into place (temp-in-same-dir →
|
|
114
|
+
* rename, so a crashed download never leaves a half-written executable).
|
|
115
|
+
*
|
|
116
|
+
* Throws with an actionable message on an unsupported platform or a failed
|
|
117
|
+
* fetch. Returns the installed path and byte size.
|
|
118
|
+
*/
|
|
119
|
+
export async function downloadTuiBinary(
|
|
120
|
+
opts: {
|
|
121
|
+
version?: string;
|
|
122
|
+
log?: (msg: string) => void;
|
|
123
|
+
} = {},
|
|
124
|
+
): Promise<{ path: string; bytes: number }> {
|
|
125
|
+
const log = opts.log ?? (() => {});
|
|
126
|
+
const version = normalizeVersion(opts.version ?? getCurrentVersion());
|
|
127
|
+
const tag = tuiPlatformTag();
|
|
128
|
+
if (!tag) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`no prebuilt TUI binary is published for ${process.platform}-${process.arch} — ` +
|
|
131
|
+
`install bun (https://bun.sh) to run the TUI surfaces from source instead`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const url = releaseAssetUrl(version, tag);
|
|
136
|
+
const dest = downloadedTuiPath(tuiStateHome(), tag, version);
|
|
137
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
138
|
+
|
|
139
|
+
log(`downloading ${url}`);
|
|
140
|
+
const res = await fetch(url);
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`could not download the TUI binary (${url} → HTTP ${res.status} ${res.statusText}). ` +
|
|
144
|
+
`Check that release v${version} exists and published its assets.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const gz = Buffer.from(await res.arrayBuffer());
|
|
149
|
+
const bin = gunzipSync(gz);
|
|
150
|
+
if (bin.byteLength < MIN_TUI_BINARY_BYTES) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`the downloaded TUI binary is only ${bin.byteLength} bytes (expected >10MB) — ` +
|
|
153
|
+
`treating it as corrupt and leaving the previous binary (if any) in place`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
158
|
+
writeFileSync(tmp, bin, { mode: 0o755 });
|
|
159
|
+
chmodSync(tmp, 0o755);
|
|
160
|
+
renameSync(tmp, dest);
|
|
161
|
+
|
|
162
|
+
const mb = (bin.byteLength / 1024 / 1024).toFixed(1);
|
|
163
|
+
log(`installed ${dest} (${mb} MB)`);
|
|
164
|
+
return { path: dest, bytes: bin.byteLength };
|
|
165
|
+
}
|