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,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tmux-ide cheat-sheet — an iPadOS-style "all the keys" command sheet.
|
|
3
|
+
*
|
|
4
|
+
* Pressing `⌥k` (or clicking the `[ ? keys ]` trigger in the chrome row) on any
|
|
5
|
+
* adopted session floats a `display-popup` running `tmux-ide cheatsheet`, which
|
|
6
|
+
* prints this grouped, styled sheet and then blocks until ANY key closes it.
|
|
7
|
+
*
|
|
8
|
+
* The sheet is STATIC content, so it renders as a plain ANSI CLI print — no
|
|
9
|
+
* OpenTUI/bun boot — which keeps the popup instant. To stop the sheet drifting
|
|
10
|
+
* from reality, its dynamic groups are SOURCED from live inputs: the configured
|
|
11
|
+
* key binds + theme tokens (`keys`/`theme`, defaulting to the app-config
|
|
12
|
+
* defaults) and the team app's `DEFAULT_KEYMAP`/`ACTION_ORDER`. Only the
|
|
13
|
+
* inherently-static tmux essentials group is hardcoded.
|
|
14
|
+
*
|
|
15
|
+
* `buildCheatsheet` and the bind/unbind command builders are PURE (tested); the
|
|
16
|
+
* CLI `cheatsheet` command wires the io (print + wait-for-key).
|
|
17
|
+
*/
|
|
18
|
+
import { DEFAULT_KEYS, DEFAULT_THEME, type AppKeys, type AppTheme } from "../../lib/app-config.ts";
|
|
19
|
+
import { ACTION_ORDER, DEFAULT_KEYMAP } from "../team/keymap.ts";
|
|
20
|
+
import { GRAMMAR_HELP } from "../../widgets/lib/grammar.ts";
|
|
21
|
+
import { PANEL_POPUPS, panelKey } from "./panels.ts";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The root-table key that floats this sheet: `M-k` (Alt+k). Like the switcher's
|
|
25
|
+
* popup key it lives in the ROOT table so it fires without the prefix from any adopted
|
|
26
|
+
* session, and Alt-letter keys are low-collision in terminal apps. Chosen to sit
|
|
27
|
+
* next to `M-p` (switcher) so the two chrome popups share an ergonomic home.
|
|
28
|
+
*/
|
|
29
|
+
export const CHEATSHEET_KEY = "M-k";
|
|
30
|
+
|
|
31
|
+
// --- ANSI styling — the CLI's bold/cyan/dim pattern (see bin/cli.ts). ---
|
|
32
|
+
const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;
|
|
33
|
+
const dim = (s: string) => `\x1b[2m${s}\x1b[22m`;
|
|
34
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[39m`;
|
|
35
|
+
const head = (s: string) => `\x1b[1;36m${s}\x1b[0m`;
|
|
36
|
+
const color = (code: number, s: string) => `\x1b[38;5;${code}m${s}\x1b[39m`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The 256-color index of a `colourN` theme token for the ANSI legend, or null
|
|
40
|
+
* for a non-`colourN` token (e.g. a hex color) — the caller then renders it dim
|
|
41
|
+
* rather than mis-mapping the code.
|
|
42
|
+
*/
|
|
43
|
+
function tokenCode(token: string): number | null {
|
|
44
|
+
const m = /^colou?r(\d+)$/.exec(token);
|
|
45
|
+
if (!m) return null;
|
|
46
|
+
const n = Number(m[1]);
|
|
47
|
+
return Number.isInteger(n) && n >= 0 && n <= 255 ? n : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Render a glyph in a theme status token's color for the legend (dim fallback). */
|
|
51
|
+
function legendMark(token: string, glyph: string): string {
|
|
52
|
+
const code = tokenCode(token);
|
|
53
|
+
return code === null ? dim(glyph) : color(code, glyph);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render a tmux key name for humans: `M-` → `⌥`, `C-` → `^`, `S-` → `⇧`. Keeps
|
|
58
|
+
* the sheet's key hints sourced from the real `M-…` constants instead of a
|
|
59
|
+
* separately-maintained pretty string.
|
|
60
|
+
*/
|
|
61
|
+
function renderKey(tmuxKey: string): string {
|
|
62
|
+
return tmuxKey.replace(/M-/g, "⌥").replace(/C-/g, "^").replace(/S-/g, "⇧");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clip a styled line to `width` VISIBLE columns, passing ANSI escapes through
|
|
67
|
+
* untouched (they cost no width) and closing any open style with a reset so a
|
|
68
|
+
* mid-style cut can't bleed into the next line. Guarantees the sheet respects a
|
|
69
|
+
* narrow popup without mangling color codes.
|
|
70
|
+
*/
|
|
71
|
+
function clip(line: string, width: number): string {
|
|
72
|
+
let out = "";
|
|
73
|
+
let visible = 0;
|
|
74
|
+
let i = 0;
|
|
75
|
+
while (i < line.length) {
|
|
76
|
+
if (line[i] === "\x1b") {
|
|
77
|
+
// eslint-disable-next-line no-control-regex -- matching the ANSI SGR escape
|
|
78
|
+
const m = /^\x1b\[[0-9;]*m/.exec(line.slice(i));
|
|
79
|
+
if (m) {
|
|
80
|
+
out += m[0];
|
|
81
|
+
i += m[0].length;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (visible >= width) break;
|
|
86
|
+
out += line[i];
|
|
87
|
+
visible++;
|
|
88
|
+
i++;
|
|
89
|
+
}
|
|
90
|
+
return `${out}\x1b[0m`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Visible-width of a styled string (ANSI escapes stripped). */
|
|
94
|
+
function visibleWidth(s: string): number {
|
|
95
|
+
// eslint-disable-next-line no-control-regex -- stripping ANSI SGR escapes
|
|
96
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build the full ANSI cheat sheet, one screenful for a ~100x28 popup.
|
|
101
|
+
*
|
|
102
|
+
* Groups (each under a bold-cyan heading):
|
|
103
|
+
* 1. dock — the ⌥p / ⌥k popups, the clickable bar, and the glyph legend.
|
|
104
|
+
* 2. picker — the keys live INSIDE the ⌥p switcher popup.
|
|
105
|
+
* 3. team app — DERIVED from ACTION_ORDER/DEFAULT_KEYMAP (two columns if the
|
|
106
|
+
* width allows), so it never drifts from the real keymap.
|
|
107
|
+
* 4. tmux — the prefix essentials (inherently static → hardcoded).
|
|
108
|
+
* 5. cli — the handful of scriptable commands worth remembering.
|
|
109
|
+
*
|
|
110
|
+
* Every line is clipped to `width` so nothing overflows a narrow popup.
|
|
111
|
+
*/
|
|
112
|
+
export function buildCheatsheet(opts: { width: number; keys?: AppKeys; theme?: AppTheme }): string {
|
|
113
|
+
const width = Math.max(20, opts.width);
|
|
114
|
+
const keys = opts.keys ?? DEFAULT_KEYS;
|
|
115
|
+
const theme = opts.theme ?? DEFAULT_THEME;
|
|
116
|
+
const lines: string[] = [];
|
|
117
|
+
const pad = (s: string) => ` ${s}`; // two-space indent under each heading
|
|
118
|
+
|
|
119
|
+
// Title.
|
|
120
|
+
lines.push(`${head(" tmux-ide")} ${dim("cheat sheet — press any key to close")}`);
|
|
121
|
+
lines.push("");
|
|
122
|
+
|
|
123
|
+
// 1. dock — sourced key hints from the configured keys.
|
|
124
|
+
lines.push(head("dock"));
|
|
125
|
+
lines.push(
|
|
126
|
+
pad(
|
|
127
|
+
`${bold(renderKey(keys.home))} home cockpit ${bold(renderKey(keys.popup))} switcher popup ${bold(renderKey(keys.cheatsheet))} this sheet ${bold(renderKey(keys.menu))} actions menu ${bold(renderKey(keys.sidebar))} sidebar`,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
lines.push(
|
|
131
|
+
pad(
|
|
132
|
+
dim(
|
|
133
|
+
`bar: click a project tab = switch there · [ ⌂ home ${renderKey(keys.home)} ] = home · [ ⧉ switch ${renderKey(keys.popup)} ] = switcher · right-click anywhere = menu`,
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
const active = theme.glyphs.active;
|
|
138
|
+
const legend =
|
|
139
|
+
`${legendMark(theme.status.blocked, active)} blocked ${legendMark(theme.status.working, active)} working ` +
|
|
140
|
+
`${legendMark(theme.status.done, active)} done ${legendMark(theme.status.idle, active)} idle ` +
|
|
141
|
+
`${dim("·")} unknown ${dim(theme.glyphs.inactive)} stopped`;
|
|
142
|
+
lines.push(pad(legend));
|
|
143
|
+
lines.push("");
|
|
144
|
+
|
|
145
|
+
// 1a. prefix twins — the RELIABLE bindings (the tmux prefix survives every
|
|
146
|
+
// keyboard protocol; Alt keys don't always). Letters pinned against
|
|
147
|
+
// prefixKeyBinds by a statusline test so they can't drift.
|
|
148
|
+
lines.push(head("prefix keys (always work)"));
|
|
149
|
+
lines.push(
|
|
150
|
+
pad(
|
|
151
|
+
`${bold("prefix h")} home ${bold("prefix j")} switcher ${bold("prefix k")} keys ${bold("prefix u")} menu ${bold("prefix b")} sidebar ${bold("prefix e")} files ${bold("prefix g")} changes ${bold("prefix v")} config`,
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
lines.push(
|
|
155
|
+
pad(dim(`prefix = your tmux prefix (usually C-b) — use these when Alt keys don't reach tmux`)),
|
|
156
|
+
);
|
|
157
|
+
lines.push("");
|
|
158
|
+
|
|
159
|
+
// 1b. panels — every widget floats as a popup, one key away, esc to close.
|
|
160
|
+
lines.push(head("panels"));
|
|
161
|
+
const panelHints = PANEL_POPUPS.map(
|
|
162
|
+
(p) => `${bold(renderKey(panelKey(p, keys.panels)))} ${p.label}`,
|
|
163
|
+
).join(" ");
|
|
164
|
+
lines.push(pad(`${panelHints} ${dim("esc/q closes any panel")}`));
|
|
165
|
+
lines.push("");
|
|
166
|
+
|
|
167
|
+
// 1c. grammar — the ONE interaction grammar every panel + the sidebar share,
|
|
168
|
+
// sourced from GRAMMAR_HELP so it can never drift from the widgets.
|
|
169
|
+
lines.push(head("in panels & sidebar"));
|
|
170
|
+
const gKeyW = Math.max(...GRAMMAR_HELP.map((r) => r.keys.length));
|
|
171
|
+
for (const row of GRAMMAR_HELP) {
|
|
172
|
+
lines.push(pad(`${bold(row.keys.padEnd(gKeyW))} ${dim(row.label)}`));
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
|
|
176
|
+
// 2. picker — keys inside the switcher popup.
|
|
177
|
+
lines.push(head(`picker ${dim(`(inside the ${renderKey(keys.popup)} popup)`)}`));
|
|
178
|
+
lines.push(
|
|
179
|
+
pad(`${bold("↵")} switch ${bold("l")} launch ${bold("/")} find ${bold("esc")} close`),
|
|
180
|
+
);
|
|
181
|
+
lines.push("");
|
|
182
|
+
|
|
183
|
+
// 3. team app — derived from the real keymap, two columns when it fits.
|
|
184
|
+
lines.push(head("team app"));
|
|
185
|
+
const cells = ACTION_ORDER.map((action) => {
|
|
186
|
+
const binding = DEFAULT_KEYMAP[action];
|
|
187
|
+
return { keys: binding.keys.join("/"), desc: binding.description };
|
|
188
|
+
});
|
|
189
|
+
const keyW = Math.max(...cells.map((c) => c.keys.length));
|
|
190
|
+
const descW = Math.max(...cells.map((c) => c.desc.length));
|
|
191
|
+
const cellW = keyW + 2 + descW; // "keys" + gap + "desc" visible width
|
|
192
|
+
const renderCell = (c: { keys: string; desc: string }): string => {
|
|
193
|
+
const text = `${bold(c.keys.padEnd(keyW))} ${dim(c.desc)}`;
|
|
194
|
+
return text + " ".repeat(Math.max(0, cellW - visibleWidth(text)));
|
|
195
|
+
};
|
|
196
|
+
const twoCols = width >= cellW * 2 + 4;
|
|
197
|
+
if (twoCols) {
|
|
198
|
+
const half = Math.ceil(cells.length / 2);
|
|
199
|
+
for (let i = 0; i < half; i++) {
|
|
200
|
+
const left = cells[i];
|
|
201
|
+
const right = cells[i + half];
|
|
202
|
+
const rendered = left ? renderCell(left) : "";
|
|
203
|
+
lines.push(pad(right ? `${rendered} ${renderCell(right)}` : rendered));
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
for (const c of cells) lines.push(pad(renderCell(c)));
|
|
207
|
+
}
|
|
208
|
+
lines.push("");
|
|
209
|
+
|
|
210
|
+
// 4. tmux essentials — static.
|
|
211
|
+
lines.push(head("tmux essentials"));
|
|
212
|
+
lines.push(
|
|
213
|
+
pad(
|
|
214
|
+
`${bold("prefix d")} detach ${bold("prefix z")} zoom pane ${bold("prefix [")} copy mode`,
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
lines.push(
|
|
218
|
+
pad(
|
|
219
|
+
`${bold("prefix c")} new window ${bold("prefix n/p")} next/prev ${bold('prefix % / "')} splits`,
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
lines.push("");
|
|
223
|
+
|
|
224
|
+
// 5. cli — scriptable commands.
|
|
225
|
+
lines.push(head("cli"));
|
|
226
|
+
lines.push(pad(cyan("tmux-ide team --json")));
|
|
227
|
+
lines.push(pad(cyan("tmux-ide wait agent-status <s> --status done")));
|
|
228
|
+
lines.push(pad(cyan("tmux-ide adopt/unadopt <session>")));
|
|
229
|
+
lines.push(pad(cyan("tmux-ide worktree create <branch>") + dim(" (⎇ in the menu)")));
|
|
230
|
+
|
|
231
|
+
return lines.map((line) => clip(line, width)).join("\n");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* PURE — the `display-popup` command STRING that floats the cheat sheet (shared
|
|
236
|
+
* by the M-k bind, the bar's `[ ? keys ]` left-click router, and the actions
|
|
237
|
+
* menu's "Cheat sheet" item, so all three open an identical popup). Mirror of the
|
|
238
|
+
* sizing in {@link cheatsheetBindCommand}.
|
|
239
|
+
*/
|
|
240
|
+
export function cheatsheetPopupCommand(cheatsheetCmd = "tmux-ide cheatsheet"): string {
|
|
241
|
+
return `display-popup -E -w 90% -h 80% "${cheatsheetCmd}"`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* PURE — the tmux argv that binds {@link CHEATSHEET_KEY}: `M-k` opens a
|
|
246
|
+
* `display-popup` running the cheat sheet (which waits for any key, then exits,
|
|
247
|
+
* closing the popup). Server-wide root-table bind, mirroring the switcher's
|
|
248
|
+
* popup key — see the note on `unadoptSession`.
|
|
249
|
+
*/
|
|
250
|
+
export function cheatsheetBindCommand(
|
|
251
|
+
cheatsheetCmd = "tmux-ide cheatsheet",
|
|
252
|
+
key = CHEATSHEET_KEY,
|
|
253
|
+
): string[] {
|
|
254
|
+
return ["bind-key", "-n", key, "display-popup", "-E", "-w", "90%", "-h", "80%", cheatsheetCmd];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** PURE — the tmux argv that removes the cheat-sheet key binding. */
|
|
258
|
+
export function cheatsheetUnbindCommand(key = CHEATSHEET_KEY): string[] {
|
|
259
|
+
return ["unbind-key", "-n", key];
|
|
260
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-pane agent chips — the border label rendered INSIDE an adopted session.
|
|
3
|
+
*
|
|
4
|
+
* Where the status bar ({@link ./statusline.ts}) shows the fleet rollup (one
|
|
5
|
+
* glyph per session), a chip shows per-PANE truth: what agent is running in
|
|
6
|
+
* THIS pane and its live state — `claude · working`, `codex · blocked`. The
|
|
7
|
+
* updater writes each adopted pane's chip into a `@tmux_ide_chip` pane option
|
|
8
|
+
* and `adoptSession` points `pane-border-format` at it (falling back to the
|
|
9
|
+
* pane title when empty). `paneChip` is pure (tested); the io lives in the
|
|
10
|
+
* updater.
|
|
11
|
+
*/
|
|
12
|
+
import { DEFAULT_THEME, type AppTheme } from "../../lib/app-config.ts";
|
|
13
|
+
import type { AgentStatus } from "../detect/classify.ts";
|
|
14
|
+
import { statusStyle } from "./statusline.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* PURE — build a pane's chip string with tmux `#[...]` markup, or `""` for a
|
|
18
|
+
* non-agent pane (`agent === null`, e.g. a raw shell). An empty chip clears the
|
|
19
|
+
* pane option so the border format falls back to the pane title. The chip is
|
|
20
|
+
* `<agent> · <status>` styled via the shared {@link statusStyle} so it reads the
|
|
21
|
+
* same as its session's rollup glyph — one palette across the bar and the chips.
|
|
22
|
+
*/
|
|
23
|
+
export function paneChip(
|
|
24
|
+
agent: string | null,
|
|
25
|
+
status: AgentStatus,
|
|
26
|
+
theme: AppTheme = DEFAULT_THEME,
|
|
27
|
+
): string {
|
|
28
|
+
if (!agent) return "";
|
|
29
|
+
return `${statusStyle(status, theme)}${agent} · ${status}#[default]`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-state TRANSITION event log — the fleet's history layer.
|
|
3
|
+
*
|
|
4
|
+
* The chrome {@link ./updater.ts updater} sees every session's status EACH
|
|
5
|
+
* tick, but a status bar is a snapshot: it can't tell you that
|
|
6
|
+
* `prototyper-platform` went `working → done` at 12:31. This module turns the
|
|
7
|
+
* per-tick fleet into a stream of transition events and appends them to a
|
|
8
|
+
* JSONL log that `tmux-ide events` tails.
|
|
9
|
+
*
|
|
10
|
+
* Split as usual: {@link diffFleet} + {@link formatEventLine} +
|
|
11
|
+
* {@link shouldRotate} are PURE (unit-tested without a filesystem), while
|
|
12
|
+
* {@link appendEvents} is the thin io wrapper that stamps `ts`, rotates the
|
|
13
|
+
* file, and appends.
|
|
14
|
+
*/
|
|
15
|
+
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import type { AgentStatus } from "../detect/classify.ts";
|
|
19
|
+
|
|
20
|
+
/** A single session-level status transition. `from` is null the first time a session is seen. */
|
|
21
|
+
export interface AgentEvent {
|
|
22
|
+
ts: string;
|
|
23
|
+
session: string;
|
|
24
|
+
from: AgentStatus | null;
|
|
25
|
+
to: AgentStatus;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A transition before it's been stamped with a timestamp (what {@link diffFleet} emits). */
|
|
29
|
+
export type AgentEventInit = Omit<AgentEvent, "ts">;
|
|
30
|
+
|
|
31
|
+
/** Rotate the log once it grows past this many bytes (crude one-generation rotation). */
|
|
32
|
+
export const EVENTS_MAX_BYTES = 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* PURE — diff the previous fleet state against the current one.
|
|
36
|
+
*
|
|
37
|
+
* For each session in `next` we emit a transition when its status changed, and
|
|
38
|
+
* a first-sight event (`from: null`) the first time we see it. A session that
|
|
39
|
+
* DISAPPEARS from `next` emits NOTHING — we don't synthesize an `idle`/gone
|
|
40
|
+
* event, we just drop it from the returned `state` (keeps the log honest: an
|
|
41
|
+
* absent session simply stops producing events until it returns).
|
|
42
|
+
*
|
|
43
|
+
* Returns both the new events and the fresh state map to thread into the next
|
|
44
|
+
* tick. The caller stamps `ts` on the events (see {@link appendEvents}).
|
|
45
|
+
*/
|
|
46
|
+
export function diffFleet(
|
|
47
|
+
prev: Map<string, AgentStatus>,
|
|
48
|
+
next: Array<{ name: string; status: AgentStatus }>,
|
|
49
|
+
): { events: AgentEventInit[]; state: Map<string, AgentStatus> } {
|
|
50
|
+
const state = new Map<string, AgentStatus>();
|
|
51
|
+
const events: AgentEventInit[] = [];
|
|
52
|
+
for (const { name, status } of next) {
|
|
53
|
+
const before = prev.has(name) ? prev.get(name)! : null;
|
|
54
|
+
state.set(name, status);
|
|
55
|
+
if (before === null) {
|
|
56
|
+
events.push({ session: name, from: null, to: status });
|
|
57
|
+
} else if (before !== status) {
|
|
58
|
+
events.push({ session: name, from: before, to: status });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { events, state };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** PURE — whether a log of `sizeBytes` is due for rotation. */
|
|
65
|
+
export function shouldRotate(sizeBytes: number): boolean {
|
|
66
|
+
return sizeBytes > EVENTS_MAX_BYTES;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Extract `HH:MM:SS` from an ISO-8601 timestamp (UTC); falls back to the raw string. */
|
|
70
|
+
function isoTime(ts: string): string {
|
|
71
|
+
const m = /T(\d{2}:\d{2}:\d{2})/.exec(ts);
|
|
72
|
+
return m ? m[1]! : ts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* PURE — render one event for the human-readable `tmux-ide events` view, e.g.
|
|
77
|
+
* `12:31:07 prototyper-platform working → done`. First-sight events (from=null)
|
|
78
|
+
* render the origin as `·`. `paint` colors a status token (defaults to
|
|
79
|
+
* identity so tests get plain text); the CLI passes an ANSI painter.
|
|
80
|
+
*/
|
|
81
|
+
export function formatEventLine(
|
|
82
|
+
ev: AgentEvent,
|
|
83
|
+
paint: (status: AgentStatus | null, text: string) => string = (_s, t) => t,
|
|
84
|
+
): string {
|
|
85
|
+
const from = ev.from === null ? "·" : paint(ev.from, ev.from);
|
|
86
|
+
return `${isoTime(ev.ts)} ${ev.session} ${from} → ${paint(ev.to, ev.to)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Absolute path to the fleet event log. */
|
|
90
|
+
export function eventsPath(): string {
|
|
91
|
+
return join(homedir(), ".tmux-ide", "events.jsonl");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* io — stamp each event with `now()` and append it to the log as JSONL.
|
|
96
|
+
*
|
|
97
|
+
* Before appending, rotates the log to `events.jsonl.1` (overwriting any prior
|
|
98
|
+
* generation) when it's grown past {@link EVENTS_MAX_BYTES}. Best-effort: a
|
|
99
|
+
* filesystem failure must never break the updater loop, so every step is
|
|
100
|
+
* guarded. No-op when there are no events.
|
|
101
|
+
*/
|
|
102
|
+
export function appendEvents(
|
|
103
|
+
events: AgentEventInit[],
|
|
104
|
+
now: () => string = () => new Date().toISOString(),
|
|
105
|
+
): void {
|
|
106
|
+
if (events.length === 0) return;
|
|
107
|
+
const path = eventsPath();
|
|
108
|
+
try {
|
|
109
|
+
mkdirSync(join(homedir(), ".tmux-ide"), { recursive: true });
|
|
110
|
+
if (existsSync(path) && shouldRotate(statSync(path).size)) {
|
|
111
|
+
renameSync(path, `${path}.1`);
|
|
112
|
+
}
|
|
113
|
+
const ts = now();
|
|
114
|
+
const lines = events.map((e) => `${JSON.stringify({ ts, ...e })}\n`).join("");
|
|
115
|
+
appendFileSync(path, lines);
|
|
116
|
+
} catch {
|
|
117
|
+
// Stale/failed log write just means a gap in history — never fatal.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty-keyboard-protocol fallback binds for the root-table Alt keys.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code enables the Kitty keyboard protocol with event-type reporting.
|
|
5
|
+
* Under it, the FOCUSED pane's terminal encodes e.g. Alt-m not as the legacy
|
|
6
|
+
* `ESC m` but as the CSI-u form `ESC[109;3:1u` (109 = 'm', 3 = the Alt modifier,
|
|
7
|
+
* `:1` = a key-PRESS event-type subparameter). tmux 3.6 does NOT normalize that
|
|
8
|
+
* `:1`-carrying full form back to `M-m`, so a root-table `bind-key -n M-m` never
|
|
9
|
+
* fires while a Claude Code pane is focused — every dock shortcut (⌥m/⌥p/⌥k/⌥b/
|
|
10
|
+
* ⌥e/⌥g/⌥,/⌥h) goes dead. Plain `ESC m` and the basic CSI-u `ESC[109;3u` both
|
|
11
|
+
* DO reach tmux as `M-m`; only the `:1` full form misses (proven live by
|
|
12
|
+
* injecting each encoding into a nested client).
|
|
13
|
+
*
|
|
14
|
+
* The fix is a tmux USER-KEY fallback per Alt bind: teach tmux the exact kitty
|
|
15
|
+
* escape as `user-keys[N]`, then bind `UserN` to the SAME action argv as the
|
|
16
|
+
* `M-…` key. tmux matches the raw escape against the user-key and fires the
|
|
17
|
+
* bind, so the shortcut works whether the focused pane speaks legacy or full
|
|
18
|
+
* kitty. Registering the fallback is harmless for panes that never send the
|
|
19
|
+
* `:1` form (nothing else emits `UserN`).
|
|
20
|
+
*
|
|
21
|
+
* Pure — tested as a table; the io (registering the binds) lives in
|
|
22
|
+
* {@link ./statusline.ts}.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* PURE — the full-kitty key-PRESS encoding for a single-char Alt key (`M-<c>`),
|
|
27
|
+
* or `null` for anything else (multi-char key names, non-Alt keys — they don't
|
|
28
|
+
* get a fallback). The sequence is `ESC[<code>;3:1u` where `<code>` is the code
|
|
29
|
+
* point of the (lowercased) character, `3` is the Alt modifier, and `:1` is the
|
|
30
|
+
* key-press event-type subparameter. Examples: `M-m` → `ESC[109;3:1u`, `M-h` →
|
|
31
|
+
* `ESC[104;3:1u`, `M-,` → `ESC[44;3:1u`.
|
|
32
|
+
*/
|
|
33
|
+
export function kittyEscapeFor(key: string): string | null {
|
|
34
|
+
const m = /^M-(.)$/.exec(key);
|
|
35
|
+
const ch = m?.[1];
|
|
36
|
+
if (ch === undefined) return null;
|
|
37
|
+
const code = ch.toLowerCase().codePointAt(0);
|
|
38
|
+
if (code === undefined) return null;
|
|
39
|
+
return `\x1b[${code};3:1u`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* PURE — the tmux `user-keys` array index for the Nth Alt fallback. We claim the
|
|
44
|
+
* range `[100, …)` (offset 100) so we never clobber a user's own
|
|
45
|
+
* `user-keys[0..]` entries — anyone hand-setting `user-keys` should keep their
|
|
46
|
+
* indices below 100.
|
|
47
|
+
*/
|
|
48
|
+
export function kittyUserKeyIndex(slot: number): number {
|
|
49
|
+
return 100 + slot;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** PURE — the tmux key name a `user-keys[idx]` entry surfaces as (`UserN`). */
|
|
53
|
+
export function kittyUserKeyName(slot: number): string {
|
|
54
|
+
return `User${kittyUserKeyIndex(slot)}`;
|
|
55
|
+
}
|