aimux-cli 0.1.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 +743 -0
- package/bin/aimux +2 -0
- package/dist/agent-events.d.ts +20 -0
- package/dist/agent-events.js +2 -0
- package/dist/agent-events.js.map +1 -0
- package/dist/agent-message-parts.d.ts +17 -0
- package/dist/agent-message-parts.js +31 -0
- package/dist/agent-message-parts.js.map +1 -0
- package/dist/agent-output-parser.d.ts +16 -0
- package/dist/agent-output-parser.js +229 -0
- package/dist/agent-output-parser.js.map +1 -0
- package/dist/agent-tracker.d.ts +9 -0
- package/dist/agent-tracker.js +144 -0
- package/dist/agent-tracker.js.map +1 -0
- package/dist/agent-watcher.d.ts +15 -0
- package/dist/agent-watcher.js +2 -0
- package/dist/agent-watcher.js.map +1 -0
- package/dist/attachment-store.d.ts +35 -0
- package/dist/attachment-store.js +129 -0
- package/dist/attachment-store.js.map +1 -0
- package/dist/builtin-metadata-watchers.d.ts +2 -0
- package/dist/builtin-metadata-watchers.js +275 -0
- package/dist/builtin-metadata-watchers.js.map +1 -0
- package/dist/claude-hooks.d.ts +29 -0
- package/dist/claude-hooks.js +106 -0
- package/dist/claude-hooks.js.map +1 -0
- package/dist/config.d.ts +78 -0
- package/dist/config.js +172 -0
- package/dist/config.js.map +1 -0
- package/dist/context/compactor.d.ts +20 -0
- package/dist/context/compactor.js +212 -0
- package/dist/context/compactor.js.map +1 -0
- package/dist/context/context-bridge.d.ts +67 -0
- package/dist/context/context-bridge.js +471 -0
- package/dist/context/context-bridge.js.map +1 -0
- package/dist/context/context-file.d.ts +11 -0
- package/dist/context/context-file.js +93 -0
- package/dist/context/context-file.js.map +1 -0
- package/dist/context/history.d.ts +40 -0
- package/dist/context/history.js +108 -0
- package/dist/context/history.js.map +1 -0
- package/dist/daemon.d.ts +39 -0
- package/dist/daemon.js +344 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dashboard-session-registry.d.ts +47 -0
- package/dist/dashboard-session-registry.js +161 -0
- package/dist/dashboard-session-registry.js.map +1 -0
- package/dist/dashboard-state.d.ts +18 -0
- package/dist/dashboard-state.js +26 -0
- package/dist/dashboard-state.js.map +1 -0
- package/dist/dashboard.d.ts +118 -0
- package/dist/dashboard.js +91 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/debug.d.ts +7 -0
- package/dist/debug.js +41 -0
- package/dist/debug.js.map +1 -0
- package/dist/fast-control.d.ts +45 -0
- package/dist/fast-control.js +174 -0
- package/dist/fast-control.js.map +1 -0
- package/dist/hotkeys.d.ts +44 -0
- package/dist/hotkeys.js +118 -0
- package/dist/hotkeys.js.map +1 -0
- package/dist/http-client.d.ts +10 -0
- package/dist/http-client.js +54 -0
- package/dist/http-client.js.map +1 -0
- package/dist/instance-directory.d.ts +32 -0
- package/dist/instance-directory.js +82 -0
- package/dist/instance-directory.js.map +1 -0
- package/dist/instance-registry.d.ts +38 -0
- package/dist/instance-registry.js +208 -0
- package/dist/instance-registry.js.map +1 -0
- package/dist/key-parser.d.ts +30 -0
- package/dist/key-parser.js +272 -0
- package/dist/key-parser.js.map +1 -0
- package/dist/last-used.d.ts +31 -0
- package/dist/last-used.js +93 -0
- package/dist/last-used.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +2483 -0
- package/dist/main.js.map +1 -0
- package/dist/metadata-server.d.ts +268 -0
- package/dist/metadata-server.js +1379 -0
- package/dist/metadata-server.js.map +1 -0
- package/dist/metadata-store.d.ts +80 -0
- package/dist/metadata-store.js +87 -0
- package/dist/metadata-store.js.map +1 -0
- package/dist/multiplexer.d.ts +471 -0
- package/dist/multiplexer.js +5714 -0
- package/dist/multiplexer.js.map +1 -0
- package/dist/notification-context.d.ts +18 -0
- package/dist/notification-context.js +68 -0
- package/dist/notification-context.js.map +1 -0
- package/dist/notifications.d.ts +38 -0
- package/dist/notifications.js +111 -0
- package/dist/notifications.js.map +1 -0
- package/dist/notify.d.ts +10 -0
- package/dist/notify.js +62 -0
- package/dist/notify.js.map +1 -0
- package/dist/orchestration-actions.d.ts +76 -0
- package/dist/orchestration-actions.js +310 -0
- package/dist/orchestration-actions.js.map +1 -0
- package/dist/orchestration-dispatcher.d.ts +22 -0
- package/dist/orchestration-dispatcher.js +49 -0
- package/dist/orchestration-dispatcher.js.map +1 -0
- package/dist/orchestration-routing.d.ts +20 -0
- package/dist/orchestration-routing.js +78 -0
- package/dist/orchestration-routing.js.map +1 -0
- package/dist/orchestration.d.ts +26 -0
- package/dist/orchestration.js +110 -0
- package/dist/orchestration.js.map +1 -0
- package/dist/osc-notifications.d.ts +15 -0
- package/dist/osc-notifications.js +180 -0
- package/dist/osc-notifications.js.map +1 -0
- package/dist/paths.d.ts +55 -0
- package/dist/paths.js +259 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugin-runtime.d.ts +46 -0
- package/dist/plugin-runtime.js +180 -0
- package/dist/plugin-runtime.js.map +1 -0
- package/dist/project-events.d.ts +36 -0
- package/dist/project-events.js +63 -0
- package/dist/project-events.js.map +1 -0
- package/dist/project-scanner.d.ts +38 -0
- package/dist/project-scanner.js +243 -0
- package/dist/project-scanner.js.map +1 -0
- package/dist/project-service-manifest.d.ts +18 -0
- package/dist/project-service-manifest.js +56 -0
- package/dist/project-service-manifest.js.map +1 -0
- package/dist/recency.d.ts +2 -0
- package/dist/recency.js +34 -0
- package/dist/recency.js.map +1 -0
- package/dist/recorder.d.ts +14 -0
- package/dist/recorder.js +130 -0
- package/dist/recorder.js.map +1 -0
- package/dist/session-bootstrap.d.ts +45 -0
- package/dist/session-bootstrap.js +436 -0
- package/dist/session-bootstrap.js.map +1 -0
- package/dist/session-message-history.d.ts +27 -0
- package/dist/session-message-history.js +105 -0
- package/dist/session-message-history.js.map +1 -0
- package/dist/session-runtime.d.ts +44 -0
- package/dist/session-runtime.js +56 -0
- package/dist/session-runtime.js.map +1 -0
- package/dist/session-semantics.d.ts +35 -0
- package/dist/session-semantics.js +110 -0
- package/dist/session-semantics.js.map +1 -0
- package/dist/status-detector.d.ts +17 -0
- package/dist/status-detector.js +67 -0
- package/dist/status-detector.js.map +1 -0
- package/dist/statusline-model.d.ts +103 -0
- package/dist/statusline-model.js +177 -0
- package/dist/statusline-model.js.map +1 -0
- package/dist/task-dispatcher.d.ts +63 -0
- package/dist/task-dispatcher.js +210 -0
- package/dist/task-dispatcher.js.map +1 -0
- package/dist/task-workflow.d.ts +13 -0
- package/dist/task-workflow.js +153 -0
- package/dist/task-workflow.js.map +1 -0
- package/dist/tasks.d.ts +60 -0
- package/dist/tasks.js +120 -0
- package/dist/tasks.js.map +1 -0
- package/dist/team.d.ts +28 -0
- package/dist/team.js +91 -0
- package/dist/team.js.map +1 -0
- package/dist/terminal-host.d.ts +10 -0
- package/dist/terminal-host.js +52 -0
- package/dist/terminal-host.js.map +1 -0
- package/dist/threads.d.ts +61 -0
- package/dist/threads.js +200 -0
- package/dist/threads.js.map +1 -0
- package/dist/tmux-doctor.d.ts +47 -0
- package/dist/tmux-doctor.js +112 -0
- package/dist/tmux-doctor.js.map +1 -0
- package/dist/tmux-runtime-manager.d.ts +164 -0
- package/dist/tmux-runtime-manager.js +794 -0
- package/dist/tmux-runtime-manager.js.map +1 -0
- package/dist/tmux-session-transport.d.ts +31 -0
- package/dist/tmux-session-transport.js +115 -0
- package/dist/tmux-session-transport.js.map +1 -0
- package/dist/tmux-statusline.d.ts +17 -0
- package/dist/tmux-statusline.js +166 -0
- package/dist/tmux-statusline.js.map +1 -0
- package/dist/tool-output-watchers.d.ts +10 -0
- package/dist/tool-output-watchers.js +190 -0
- package/dist/tool-output-watchers.js.map +1 -0
- package/dist/tui/render/box.d.ts +1 -0
- package/dist/tui/render/box.js +20 -0
- package/dist/tui/render/box.js.map +1 -0
- package/dist/tui/render/text.d.ts +8 -0
- package/dist/tui/render/text.js +92 -0
- package/dist/tui/render/text.js.map +1 -0
- package/dist/tui/screens/dashboard-renderers.d.ts +23 -0
- package/dist/tui/screens/dashboard-renderers.js +411 -0
- package/dist/tui/screens/dashboard-renderers.js.map +1 -0
- package/dist/tui/screens/overlay-renderers.d.ts +10 -0
- package/dist/tui/screens/overlay-renderers.js +274 -0
- package/dist/tui/screens/overlay-renderers.js.map +1 -0
- package/dist/tui/screens/subscreen-renderers.d.ts +9 -0
- package/dist/tui/screens/subscreen-renderers.js +327 -0
- package/dist/tui/screens/subscreen-renderers.js.map +1 -0
- package/dist/workflow.d.ts +19 -0
- package/dist/workflow.js +111 -0
- package/dist/workflow.js.map +1 -0
- package/dist/worktree.d.ts +23 -0
- package/dist/worktree.js +101 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import * as lockfile from "proper-lockfile";
|
|
4
|
+
import { getInstancesPath, getAimuxDirFor } from "./paths.js";
|
|
5
|
+
import { findMainRepo } from "./worktree.js";
|
|
6
|
+
import { debug } from "./debug.js";
|
|
7
|
+
const HEARTBEAT_STALE_MS = 15_000;
|
|
8
|
+
const LOCK_RETRIES = { retries: 5, minTimeout: 50 };
|
|
9
|
+
/**
|
|
10
|
+
* Check if a PID is alive.
|
|
11
|
+
*/
|
|
12
|
+
function isPidAlive(pid) {
|
|
13
|
+
try {
|
|
14
|
+
process.kill(pid, 0);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Filter out dead instances (dead PID or stale heartbeat).
|
|
23
|
+
*/
|
|
24
|
+
function pruneDeadEntries(instances) {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
return instances.filter((inst) => {
|
|
27
|
+
if (!isPidAlive(inst.pid)) {
|
|
28
|
+
debug(`pruning dead instance ${inst.instanceId} (PID ${inst.pid} dead)`, "instance");
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const heartbeatAge = now - new Date(inst.heartbeat).getTime();
|
|
32
|
+
if (heartbeatAge > HEARTBEAT_STALE_MS) {
|
|
33
|
+
debug(`pruning stale instance ${inst.instanceId} (heartbeat ${Math.round(heartbeatAge / 1000)}s old)`, "instance");
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read instances.json, returning empty array if missing or corrupt.
|
|
41
|
+
*/
|
|
42
|
+
function readInstancesFile(filePath) {
|
|
43
|
+
if (!existsSync(filePath))
|
|
44
|
+
return [];
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ensure the directory for the instances file exists and the file is created.
|
|
54
|
+
*/
|
|
55
|
+
function ensureInstancesFile(filePath) {
|
|
56
|
+
const dir = filePath.replace(/\/[^/]+$/, "");
|
|
57
|
+
if (!existsSync(dir)) {
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
if (!existsSync(filePath)) {
|
|
61
|
+
writeFileSync(filePath, "[]");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Run an operation against instances.json with locking.
|
|
66
|
+
* Operates on both the global project state dir and the main repo (for cross-worktree discovery).
|
|
67
|
+
*/
|
|
68
|
+
async function withLockedInstances(cwd, fn) {
|
|
69
|
+
const paths = getInstancesPaths(cwd);
|
|
70
|
+
for (const filePath of paths) {
|
|
71
|
+
ensureInstancesFile(filePath);
|
|
72
|
+
let release;
|
|
73
|
+
try {
|
|
74
|
+
release = await lockfile.lock(filePath, { retries: LOCK_RETRIES });
|
|
75
|
+
const instances = readInstancesFile(filePath);
|
|
76
|
+
const updated = fn(instances, filePath);
|
|
77
|
+
writeFileSync(filePath, JSON.stringify(updated, null, 2) + "\n");
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (release)
|
|
81
|
+
await release();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all instances.json paths to update (global project state + main repo .aimux/ if different).
|
|
87
|
+
* The global path is primary; the in-repo path is for cross-worktree discovery by older instances.
|
|
88
|
+
*/
|
|
89
|
+
function getInstancesPaths(cwd) {
|
|
90
|
+
const globalPath = getInstancesPath();
|
|
91
|
+
const paths = [globalPath];
|
|
92
|
+
// Also write to in-repo .aimux/ for backward compat with older instances
|
|
93
|
+
try {
|
|
94
|
+
const mainRepo = findMainRepo(cwd);
|
|
95
|
+
const inRepoPath = join(getAimuxDirFor(mainRepo), "instances.json");
|
|
96
|
+
if (inRepoPath !== globalPath) {
|
|
97
|
+
paths.push(inRepoPath);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Not in a git repo or worktree detection failed — global only
|
|
102
|
+
}
|
|
103
|
+
return paths;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Register this instance in instances.json.
|
|
107
|
+
*/
|
|
108
|
+
export async function registerInstance(instanceId, cwd) {
|
|
109
|
+
let remoteInstances = [];
|
|
110
|
+
await withLockedInstances(cwd, (instances) => {
|
|
111
|
+
const pruned = pruneDeadEntries(instances);
|
|
112
|
+
const entry = {
|
|
113
|
+
instanceId,
|
|
114
|
+
pid: process.pid,
|
|
115
|
+
startedAt: new Date().toISOString(),
|
|
116
|
+
heartbeat: new Date().toISOString(),
|
|
117
|
+
cwd,
|
|
118
|
+
sessions: [],
|
|
119
|
+
};
|
|
120
|
+
const updated = [...pruned, entry];
|
|
121
|
+
remoteInstances = pruned.filter((i) => i.instanceId !== instanceId);
|
|
122
|
+
return updated;
|
|
123
|
+
});
|
|
124
|
+
if (remoteInstances.length > 0) {
|
|
125
|
+
debug(`registered instance ${instanceId}, found ${remoteInstances.length} other instance(s)`, "instance");
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
debug(`registered instance ${instanceId} (sole instance)`, "instance");
|
|
129
|
+
}
|
|
130
|
+
return remoteInstances;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Unregister this instance from instances.json.
|
|
134
|
+
*/
|
|
135
|
+
export async function unregisterInstance(instanceId, cwd) {
|
|
136
|
+
await withLockedInstances(cwd, (instances) => {
|
|
137
|
+
return instances.filter((i) => i.instanceId !== instanceId);
|
|
138
|
+
});
|
|
139
|
+
debug(`unregistered instance ${instanceId}`, "instance");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Update heartbeat timestamp and sessions list. Also prunes dead instances.
|
|
143
|
+
* Returns the session IDs that were in the registry BEFORE this update
|
|
144
|
+
* (so the caller can detect which were claimed by comparing against expectations).
|
|
145
|
+
*/
|
|
146
|
+
export async function updateHeartbeat(instanceId, sessions, cwd) {
|
|
147
|
+
// Collect previous session IDs across ALL instances.json files (global + in-repo).
|
|
148
|
+
// withLockedInstances calls the callback once per file, so we merge results.
|
|
149
|
+
const allPreviousIds = new Set();
|
|
150
|
+
await withLockedInstances(cwd, (instances) => {
|
|
151
|
+
// Prune dead instances, but never prune ourselves — we know we're alive
|
|
152
|
+
const pruned = pruneDeadEntries(instances.filter((i) => i.instanceId !== instanceId));
|
|
153
|
+
const self = instances.find((i) => i.instanceId === instanceId);
|
|
154
|
+
const result = pruned.map((inst) => inst);
|
|
155
|
+
if (self) {
|
|
156
|
+
for (const s of self.sessions)
|
|
157
|
+
allPreviousIds.add(s.id);
|
|
158
|
+
result.push({ ...self, heartbeat: new Date().toISOString(), sessions });
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
});
|
|
162
|
+
return [...allPreviousIds];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get instances belonging to other aimux processes.
|
|
166
|
+
* Reads from all known instances.json files for cross-worktree visibility.
|
|
167
|
+
*/
|
|
168
|
+
export function getRemoteInstances(ownInstanceId, cwd) {
|
|
169
|
+
const paths = getInstancesPaths(cwd);
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
const result = [];
|
|
172
|
+
for (const filePath of paths) {
|
|
173
|
+
const instances = readInstancesFile(filePath);
|
|
174
|
+
const alive = pruneDeadEntries(instances);
|
|
175
|
+
for (const inst of alive) {
|
|
176
|
+
if (inst.instanceId !== ownInstanceId && !seen.has(inst.instanceId)) {
|
|
177
|
+
seen.add(inst.instanceId);
|
|
178
|
+
result.push(inst);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Remove a session from another instance's entry (takeover step).
|
|
186
|
+
* Returns the claimed session ref, or undefined if not found.
|
|
187
|
+
*/
|
|
188
|
+
export async function claimSession(sessionId, fromInstanceId, cwd) {
|
|
189
|
+
let claimed;
|
|
190
|
+
await withLockedInstances(cwd, (instances) => {
|
|
191
|
+
return instances.map((inst) => {
|
|
192
|
+
if (inst.instanceId === fromInstanceId) {
|
|
193
|
+
const session = inst.sessions.find((s) => s.id === sessionId);
|
|
194
|
+
if (session) {
|
|
195
|
+
claimed = session;
|
|
196
|
+
debug(`claimed session ${sessionId} from instance ${fromInstanceId}`, "instance");
|
|
197
|
+
return {
|
|
198
|
+
...inst,
|
|
199
|
+
sessions: inst.sessions.filter((s) => s.id !== sessionId),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return inst;
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
return claimed;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=instance-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-registry.js","sourceRoot":"","sources":["../src/instance-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal CSI escape sequence parser.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes raw terminal key sequences into a clean KeyEvent format.
|
|
5
|
+
* Handles xterm modifyOtherKeys, kitty keyboard protocol, and standard CSI.
|
|
6
|
+
*
|
|
7
|
+
* This is the single choke point for all terminal key handling —
|
|
8
|
+
* swap this module to change the parsing strategy.
|
|
9
|
+
*/
|
|
10
|
+
export interface KeyEvent {
|
|
11
|
+
/** The printable character, or empty for special keys */
|
|
12
|
+
char: string;
|
|
13
|
+
/** Named key: "enter", "tab", "backspace", "escape", "up", "down", "left", "right", "home", "end", "delete", "pageup", "pagedown", "f1"-"f12", "focusin", "focusout" */
|
|
14
|
+
name: string;
|
|
15
|
+
shift: boolean;
|
|
16
|
+
ctrl: boolean;
|
|
17
|
+
alt: boolean;
|
|
18
|
+
/** The raw input string (for debugging) */
|
|
19
|
+
raw: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse raw stdin data into KeyEvents.
|
|
23
|
+
* Returns an array because a single stdin chunk can contain multiple key sequences.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseKeys(data: Buffer | string): KeyEvent[];
|
|
26
|
+
/**
|
|
27
|
+
* Check if a KeyEvent matches a key descriptor.
|
|
28
|
+
* Examples: "ctrl+o", "shift+enter", "ctrl+shift+left", "alt+b"
|
|
29
|
+
*/
|
|
30
|
+
export declare function matchKey(event: KeyEvent, descriptor: string): boolean;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal CSI escape sequence parser.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes raw terminal key sequences into a clean KeyEvent format.
|
|
5
|
+
* Handles xterm modifyOtherKeys, kitty keyboard protocol, and standard CSI.
|
|
6
|
+
*
|
|
7
|
+
* This is the single choke point for all terminal key handling —
|
|
8
|
+
* swap this module to change the parsing strategy.
|
|
9
|
+
*/
|
|
10
|
+
// Modifier bitmask (shared by xterm and standard CSI):
|
|
11
|
+
// 1=none, 2=shift, 3=alt, 4=shift+alt, 5=ctrl, 6=ctrl+shift, 7=ctrl+alt, 8=ctrl+shift+alt
|
|
12
|
+
function parseModifier(mod) {
|
|
13
|
+
const m = mod - 1; // convert to 0-based bitmask
|
|
14
|
+
return {
|
|
15
|
+
shift: !!(m & 1),
|
|
16
|
+
alt: !!(m & 2),
|
|
17
|
+
ctrl: !!(m & 4),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const KEYCODE_NAMES = {
|
|
21
|
+
9: "tab",
|
|
22
|
+
13: "enter",
|
|
23
|
+
27: "escape",
|
|
24
|
+
127: "backspace",
|
|
25
|
+
};
|
|
26
|
+
const CSI_FINAL_NAMES = {
|
|
27
|
+
A: "up",
|
|
28
|
+
B: "down",
|
|
29
|
+
C: "right",
|
|
30
|
+
D: "left",
|
|
31
|
+
H: "home",
|
|
32
|
+
F: "end",
|
|
33
|
+
};
|
|
34
|
+
const CSI_TILDE_NAMES = {
|
|
35
|
+
1: "home",
|
|
36
|
+
2: "insert",
|
|
37
|
+
3: "delete",
|
|
38
|
+
4: "end",
|
|
39
|
+
5: "pageup",
|
|
40
|
+
6: "pagedown",
|
|
41
|
+
11: "f1",
|
|
42
|
+
12: "f2",
|
|
43
|
+
13: "f3",
|
|
44
|
+
14: "f4",
|
|
45
|
+
15: "f5",
|
|
46
|
+
17: "f6",
|
|
47
|
+
18: "f7",
|
|
48
|
+
19: "f8",
|
|
49
|
+
20: "f9",
|
|
50
|
+
21: "f10",
|
|
51
|
+
23: "f11",
|
|
52
|
+
24: "f12",
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Parse raw stdin data into KeyEvents.
|
|
56
|
+
* Returns an array because a single stdin chunk can contain multiple key sequences.
|
|
57
|
+
*/
|
|
58
|
+
export function parseKeys(data) {
|
|
59
|
+
const str = typeof data === "string" ? data : data.toString("utf-8");
|
|
60
|
+
const events = [];
|
|
61
|
+
let i = 0;
|
|
62
|
+
while (i < str.length) {
|
|
63
|
+
// ESC sequence
|
|
64
|
+
if (str[i] === "\x1b") {
|
|
65
|
+
// Bracketed paste: ESC [200~ ... ESC [201~
|
|
66
|
+
// Terminals wrap pasted text in these markers
|
|
67
|
+
if (str.startsWith("\x1b[200~", i)) {
|
|
68
|
+
const pasteStart = i + 6; // skip ESC [200~
|
|
69
|
+
const pasteEnd = str.indexOf("\x1b[201~", pasteStart);
|
|
70
|
+
const content = pasteEnd >= 0 ? str.slice(pasteStart, pasteEnd) : str.slice(pasteStart); // no end marker yet — take everything
|
|
71
|
+
if (content) {
|
|
72
|
+
events.push({ char: content, name: "paste", shift: false, ctrl: false, alt: false, raw: content });
|
|
73
|
+
}
|
|
74
|
+
i = pasteEnd >= 0 ? pasteEnd + 6 : str.length;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// CSI: ESC [
|
|
78
|
+
if (i + 1 < str.length && str[i + 1] === "[") {
|
|
79
|
+
const parsed = parseCSI(str, i + 2);
|
|
80
|
+
if (parsed) {
|
|
81
|
+
parsed.event.raw = str.slice(i, i + 2 + parsed.consumed);
|
|
82
|
+
events.push(parsed.event);
|
|
83
|
+
i += 2 + parsed.consumed;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// SS3: ESC O (used by some terminals for arrow keys, function keys)
|
|
88
|
+
if (i + 1 < str.length && str[i + 1] === "O") {
|
|
89
|
+
if (i + 2 < str.length) {
|
|
90
|
+
const ch = str[i + 2];
|
|
91
|
+
const name = CSI_FINAL_NAMES[ch];
|
|
92
|
+
if (name) {
|
|
93
|
+
events.push({ char: "", name, shift: false, ctrl: false, alt: false, raw: str.slice(i, i + 3) });
|
|
94
|
+
i += 3;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// SS3 P-S = F1-F4
|
|
98
|
+
if (ch >= "P" && ch <= "S") {
|
|
99
|
+
const fNum = ch.charCodeAt(0) - "P".charCodeAt(0) + 1;
|
|
100
|
+
events.push({
|
|
101
|
+
char: "",
|
|
102
|
+
name: `f${fNum}`,
|
|
103
|
+
shift: false,
|
|
104
|
+
ctrl: false,
|
|
105
|
+
alt: false,
|
|
106
|
+
raw: str.slice(i, i + 3),
|
|
107
|
+
});
|
|
108
|
+
i += 3;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ESC + single char = Alt+char (meta key)
|
|
114
|
+
if (i + 1 < str.length && str[i + 1] !== "\x1b") {
|
|
115
|
+
const ch = str[i + 1];
|
|
116
|
+
const code = ch.charCodeAt(0);
|
|
117
|
+
// Map control characters to their names (e.g., 0x7F = backspace, 0x0D = enter)
|
|
118
|
+
const ctrlName = code === 127 || code === 8
|
|
119
|
+
? "backspace"
|
|
120
|
+
: code === 13
|
|
121
|
+
? "enter"
|
|
122
|
+
: code === 9
|
|
123
|
+
? "tab"
|
|
124
|
+
: code < 32
|
|
125
|
+
? String.fromCharCode(code + 96) // Ctrl+letter
|
|
126
|
+
: "";
|
|
127
|
+
events.push({
|
|
128
|
+
char: ctrlName ? "" : ch,
|
|
129
|
+
name: ctrlName || ch,
|
|
130
|
+
shift: false,
|
|
131
|
+
ctrl: code < 32 && code !== 13 && code !== 9 && code !== 8,
|
|
132
|
+
alt: true,
|
|
133
|
+
raw: str.slice(i, i + 2),
|
|
134
|
+
});
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Lone ESC
|
|
139
|
+
events.push({ char: "", name: "escape", shift: false, ctrl: false, alt: false, raw: "\x1b" });
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Control characters
|
|
144
|
+
const code = str.charCodeAt(i);
|
|
145
|
+
if (code < 32 || code === 127) {
|
|
146
|
+
if (code === 13) {
|
|
147
|
+
events.push({ char: "", name: "enter", shift: false, ctrl: false, alt: false, raw: str[i] });
|
|
148
|
+
}
|
|
149
|
+
else if (code === 9) {
|
|
150
|
+
events.push({ char: "", name: "tab", shift: false, ctrl: false, alt: false, raw: str[i] });
|
|
151
|
+
}
|
|
152
|
+
else if (code === 127 || code === 8) {
|
|
153
|
+
events.push({ char: "", name: "backspace", shift: false, ctrl: false, alt: false, raw: str[i] });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Ctrl+letter: Ctrl+A = 1, Ctrl+B = 2, ..., Ctrl+Z = 26
|
|
157
|
+
const letter = String.fromCharCode(code + 96); // 1 -> 'a', etc.
|
|
158
|
+
events.push({ char: "", name: letter, shift: false, ctrl: true, alt: false, raw: str[i] });
|
|
159
|
+
}
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Regular printable character(s) — batch consecutive printable chars
|
|
164
|
+
let end = i + 1;
|
|
165
|
+
while (end < str.length && str.charCodeAt(end) >= 32 && str[end] !== "\x1b") {
|
|
166
|
+
end++;
|
|
167
|
+
}
|
|
168
|
+
const chars = str.slice(i, end);
|
|
169
|
+
events.push({ char: chars, name: "", shift: false, ctrl: false, alt: false, raw: chars });
|
|
170
|
+
i = end;
|
|
171
|
+
}
|
|
172
|
+
return events;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parse CSI sequence parameters (after ESC [).
|
|
176
|
+
* Returns the parsed event and number of characters consumed after "ESC [".
|
|
177
|
+
*/
|
|
178
|
+
function parseCSI(str, start) {
|
|
179
|
+
// Collect parameter bytes (digits and semicolons) and the final byte
|
|
180
|
+
let i = start;
|
|
181
|
+
let params = "";
|
|
182
|
+
// Collect parameter bytes: 0x30-0x3F (digits, semicolons, etc.)
|
|
183
|
+
while (i < str.length && str.charCodeAt(i) >= 0x30 && str.charCodeAt(i) <= 0x3f) {
|
|
184
|
+
params += str[i];
|
|
185
|
+
i++;
|
|
186
|
+
}
|
|
187
|
+
// Final byte: 0x40-0x7E
|
|
188
|
+
if (i >= str.length)
|
|
189
|
+
return null;
|
|
190
|
+
const finalByte = str[i];
|
|
191
|
+
const finalCode = finalByte.charCodeAt(0);
|
|
192
|
+
if (finalCode < 0x40 || finalCode > 0x7e)
|
|
193
|
+
return null;
|
|
194
|
+
const consumed = i - start + 1;
|
|
195
|
+
const parts = params.split(";").map(Number);
|
|
196
|
+
// Kitty keyboard protocol: ESC [ keycode ; modifier u
|
|
197
|
+
if (finalByte === "u") {
|
|
198
|
+
const keycode = parts[0] ?? 0;
|
|
199
|
+
const mods = parseModifier(parts[1] ?? 1);
|
|
200
|
+
const name = KEYCODE_NAMES[keycode] ?? "";
|
|
201
|
+
const char = name ? "" : String.fromCharCode(keycode);
|
|
202
|
+
return {
|
|
203
|
+
event: { char, name: name || char, ...mods, raw: "" },
|
|
204
|
+
consumed,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
// xterm modifyOtherKeys: ESC [ 27 ; modifier ; keycode ~
|
|
208
|
+
if (finalByte === "~" && parts[0] === 27 && parts.length >= 3) {
|
|
209
|
+
const mods = parseModifier(parts[1] ?? 1);
|
|
210
|
+
const keycode = parts[2] ?? 0;
|
|
211
|
+
const name = KEYCODE_NAMES[keycode] ?? "";
|
|
212
|
+
const char = name ? "" : String.fromCharCode(keycode);
|
|
213
|
+
return {
|
|
214
|
+
event: { char, name: name || char, ...mods, raw: "" },
|
|
215
|
+
consumed,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// Standard CSI with tilde: ESC [ number ~ (with optional modifier)
|
|
219
|
+
if (finalByte === "~") {
|
|
220
|
+
const keyNum = parts[0] ?? 0;
|
|
221
|
+
const mods = parseModifier(parts[1] ?? 1);
|
|
222
|
+
const name = CSI_TILDE_NAMES[keyNum] ?? "";
|
|
223
|
+
if (name) {
|
|
224
|
+
return {
|
|
225
|
+
event: { char: "", name, ...mods, raw: "" },
|
|
226
|
+
consumed,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
// Standard CSI arrow/nav: ESC [ (modifier ;)? final
|
|
232
|
+
// e.g., ESC [ 1 ; 2 A = Shift+Up
|
|
233
|
+
if (finalByte === "I") {
|
|
234
|
+
return {
|
|
235
|
+
event: { char: "", name: "focusin", shift: false, ctrl: false, alt: false, raw: "" },
|
|
236
|
+
consumed,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (finalByte === "O") {
|
|
240
|
+
return {
|
|
241
|
+
event: { char: "", name: "focusout", shift: false, ctrl: false, alt: false, raw: "" },
|
|
242
|
+
consumed,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const name = CSI_FINAL_NAMES[finalByte];
|
|
246
|
+
if (name) {
|
|
247
|
+
// Modifier is in the last param if there are two parts
|
|
248
|
+
const mod = parts.length >= 2 ? (parts[1] ?? 1) : 1;
|
|
249
|
+
const mods = parseModifier(mod);
|
|
250
|
+
return {
|
|
251
|
+
event: { char: "", name, ...mods, raw: "" },
|
|
252
|
+
consumed,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Check if a KeyEvent matches a key descriptor.
|
|
259
|
+
* Examples: "ctrl+o", "shift+enter", "ctrl+shift+left", "alt+b"
|
|
260
|
+
*/
|
|
261
|
+
export function matchKey(event, descriptor) {
|
|
262
|
+
const parts = descriptor.toLowerCase().split("+");
|
|
263
|
+
const key = parts.pop();
|
|
264
|
+
const wantCtrl = parts.includes("ctrl");
|
|
265
|
+
const wantShift = parts.includes("shift");
|
|
266
|
+
const wantAlt = parts.includes("alt");
|
|
267
|
+
return (event.ctrl === wantCtrl &&
|
|
268
|
+
event.shift === wantShift &&
|
|
269
|
+
event.alt === wantAlt &&
|
|
270
|
+
(event.name === key || event.char === key));
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=key-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-parser.js","sourceRoot":"","sources":["../src/key-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface LastUsedEntry {
|
|
2
|
+
lastUsedAt: string;
|
|
3
|
+
}
|
|
4
|
+
export interface LastUsedClientState {
|
|
5
|
+
recentIds: string[];
|
|
6
|
+
updatedAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LastUsedState {
|
|
9
|
+
version: number;
|
|
10
|
+
items: Record<string, LastUsedEntry>;
|
|
11
|
+
clients: Record<string, LastUsedClientState>;
|
|
12
|
+
projectRecentIds: string[];
|
|
13
|
+
updatedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface MarkLastUsedOptions {
|
|
16
|
+
itemId: string;
|
|
17
|
+
clientSession?: string;
|
|
18
|
+
usedAt?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function getLastUsedPath(projectRoot: string): string;
|
|
21
|
+
export declare function loadLastUsedState(projectRoot: string): LastUsedState;
|
|
22
|
+
export declare function markLastUsed(projectRoot: string, options: MarkLastUsedOptions): LastUsedState;
|
|
23
|
+
export declare function getLastUsedAt(projectRoot: string, itemId: string): string | undefined;
|
|
24
|
+
export declare function getRecentRankMap(projectRoot: string, clientSession?: string): Map<string, number>;
|
|
25
|
+
export declare function compareLastUsed(left: {
|
|
26
|
+
id: string;
|
|
27
|
+
lastUsedAt?: string;
|
|
28
|
+
}, right: {
|
|
29
|
+
id: string;
|
|
30
|
+
lastUsedAt?: string;
|
|
31
|
+
}, rankMap: Map<string, number>): number;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { getProjectStateDirFor } from "./paths.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parseRecencyTimestamp } from "./recency.js";
|
|
5
|
+
const LAST_USED_VERSION = 1;
|
|
6
|
+
const MAX_RECENT_IDS = 64;
|
|
7
|
+
const EMPTY_STATE = {
|
|
8
|
+
version: LAST_USED_VERSION,
|
|
9
|
+
items: {},
|
|
10
|
+
clients: {},
|
|
11
|
+
projectRecentIds: [],
|
|
12
|
+
};
|
|
13
|
+
export function getLastUsedPath(projectRoot) {
|
|
14
|
+
return join(getProjectStateDirFor(projectRoot), "last-used.json");
|
|
15
|
+
}
|
|
16
|
+
export function loadLastUsedState(projectRoot) {
|
|
17
|
+
const path = getLastUsedPath(projectRoot);
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
return { ...EMPTY_STATE };
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
22
|
+
return normalizeLastUsedState(parsed);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { ...EMPTY_STATE };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function markLastUsed(projectRoot, options) {
|
|
29
|
+
const itemId = options.itemId?.trim();
|
|
30
|
+
if (!itemId)
|
|
31
|
+
return loadLastUsedState(projectRoot);
|
|
32
|
+
const usedAt = options.usedAt?.trim() || new Date().toISOString();
|
|
33
|
+
const state = loadLastUsedState(projectRoot);
|
|
34
|
+
state.items[itemId] = { lastUsedAt: usedAt };
|
|
35
|
+
state.projectRecentIds = pushRecentId(state.projectRecentIds, itemId);
|
|
36
|
+
const clientSession = options.clientSession?.trim();
|
|
37
|
+
if (clientSession) {
|
|
38
|
+
const existing = state.clients[clientSession] ?? { recentIds: [], updatedAt: usedAt };
|
|
39
|
+
state.clients[clientSession] = {
|
|
40
|
+
recentIds: pushRecentId(existing.recentIds, itemId),
|
|
41
|
+
updatedAt: usedAt,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
state.updatedAt = usedAt;
|
|
45
|
+
persistLastUsedState(projectRoot, state);
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
export function getLastUsedAt(projectRoot, itemId) {
|
|
49
|
+
return loadLastUsedState(projectRoot).items[itemId]?.lastUsedAt;
|
|
50
|
+
}
|
|
51
|
+
export function getRecentRankMap(projectRoot, clientSession) {
|
|
52
|
+
const state = loadLastUsedState(projectRoot);
|
|
53
|
+
const clientIds = clientSession ? (state.clients[clientSession]?.recentIds ?? []) : [];
|
|
54
|
+
const ordered = [...clientIds, ...state.projectRecentIds.filter((id) => !clientIds.includes(id))];
|
|
55
|
+
return new Map(ordered.map((id, index) => [id, index]));
|
|
56
|
+
}
|
|
57
|
+
export function compareLastUsed(left, right, rankMap) {
|
|
58
|
+
const leftRank = rankMap.get(left.id) ?? Number.MAX_SAFE_INTEGER;
|
|
59
|
+
const rightRank = rankMap.get(right.id) ?? Number.MAX_SAFE_INTEGER;
|
|
60
|
+
if (leftRank !== rightRank)
|
|
61
|
+
return leftRank - rightRank;
|
|
62
|
+
const leftUsedAt = parseRecencyTimestamp(left.lastUsedAt) ?? 0;
|
|
63
|
+
const rightUsedAt = parseRecencyTimestamp(right.lastUsedAt) ?? 0;
|
|
64
|
+
return rightUsedAt - leftUsedAt;
|
|
65
|
+
}
|
|
66
|
+
function persistLastUsedState(projectRoot, state) {
|
|
67
|
+
const dir = getProjectStateDirFor(projectRoot);
|
|
68
|
+
mkdirSync(dir, { recursive: true });
|
|
69
|
+
writeFileSync(getLastUsedPath(projectRoot), JSON.stringify(state, null, 2));
|
|
70
|
+
}
|
|
71
|
+
function normalizeLastUsedState(state) {
|
|
72
|
+
const items = Object.fromEntries(Object.entries(state.items ?? {}).flatMap(([itemId, value]) => typeof value?.lastUsedAt === "string" ? [[itemId, { lastUsedAt: value.lastUsedAt }]] : []));
|
|
73
|
+
const clients = Object.fromEntries(Object.entries(state.clients ?? {}).map(([clientSession, value]) => [
|
|
74
|
+
clientSession,
|
|
75
|
+
{
|
|
76
|
+
recentIds: Array.isArray(value?.recentIds) ? value.recentIds.filter(Boolean).slice(0, MAX_RECENT_IDS) : [],
|
|
77
|
+
updatedAt: typeof value?.updatedAt === "string" ? value.updatedAt : "",
|
|
78
|
+
},
|
|
79
|
+
]));
|
|
80
|
+
return {
|
|
81
|
+
version: LAST_USED_VERSION,
|
|
82
|
+
items,
|
|
83
|
+
clients,
|
|
84
|
+
projectRecentIds: Array.isArray(state.projectRecentIds)
|
|
85
|
+
? state.projectRecentIds.filter(Boolean).slice(0, MAX_RECENT_IDS)
|
|
86
|
+
: [],
|
|
87
|
+
updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function pushRecentId(ids, itemId) {
|
|
91
|
+
return [itemId, ...ids.filter((entry) => entry !== itemId)].slice(0, MAX_RECENT_IDS);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=last-used.js.map
|