tmux-ide 2.7.0 → 2.8.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 +22 -5
- package/bin/cli.js +3532 -1090
- package/bin/cli.ts +368 -71
- package/package.json +2 -1
- package/packages/contracts/src/__tests__/control.test.ts +154 -0
- package/packages/contracts/src/control.ts +217 -0
- package/packages/contracts/src/index.ts +1 -0
- package/packages/daemon/dist/control/client.d.ts +23 -0
- package/packages/daemon/dist/control/client.js +105 -0
- package/packages/daemon/dist/control/dispatch.d.ts +34 -0
- package/packages/daemon/dist/control/dispatch.js +83 -0
- package/packages/daemon/dist/control/fanout.d.ts +19 -0
- package/packages/daemon/dist/control/fanout.js +37 -0
- package/packages/daemon/dist/control/frames.d.ts +23 -0
- package/packages/daemon/dist/control/frames.js +37 -0
- package/packages/daemon/dist/control/lifecycle.d.ts +45 -0
- package/packages/daemon/dist/control/lifecycle.js +114 -0
- package/packages/daemon/dist/control/server.d.ts +16 -0
- package/packages/daemon/dist/control/server.js +214 -0
- package/packages/daemon/dist/control/verbs.d.ts +11 -0
- package/packages/daemon/dist/control/verbs.js +91 -0
- package/packages/daemon/dist/doctor.d.ts +18 -0
- package/packages/daemon/dist/doctor.js +105 -15
- package/packages/daemon/dist/lib/agent-discovery.d.ts +27 -2
- package/packages/daemon/dist/lib/agent-discovery.js +29 -14
- package/packages/daemon/dist/lib/app-config.d.ts +106 -0
- package/packages/daemon/dist/lib/app-config.js +104 -5
- package/packages/daemon/dist/lib/manifest-pack.d.ts +79 -0
- package/packages/daemon/dist/lib/manifest-pack.js +232 -0
- package/packages/daemon/dist/lib/state-home.d.ts +2 -0
- package/packages/daemon/dist/lib/state-home.js +12 -0
- package/packages/daemon/dist/lib/update-check.js +5 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +34 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/PkgInfo +1 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/AppIcon.icns +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +139 -0
- package/packages/daemon/dist/restore.d.ts +35 -8
- package/packages/daemon/dist/restore.js +52 -15
- package/packages/daemon/dist/send.d.ts +33 -1
- package/packages/daemon/dist/send.js +32 -19
- package/packages/daemon/src/control/client.ts +128 -0
- package/packages/daemon/src/control/dispatch.ts +107 -0
- package/packages/daemon/src/control/fanout.ts +44 -0
- package/packages/daemon/src/control/frames.ts +40 -0
- package/packages/daemon/src/control/lifecycle.ts +151 -0
- package/packages/daemon/src/control/server.ts +237 -0
- package/packages/daemon/src/control/verbs.ts +118 -0
- package/packages/daemon/src/doctor.ts +113 -28
- package/packages/daemon/src/lib/agent-discovery.ts +53 -13
- package/packages/daemon/src/lib/app-config.ts +103 -5
- package/packages/daemon/src/lib/manifest-pack.ts +255 -0
- package/packages/daemon/src/lib/state-home.ts +13 -0
- package/packages/daemon/src/lib/update-check.ts +5 -0
- package/packages/daemon/src/restore.ts +53 -15
- package/packages/daemon/src/send.ts +55 -21
- package/packages/daemon/src/tui/chrome/events.ts +4 -4
- package/packages/daemon/src/tui/chrome/front-door.ts +39 -0
- package/packages/daemon/src/tui/chrome/notify-prefs.ts +58 -0
- package/packages/daemon/src/tui/chrome/notify-state.ts +76 -0
- package/packages/daemon/src/tui/chrome/notify.ts +582 -84
- package/packages/daemon/src/tui/chrome/updater.ts +268 -62
- package/packages/daemon/src/tui/detect/classify.ts +34 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +54 -5
- package/packages/daemon/src/tui/detect/manifest.ts +24 -3
- package/packages/daemon/src/tui/detect/manifests.ts +240 -6
- package/packages/daemon/src/tui/detect/process-tree.ts +13 -3
- package/packages/daemon/src/tui/detect/session-id.ts +503 -0
- package/packages/daemon/src/tui/integrations/opencode.ts +121 -0
- package/packages/daemon/src/tui/mirror/agent-chip.ts +40 -11
- package/packages/daemon/src/tui/mirror/agent-lifecycle.ts +437 -0
- package/packages/daemon/src/tui/mirror/agent-rows.ts +27 -5
- package/packages/daemon/src/tui/mirror/app-state.ts +171 -8
- package/packages/daemon/src/tui/mirror/app.tsx +2182 -399
- package/packages/daemon/src/tui/mirror/attention.ts +110 -0
- package/packages/daemon/src/tui/mirror/dialog-stack.ts +17 -4
- package/packages/daemon/src/tui/mirror/diff-model.ts +279 -4
- package/packages/daemon/src/tui/mirror/file-tree.ts +231 -6
- package/packages/daemon/src/tui/mirror/host-terminal.ts +49 -0
- package/packages/daemon/src/tui/mirror/hosted.ts +205 -0
- package/packages/daemon/src/tui/mirror/layout-parse.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +27 -4
- package/packages/daemon/src/tui/mirror/palette.ts +299 -9
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +82 -4
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +18 -11
- package/packages/daemon/src/tui/mirror/perf-tap.ts +29 -3
- package/packages/daemon/src/tui/mirror/selection.ts +122 -8
- package/packages/daemon/src/tui/mirror/session-mirror.ts +349 -68
- package/packages/daemon/src/tui/mirror/settings-model.ts +96 -16
- package/packages/daemon/src/tui/mirror/sidebar.tsx +218 -0
- package/packages/daemon/src/tui/mirror/size-truth.ts +53 -0
- package/packages/daemon/src/tui/mirror/theme.ts +45 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +20 -0
- package/packages/daemon/src/tui/team/sessions.ts +85 -7
- package/packages/daemon/src/tui/team/wait.ts +144 -0
- package/scripts/build-macos-notifier.mjs +160 -0
- package/scripts/postinstall.js +8 -1
- package/scripts/prepublish-check.mjs +37 -1
- package/scripts/publish-tap.sh +55 -0
- package/skill/SKILL.md +88 -2
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* file, and appends.
|
|
14
14
|
*/
|
|
15
15
|
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
16
|
import { join } from "node:path";
|
|
17
|
+
import { stateHome } from "../../lib/state-home.ts";
|
|
18
18
|
import type { AgentStatus } from "../detect/classify.ts";
|
|
19
19
|
|
|
20
20
|
/** A single session-level status transition. `from` is null the first time a session is seen. */
|
|
@@ -86,9 +86,9 @@ export function formatEventLine(
|
|
|
86
86
|
return `${isoTime(ev.ts)} ${ev.session} ${from} → ${paint(ev.to, ev.to)}`;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/** Absolute path to the fleet event log. */
|
|
89
|
+
/** Absolute path to the fleet event log (under the `TMUX_IDE_HOME`-aware home). */
|
|
90
90
|
export function eventsPath(): string {
|
|
91
|
-
return join(
|
|
91
|
+
return join(stateHome(), "events.jsonl");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
@@ -106,7 +106,7 @@ export function appendEvents(
|
|
|
106
106
|
if (events.length === 0) return;
|
|
107
107
|
const path = eventsPath();
|
|
108
108
|
try {
|
|
109
|
-
mkdirSync(
|
|
109
|
+
mkdirSync(stateHome(), { recursive: true });
|
|
110
110
|
if (existsSync(path) && shouldRotate(statSync(path).size)) {
|
|
111
111
|
renameSync(path, `${path}.1`);
|
|
112
112
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chrome front door (M25.1) — the tiny, LEAF constants + argv builders the
|
|
3
|
+
* unified app needs to get a session watched without importing the chrome/data
|
|
4
|
+
* graph (app.tsx deliberately never imports the sync fleet-scan modules that
|
|
5
|
+
* {@link ./updater.ts} pulls in; see the FleetSession note there).
|
|
6
|
+
*
|
|
7
|
+
* "Watched" means: stamped with {@link ADOPTED_OPTION} so the background
|
|
8
|
+
* updater enumerates it (VERIFIED inert for a session that never ran `adopt` —
|
|
9
|
+
* none of adopt's status-row/border options are set, so the updater's status/
|
|
10
|
+
* chip var writes land on options nothing reads), and the updater itself
|
|
11
|
+
* ensured up. The sync twin of that flow lives in `updater.ts`
|
|
12
|
+
* (`startUpdaterIfNeeded`); the app uses these argvs with async execFile (the
|
|
13
|
+
* render-loop law).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Per-session marker option set on adopt so the updater can enumerate adopted
|
|
17
|
+
* sessions. (Owned here; re-exported by `updater.ts` for its callers.) */
|
|
18
|
+
export const ADOPTED_OPTION = "@tmux_ide_adopted";
|
|
19
|
+
|
|
20
|
+
/** The hidden internal session that hosts the updater loop. */
|
|
21
|
+
export const UPDATER_SESSION = "_tmux-ide-chrome";
|
|
22
|
+
|
|
23
|
+
/** PURE — stamp `session` watched (see the header: inert re: chrome painting). */
|
|
24
|
+
export function adoptMarkArgv(session: string): string[] {
|
|
25
|
+
return ["set-option", "-t", session, ADOPTED_OPTION, "1"];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** PURE — the existence probe for the updater session (`=` exact-match, so a
|
|
29
|
+
* user session that merely starts with the name can't shadow it). Exit 0 =
|
|
30
|
+
* running. */
|
|
31
|
+
export function updaterProbeArgv(): string[] {
|
|
32
|
+
return ["has-session", "-t", `=${UPDATER_SESSION}`];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** PURE — the argv that spawns the updater loop, detached. `exec` replaces the
|
|
36
|
+
* shell so the pane IS the loop; killing the session stops it. */
|
|
37
|
+
export function updaterSpawnArgv(): string[] {
|
|
38
|
+
return ["new-session", "-d", "-s", UPDATER_SESSION, "exec tmux-ide chrome-updater"];
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification PREFERENCES — the pure shapes and parsing, hoisted out of
|
|
3
|
+
* {@link ./notify.ts} (which is io: node:child_process, node:fs, tmux).
|
|
4
|
+
*
|
|
5
|
+
* Why the split: `settings-model.ts` is pure and only needs `NotificationPrefs`
|
|
6
|
+
* + `parseHHMM`, but importing them from notify.ts dragged the whole io graph in
|
|
7
|
+
* behind them. That's invisible in the daemon (node has `process`), and it broke
|
|
8
|
+
* the moment a pure surface was imported anywhere else — the web host loads
|
|
9
|
+
* these same modules in a browser, where `process` does not exist.
|
|
10
|
+
*
|
|
11
|
+
* The rule this restores is the repo's own: pure core, thin io. Anything a pure
|
|
12
|
+
* module needs belongs in a pure module.
|
|
13
|
+
*
|
|
14
|
+
* notify.ts re-exports these, so existing importers are untouched.
|
|
15
|
+
*/
|
|
16
|
+
import type { NotificationSound } from "../../lib/app-config.ts";
|
|
17
|
+
|
|
18
|
+
/** A local-time window, `HH:MM`–`HH:MM`. */
|
|
19
|
+
export interface QuietHours {
|
|
20
|
+
/** Local `HH:MM` the window opens (e.g. `22:00`). */
|
|
21
|
+
start: string;
|
|
22
|
+
/** Local `HH:MM` the window closes (e.g. `08:00`). */
|
|
23
|
+
end: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface NotificationPrefs {
|
|
27
|
+
/** Master switch — false silences every channel. */
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
/** In-terminal status-line toasts. */
|
|
30
|
+
toast: boolean;
|
|
31
|
+
/** System banners (native macOS helper, Linux `notify-send`). */
|
|
32
|
+
macos: boolean;
|
|
33
|
+
/** Terminal-native banners — OSC 9/99 escapes to eligible client ttys (M25.2). */
|
|
34
|
+
terminal: boolean;
|
|
35
|
+
/** Seconds the OS-level channels wait + re-verify before firing (0 = immediate). */
|
|
36
|
+
delaySeconds: number;
|
|
37
|
+
/** Sound channel — ping sound + BEL routing (M25.2). */
|
|
38
|
+
sound: NotificationSound;
|
|
39
|
+
/** Ping when an agent goes `blocked`. */
|
|
40
|
+
onBlocked: boolean;
|
|
41
|
+
/** Ping when an agent goes `done`. */
|
|
42
|
+
onDone: boolean;
|
|
43
|
+
/** Optional local-time window that suppresses every OS-LEVEL channel (banner,
|
|
44
|
+
* terminal escape, sound, BEL — since M25.2). In-app toasts and the event
|
|
45
|
+
* log stay on. */
|
|
46
|
+
quietHours: QuietHours | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** PURE — parse `HH:MM` to minutes-since-midnight, or null when malformed / out of range. */
|
|
50
|
+
export function parseHHMM(value: unknown): number | null {
|
|
51
|
+
if (typeof value !== "string") return null;
|
|
52
|
+
const m = /^(\d{2}):(\d{2})$/.exec(value.trim());
|
|
53
|
+
if (!m) return null;
|
|
54
|
+
const hours = Number(m[1]);
|
|
55
|
+
const minutes = Number(m[2]);
|
|
56
|
+
if (hours > 23 || minutes > 59) return null;
|
|
57
|
+
return hours * 60 + minutes;
|
|
58
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence for the notification debounce map (M25.1) — `lastNotified`
|
|
3
|
+
* survives updater restarts, so a respawned updater can't re-ping inside the
|
|
4
|
+
* debounce window (the old in-memory map made every restart an amnesty).
|
|
5
|
+
*
|
|
6
|
+
* Same split and same home discipline as {@link ../../lib/update-check.ts}:
|
|
7
|
+
* {@link serializeLastNotified} + {@link parseLastNotified} are PURE (and prune
|
|
8
|
+
* entries older than the debounce window — a stamp that can no longer suppress
|
|
9
|
+
* anything is dead weight); {@link loadLastNotified} / {@link saveLastNotified}
|
|
10
|
+
* are the thin, never-throwing io wrappers over
|
|
11
|
+
* `<TMUX_IDE_HOME>/notify-state.json`.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { stateHome } from "../../lib/state-home.ts";
|
|
16
|
+
import { NOTIFY_DEBOUNCE_MS } from "./notify.ts";
|
|
17
|
+
|
|
18
|
+
/** Absolute path to the persisted debounce map. */
|
|
19
|
+
export function notifyStatePath(): string {
|
|
20
|
+
return join(stateHome(), "notify-state.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** PURE — the JSON body for a debounce map, dropping entries whose timestamp
|
|
24
|
+
* is already outside the debounce window (they can't suppress anything). */
|
|
25
|
+
export function serializeLastNotified(map: ReadonlyMap<string, number>, nowMs: number): string {
|
|
26
|
+
const lastNotified: Record<string, number> = {};
|
|
27
|
+
for (const [key, ts] of map) {
|
|
28
|
+
if (nowMs - ts < NOTIFY_DEBOUNCE_MS) lastNotified[key] = ts;
|
|
29
|
+
}
|
|
30
|
+
return JSON.stringify({ lastNotified });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** PURE — parse a persisted body back into the map, pruning stale entries.
|
|
34
|
+
* Malformed input (or a FUTURE timestamp beyond the window — clock skew)
|
|
35
|
+
* yields an empty/partial map, never a throw. */
|
|
36
|
+
export function parseLastNotified(json: string, nowMs: number): Map<string, number> {
|
|
37
|
+
const out = new Map<string, number>();
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(json) as { lastNotified?: unknown };
|
|
40
|
+
const raw = parsed?.lastNotified;
|
|
41
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return out;
|
|
42
|
+
for (const [key, ts] of Object.entries(raw as Record<string, unknown>)) {
|
|
43
|
+
if (typeof ts !== "number") continue;
|
|
44
|
+
if (nowMs - ts >= NOTIFY_DEBOUNCE_MS) continue; // expired
|
|
45
|
+
if (ts - nowMs > NOTIFY_DEBOUNCE_MS) continue; // absurd future stamp
|
|
46
|
+
out.set(key, ts);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// unreadable state just means a fresh debounce map
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** io — load the persisted map (empty when missing/unreadable). Never throws. */
|
|
55
|
+
export function loadLastNotified(nowMs: number = Date.now()): Map<string, number> {
|
|
56
|
+
const path = notifyStatePath();
|
|
57
|
+
if (!existsSync(path)) return new Map();
|
|
58
|
+
try {
|
|
59
|
+
return parseLastNotified(readFileSync(path, "utf-8"), nowMs);
|
|
60
|
+
} catch {
|
|
61
|
+
return new Map();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** io — persist the map (creating the home if needed). Best-effort. */
|
|
66
|
+
export function saveLastNotified(
|
|
67
|
+
map: ReadonlyMap<string, number>,
|
|
68
|
+
nowMs: number = Date.now(),
|
|
69
|
+
): void {
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(stateHome(), { recursive: true });
|
|
72
|
+
writeFileSync(notifyStatePath(), serializeLastNotified(map, nowMs));
|
|
73
|
+
} catch {
|
|
74
|
+
// an unwritable map just means a restart re-arms the debounce — never fatal
|
|
75
|
+
}
|
|
76
|
+
}
|