@gigaflow/gmux 0.15.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.
Files changed (229) hide show
  1. package/CHANGELOG.md +654 -0
  2. package/LICENSE +21 -0
  3. package/README.md +351 -0
  4. package/dist/adapters/claude-code.d.ts +52 -0
  5. package/dist/adapters/claude-code.js +315 -0
  6. package/dist/adapters/claude-code.js.map +1 -0
  7. package/dist/adapters/codex.d.ts +39 -0
  8. package/dist/adapters/codex.js +233 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/jsonl.d.ts +40 -0
  11. package/dist/adapters/jsonl.js +92 -0
  12. package/dist/adapters/jsonl.js.map +1 -0
  13. package/dist/adapters/registry.d.ts +11 -0
  14. package/dist/adapters/registry.js +21 -0
  15. package/dist/adapters/registry.js.map +1 -0
  16. package/dist/adapters/types.d.ts +43 -0
  17. package/dist/adapters/types.js +2 -0
  18. package/dist/adapters/types.js.map +1 -0
  19. package/dist/cli/border-client.d.ts +8 -0
  20. package/dist/cli/border-client.js +11 -0
  21. package/dist/cli/border-client.js.map +1 -0
  22. package/dist/cli/commands/__ask-cancel.d.ts +35 -0
  23. package/dist/cli/commands/__ask-cancel.js +91 -0
  24. package/dist/cli/commands/__ask-cancel.js.map +1 -0
  25. package/dist/cli/commands/__ask-refresh.d.ts +40 -0
  26. package/dist/cli/commands/__ask-refresh.js +54 -0
  27. package/dist/cli/commands/__ask-refresh.js.map +1 -0
  28. package/dist/cli/commands/__ask-run.d.ts +43 -0
  29. package/dist/cli/commands/__ask-run.js +126 -0
  30. package/dist/cli/commands/__ask-run.js.map +1 -0
  31. package/dist/cli/commands/__ask-send.d.ts +50 -0
  32. package/dist/cli/commands/__ask-send.js +138 -0
  33. package/dist/cli/commands/__ask-send.js.map +1 -0
  34. package/dist/cli/commands/__preview-card.d.ts +31 -0
  35. package/dist/cli/commands/__preview-card.js +72 -0
  36. package/dist/cli/commands/__preview-card.js.map +1 -0
  37. package/dist/cli/commands/ask.d.ts +50 -0
  38. package/dist/cli/commands/ask.js +240 -0
  39. package/dist/cli/commands/ask.js.map +1 -0
  40. package/dist/cli/commands/auto.d.ts +12 -0
  41. package/dist/cli/commands/auto.js +28 -0
  42. package/dist/cli/commands/auto.js.map +1 -0
  43. package/dist/cli/commands/cockpit.d.ts +14 -0
  44. package/dist/cli/commands/cockpit.js +52 -0
  45. package/dist/cli/commands/cockpit.js.map +1 -0
  46. package/dist/cli/commands/daemon.d.ts +75 -0
  47. package/dist/cli/commands/daemon.js +258 -0
  48. package/dist/cli/commands/daemon.js.map +1 -0
  49. package/dist/cli/commands/doctor.d.ts +6 -0
  50. package/dist/cli/commands/doctor.js +159 -0
  51. package/dist/cli/commands/doctor.js.map +1 -0
  52. package/dist/cli/commands/grep.d.ts +2 -0
  53. package/dist/cli/commands/grep.js +52 -0
  54. package/dist/cli/commands/grep.js.map +1 -0
  55. package/dist/cli/commands/index-cmd.d.ts +2 -0
  56. package/dist/cli/commands/index-cmd.js +29 -0
  57. package/dist/cli/commands/index-cmd.js.map +1 -0
  58. package/dist/cli/commands/ls.d.ts +30 -0
  59. package/dist/cli/commands/ls.js +95 -0
  60. package/dist/cli/commands/ls.js.map +1 -0
  61. package/dist/cli/commands/overlay.d.ts +26 -0
  62. package/dist/cli/commands/overlay.js +216 -0
  63. package/dist/cli/commands/overlay.js.map +1 -0
  64. package/dist/cli/commands/pick.d.ts +59 -0
  65. package/dist/cli/commands/pick.js +254 -0
  66. package/dist/cli/commands/pick.js.map +1 -0
  67. package/dist/cli/commands/picker-rows.d.ts +17 -0
  68. package/dist/cli/commands/picker-rows.js +47 -0
  69. package/dist/cli/commands/picker-rows.js.map +1 -0
  70. package/dist/cli/commands/resume.d.ts +25 -0
  71. package/dist/cli/commands/resume.js +88 -0
  72. package/dist/cli/commands/resume.js.map +1 -0
  73. package/dist/cli/commands/run.d.ts +13 -0
  74. package/dist/cli/commands/run.js +78 -0
  75. package/dist/cli/commands/run.js.map +1 -0
  76. package/dist/cli/commands/setup.d.ts +37 -0
  77. package/dist/cli/commands/setup.js +200 -0
  78. package/dist/cli/commands/setup.js.map +1 -0
  79. package/dist/cli/commands/show.d.ts +2 -0
  80. package/dist/cli/commands/show.js +24 -0
  81. package/dist/cli/commands/show.js.map +1 -0
  82. package/dist/cli/commands/summarize.d.ts +2 -0
  83. package/dist/cli/commands/summarize.js +59 -0
  84. package/dist/cli/commands/summarize.js.map +1 -0
  85. package/dist/cli/commands/tmux.d.ts +11 -0
  86. package/dist/cli/commands/tmux.js +90 -0
  87. package/dist/cli/commands/tmux.js.map +1 -0
  88. package/dist/cli/commands/watch.d.ts +2 -0
  89. package/dist/cli/commands/watch.js +76 -0
  90. package/dist/cli/commands/watch.js.map +1 -0
  91. package/dist/cli/format.d.ts +74 -0
  92. package/dist/cli/format.js +242 -0
  93. package/dist/cli/format.js.map +1 -0
  94. package/dist/cli/gmux-render.d.ts +11 -0
  95. package/dist/cli/gmux-render.js +60 -0
  96. package/dist/cli/gmux-render.js.map +1 -0
  97. package/dist/cli/main.d.ts +8 -0
  98. package/dist/cli/main.js +145 -0
  99. package/dist/cli/main.js.map +1 -0
  100. package/dist/cli/overlay-ask.d.ts +13 -0
  101. package/dist/cli/overlay-ask.js +32 -0
  102. package/dist/cli/overlay-ask.js.map +1 -0
  103. package/dist/cli/overlay.d.ts +26 -0
  104. package/dist/cli/overlay.js +149 -0
  105. package/dist/cli/overlay.js.map +1 -0
  106. package/dist/cli/picker.d.ts +308 -0
  107. package/dist/cli/picker.js +642 -0
  108. package/dist/cli/picker.js.map +1 -0
  109. package/dist/cli/preview.d.ts +87 -0
  110. package/dist/cli/preview.js +292 -0
  111. package/dist/cli/preview.js.map +1 -0
  112. package/dist/cli/tmux-label.d.ts +50 -0
  113. package/dist/cli/tmux-label.js +146 -0
  114. package/dist/cli/tmux-label.js.map +1 -0
  115. package/dist/core/errors.d.ts +54 -0
  116. package/dist/core/errors.js +91 -0
  117. package/dist/core/errors.js.map +1 -0
  118. package/dist/core/fingerprint.d.ts +21 -0
  119. package/dist/core/fingerprint.js +59 -0
  120. package/dist/core/fingerprint.js.map +1 -0
  121. package/dist/core/gmux-types.d.ts +102 -0
  122. package/dist/core/gmux-types.js +13 -0
  123. package/dist/core/gmux-types.js.map +1 -0
  124. package/dist/core/pane-state.d.ts +5 -0
  125. package/dist/core/pane-state.js +15 -0
  126. package/dist/core/pane-state.js.map +1 -0
  127. package/dist/core/paths.d.ts +68 -0
  128. package/dist/core/paths.js +106 -0
  129. package/dist/core/paths.js.map +1 -0
  130. package/dist/core/proc-tree.d.ts +9 -0
  131. package/dist/core/proc-tree.js +33 -0
  132. package/dist/core/proc-tree.js.map +1 -0
  133. package/dist/core/text.d.ts +34 -0
  134. package/dist/core/text.js +126 -0
  135. package/dist/core/text.js.map +1 -0
  136. package/dist/core/types.d.ts +322 -0
  137. package/dist/core/types.js +10 -0
  138. package/dist/core/types.js.map +1 -0
  139. package/dist/services/ask-transcript.d.ts +256 -0
  140. package/dist/services/ask-transcript.js +544 -0
  141. package/dist/services/ask-transcript.js.map +1 -0
  142. package/dist/services/ask.d.ts +54 -0
  143. package/dist/services/ask.js +161 -0
  144. package/dist/services/ask.js.map +1 -0
  145. package/dist/services/auto-summarize.d.ts +188 -0
  146. package/dist/services/auto-summarize.js +415 -0
  147. package/dist/services/auto-summarize.js.map +1 -0
  148. package/dist/services/concurrency.d.ts +2 -0
  149. package/dist/services/concurrency.js +16 -0
  150. package/dist/services/concurrency.js.map +1 -0
  151. package/dist/services/config.d.ts +131 -0
  152. package/dist/services/config.js +325 -0
  153. package/dist/services/config.js.map +1 -0
  154. package/dist/services/daemon-client.d.ts +9 -0
  155. package/dist/services/daemon-client.js +34 -0
  156. package/dist/services/daemon-client.js.map +1 -0
  157. package/dist/services/daemon-socket.d.ts +30 -0
  158. package/dist/services/daemon-socket.js +76 -0
  159. package/dist/services/daemon-socket.js.map +1 -0
  160. package/dist/services/daemon.d.ts +43 -0
  161. package/dist/services/daemon.js +106 -0
  162. package/dist/services/daemon.js.map +1 -0
  163. package/dist/services/distill.d.ts +39 -0
  164. package/dist/services/distill.js +93 -0
  165. package/dist/services/distill.js.map +1 -0
  166. package/dist/services/guardian.d.ts +26 -0
  167. package/dist/services/guardian.js +71 -0
  168. package/dist/services/guardian.js.map +1 -0
  169. package/dist/services/index-store.d.ts +36 -0
  170. package/dist/services/index-store.js +125 -0
  171. package/dist/services/index-store.js.map +1 -0
  172. package/dist/services/log-rotation.d.ts +3 -0
  173. package/dist/services/log-rotation.js +28 -0
  174. package/dist/services/log-rotation.js.map +1 -0
  175. package/dist/services/pane-links.d.ts +10 -0
  176. package/dist/services/pane-links.js +47 -0
  177. package/dist/services/pane-links.js.map +1 -0
  178. package/dist/services/pane-process.d.ts +95 -0
  179. package/dist/services/pane-process.js +183 -0
  180. package/dist/services/pane-process.js.map +1 -0
  181. package/dist/services/pane-registry.d.ts +24 -0
  182. package/dist/services/pane-registry.js +40 -0
  183. package/dist/services/pane-registry.js.map +1 -0
  184. package/dist/services/provider-process.d.ts +37 -0
  185. package/dist/services/provider-process.js +105 -0
  186. package/dist/services/provider-process.js.map +1 -0
  187. package/dist/services/providers.d.ts +76 -0
  188. package/dist/services/providers.js +104 -0
  189. package/dist/services/providers.js.map +1 -0
  190. package/dist/services/resolve.d.ts +3 -0
  191. package/dist/services/resolve.js +18 -0
  192. package/dist/services/resolve.js.map +1 -0
  193. package/dist/services/resources.d.ts +17 -0
  194. package/dist/services/resources.js +113 -0
  195. package/dist/services/resources.js.map +1 -0
  196. package/dist/services/search.d.ts +29 -0
  197. package/dist/services/search.js +135 -0
  198. package/dist/services/search.js.map +1 -0
  199. package/dist/services/semantic-gate.d.ts +13 -0
  200. package/dist/services/semantic-gate.js +23 -0
  201. package/dist/services/semantic-gate.js.map +1 -0
  202. package/dist/services/semantic.d.ts +113 -0
  203. package/dist/services/semantic.js +216 -0
  204. package/dist/services/semantic.js.map +1 -0
  205. package/dist/services/sensors.d.ts +92 -0
  206. package/dist/services/sensors.js +246 -0
  207. package/dist/services/sensors.js.map +1 -0
  208. package/dist/services/summarize.d.ts +84 -0
  209. package/dist/services/summarize.js +230 -0
  210. package/dist/services/summarize.js.map +1 -0
  211. package/dist/services/tmux-gateway.d.ts +17 -0
  212. package/dist/services/tmux-gateway.js +10 -0
  213. package/dist/services/tmux-gateway.js.map +1 -0
  214. package/dist/services/tmux-resolve.d.ts +54 -0
  215. package/dist/services/tmux-resolve.js +0 -0
  216. package/dist/services/tmux-resolve.js.map +1 -0
  217. package/dist/services/tmux.d.ts +32 -0
  218. package/dist/services/tmux.js +107 -0
  219. package/dist/services/tmux.js.map +1 -0
  220. package/dist/services/views.d.ts +7 -0
  221. package/dist/services/views.js +18 -0
  222. package/dist/services/views.js.map +1 -0
  223. package/dist/services/watch.d.ts +36 -0
  224. package/dist/services/watch.js +98 -0
  225. package/dist/services/watch.js.map +1 -0
  226. package/dist/services/workspace.d.ts +19 -0
  227. package/dist/services/workspace.js +88 -0
  228. package/dist/services/workspace.js.map +1 -0
  229. package/package.json +64 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Config: the one thing gmux remembers because a human said so.
3
+ *
4
+ * Everything else in `~/.cache/gmux` is derived and disposable. This
5
+ * isn't, which is why it lives under the config dir instead — see core/paths.ts.
6
+ *
7
+ * Two rules run through this file:
8
+ *
9
+ * 1. **A bad config is not an error.** Unreadable, malformed, or written by a
10
+ * future version — all are treated as *absent*. `gmux ls` must not die because
11
+ * a JSON file got truncated; it should fall back to the behavior of someone
12
+ * who never ran `gmux setup`, which is a behavior that works.
13
+ * 2. **The env var wins.** `GMUX_SUMMARY_CMD` sat on top before config
14
+ * existed, and every script, test and CI job that sets it must keep working
15
+ * without being told about a new file.
16
+ */
17
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
18
+ import { dirname } from "node:path";
19
+ import { configPath } from "../core/paths.js";
20
+ import { DEFAULT_GMUX_CONFIG } from "../core/gmux-types.js";
21
+ import { CONFIG_VERSION } from "../core/types.js";
22
+ import { askArgvFor, firstDetected, summaryArgvFor } from "./providers.js";
23
+ /** The last-resort provider, unchanged from before config existed. */
24
+ export const FALLBACK_COMMAND = ["claude", "-p"];
25
+ /** The env var that outranks everything here. */
26
+ export const SUMMARY_CMD_ENV = "GMUX_SUMMARY_CMD";
27
+ /**
28
+ * Set on any `gmux` we spawn ourselves.
29
+ *
30
+ * THE RECURSION GUARD. `gmux ask` runs a provider that may run `gmux grep`, and
31
+ * `gmux grep` would otherwise fire the postAction hook and spawn a summarizer —
32
+ * from inside a process that a summarizer-adjacent call started. The lock would
33
+ * absorb most of it; this stops it being a question in the first place.
34
+ */
35
+ export const CHILD_ENV = "GMUX_CHILD";
36
+ export function isChildProcess(env = process.env) {
37
+ const raw = env[CHILD_ENV];
38
+ return raw != null && raw.trim() !== "" && raw !== "0";
39
+ }
40
+ /** The env a spawned provider gets: ours, plus the marker. */
41
+ export function childEnv(env = process.env) {
42
+ return { ...env, [CHILD_ENV]: "1" };
43
+ }
44
+ export function defaultConfig() {
45
+ return { version: CONFIG_VERSION, provider: null, autoSummarize: false };
46
+ }
47
+ /** Parse a config blob. Returns null for anything we cannot trust. */
48
+ export function parseConfig(raw) {
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(raw);
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
57
+ return null;
58
+ const object = parsed;
59
+ // A config from a future version may mean things we'd misread. Ignoring it is
60
+ // safe; guessing is not.
61
+ const version = object["version"];
62
+ if (typeof version !== "number" || version > CONFIG_VERSION)
63
+ return null;
64
+ // Every field must be present and well-typed, or the file is not one we
65
+ // wrote and we do not get to guess at what it meant.
66
+ if (typeof object["autoSummarize"] !== "boolean")
67
+ return null;
68
+ if (!("provider" in object))
69
+ return null;
70
+ /**
71
+ * THE DISTINCTION THIS FUNCTION EXISTS FOR.
72
+ *
73
+ * An explicit `"provider": null` is a *decision* — "make no model calls" —
74
+ * and must be honored. A provider that is present but malformed is
75
+ * *corruption*, and must make the whole config untrusted, which resolves to
76
+ * autodetect.
77
+ *
78
+ * Collapsing the two means a truncated write, or a hand-edited file with a
79
+ * typo, silently disables every model-backed feature — permanently, since the
80
+ * file still exists and so the wizard never runs again. The user would see
81
+ * summaries stop and have nothing to look at but a config that reads fine.
82
+ */
83
+ const raw_provider = object["provider"];
84
+ let provider = null;
85
+ if (raw_provider !== null) {
86
+ provider = parseProvider(raw_provider);
87
+ if (!provider)
88
+ return null;
89
+ }
90
+ // gmux is newer than the rest of GmConfig, so its absence — or a partial
91
+ // block from a version that wrote fewer fields — must not untrust the whole
92
+ // file. resolveGmuxConfig fills in whatever this leaves out.
93
+ const gmux = parseGmuxBlock(object["gmux"]);
94
+ return { version, provider, autoSummarize: object["autoSummarize"], ...(gmux ? { gmux } : {}) };
95
+ }
96
+ /**
97
+ * Parse the gmux block leniently: field-by-field, keeping whatever is
98
+ * well-typed and dropping the rest — never the whole block, and never the
99
+ * config it lives in. `resolveGmuxConfig` is what turns this partial result
100
+ * into a complete `GmuxConfig`.
101
+ */
102
+ function parseGmuxBlock(value) {
103
+ if (!value || typeof value !== "object" || Array.isArray(value))
104
+ return undefined;
105
+ const object = value;
106
+ const result = {};
107
+ if (object["guardianPolicy"] === "off" || object["guardianPolicy"] === "notify" || object["guardianPolicy"] === "auto") {
108
+ result.guardianPolicy = object["guardianPolicy"];
109
+ }
110
+ if (typeof object["memoryThreshold"] === "number")
111
+ result.memoryThreshold = object["memoryThreshold"];
112
+ if (typeof object["cooldownSeconds"] === "number")
113
+ result.cooldownSeconds = object["cooldownSeconds"];
114
+ if (typeof object["tickMs"] === "number")
115
+ result.tickMs = object["tickMs"];
116
+ if (Object.keys(result).length === 0)
117
+ return undefined;
118
+ return result;
119
+ }
120
+ /**
121
+ * Fill in whatever a stored `gmux` block leaves out, from `DEFAULT_GMUX_CONFIG`.
122
+ *
123
+ * Never fails on a missing or partial block — older configs have none at all,
124
+ * and `parseGmuxBlock` above only ever keeps well-typed fields, so what
125
+ * reaches here is trustworthy as far as it goes.
126
+ */
127
+ export function resolveGmuxConfig(config) {
128
+ const g = config?.gmux;
129
+ if (!g)
130
+ return { ...DEFAULT_GMUX_CONFIG };
131
+ const policy = g.guardianPolicy === "off" || g.guardianPolicy === "notify" || g.guardianPolicy === "auto"
132
+ ? g.guardianPolicy
133
+ : DEFAULT_GMUX_CONFIG.guardianPolicy;
134
+ return {
135
+ guardianPolicy: policy,
136
+ memoryThreshold: typeof g.memoryThreshold === "number" ? g.memoryThreshold : DEFAULT_GMUX_CONFIG.memoryThreshold,
137
+ cooldownSeconds: typeof g.cooldownSeconds === "number" ? g.cooldownSeconds : DEFAULT_GMUX_CONFIG.cooldownSeconds,
138
+ tickMs: typeof g.tickMs === "number" ? g.tickMs : DEFAULT_GMUX_CONFIG.tickMs,
139
+ };
140
+ }
141
+ /**
142
+ * Parse a provider. Null means malformed — NOT "no provider".
143
+ *
144
+ * The caller turns a null here into an untrusted config. A command containing
145
+ * anything that is not a string is rejected rather than filtered: dropping the
146
+ * bad part would spawn a *different* command than the one on disk, which is a
147
+ * worse outcome than declining to spawn anything.
148
+ */
149
+ function parseProvider(value) {
150
+ if (!value || typeof value !== "object" || Array.isArray(value))
151
+ return null;
152
+ const object = value;
153
+ const id = object["id"];
154
+ const command = object["command"];
155
+ if (typeof id !== "string" || id.trim() === "")
156
+ return null;
157
+ if (!Array.isArray(command) || command.length === 0)
158
+ return null;
159
+ if (!command.every((part) => typeof part === "string"))
160
+ return null;
161
+ return { id, command: [...command] };
162
+ }
163
+ /** The config on disk, or null when there isn't a usable one. */
164
+ export async function readConfig() {
165
+ try {
166
+ return parseConfig(await readFile(configPath(), "utf8"));
167
+ }
168
+ catch {
169
+ return null; // Absent, unreadable — same answer either way.
170
+ }
171
+ }
172
+ /**
173
+ * True when a config file exists, however broken.
174
+ *
175
+ * Distinct from `readConfig() !== null` on purpose: the first-run wizard keys
176
+ * off *existence*, so a corrupt file doesn't re-launch the wizard on every
177
+ * single run. It falls back to defaults quietly instead, and `gmux doctor` says so.
178
+ */
179
+ export async function configExists() {
180
+ try {
181
+ await readFile(configPath(), "utf8");
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ /** Write config through a temp file and a rename, so a kill can't truncate it. */
189
+ export async function writeConfig(config) {
190
+ const path = configPath();
191
+ await mkdir(dirname(path), { recursive: true });
192
+ const temp = `${path}.${process.pid}.tmp`;
193
+ await writeFile(temp, `${JSON.stringify(config, null, 2)}\n`, "utf8");
194
+ await rename(temp, path);
195
+ }
196
+ /**
197
+ * Split a command string into argv, honoring quotes and backslash escapes.
198
+ *
199
+ * Splitting on whitespace alone was wrong in a way that fails quietly: a
200
+ * perfectly reasonable `llm -m "model name"` became four arguments with literal
201
+ * quote characters embedded — `["llm","-m","\"model","name\""]` — and gmux then
202
+ * spawned a command the user never typed. The wizard invites free-form input, so
203
+ * "don't type spaces in your arguments" is not a rule we get to have.
204
+ *
205
+ * This is not a shell. It handles the quoting a person actually uses in an
206
+ * answer to a prompt — single quotes, double quotes, backslash escapes — and
207
+ * deliberately does NOT do expansion, substitution, globbing or operators. The
208
+ * argv goes to `spawn` without a shell, so there is nothing for those to mean,
209
+ * and pretending otherwise would invite an injection surface where none exists.
210
+ *
211
+ * An unterminated quote closes at end of input rather than throwing: the user is
212
+ * standing at a prompt, and taking their obvious intent beats an error message
213
+ * about lexing.
214
+ */
215
+ export function parseCommand(input) {
216
+ const argv = [];
217
+ let current = "";
218
+ let started = false; // Distinguishes a real empty argument ("") from no argument.
219
+ let quote = null;
220
+ for (let i = 0; i < input.length; i++) {
221
+ const char = input[i];
222
+ if (quote === null && (char === " " || char === "\t" || char === "\n")) {
223
+ if (started)
224
+ argv.push(current);
225
+ current = "";
226
+ started = false;
227
+ continue;
228
+ }
229
+ // Backslash escapes the next character, except inside single quotes — where
230
+ // a backslash is a literal backslash, as in every POSIX shell.
231
+ if (char === "\\" && quote !== "'" && i + 1 < input.length) {
232
+ current += input[++i];
233
+ started = true;
234
+ continue;
235
+ }
236
+ if (quote === null && (char === '"' || char === "'")) {
237
+ quote = char;
238
+ started = true; // `gmux ask ""` is an empty argument, not an absent one.
239
+ continue;
240
+ }
241
+ if (char === quote) {
242
+ quote = null;
243
+ continue;
244
+ }
245
+ current += char;
246
+ started = true;
247
+ }
248
+ if (started)
249
+ argv.push(current);
250
+ return argv;
251
+ }
252
+ /**
253
+ * The command to use for summaries, or null for "make no model calls".
254
+ *
255
+ * Pure, and takes `detected` rather than probing PATH itself, so the precedence
256
+ * rule can be tested without a machine that happens to have Claude installed.
257
+ *
258
+ * Highest wins:
259
+ * 1. GMUX_SUMMARY_CMD
260
+ * 2. config.provider.command
261
+ * 3. first detected provider
262
+ * 4. `claude -p`
263
+ *
264
+ * Rules 3 and 4 are the pre-config behavior, preserved exactly: someone with no
265
+ * config file sees no change at all.
266
+ */
267
+ export function resolveSummaryCommand(config, env = process.env, detected = firstDetected()) {
268
+ const override = env[SUMMARY_CMD_ENV];
269
+ if (override && override.trim() !== "")
270
+ return parseCommand(override);
271
+ // Only an existing config can say "no": absent config means "not asked yet",
272
+ // which falls through to autodetect rather than to silence. A known provider
273
+ // re-derives from the catalog (summaryArgvFor), so a flag added after setup
274
+ // reaches it — the same self-healing resolveAskCommand already has.
275
+ if (config)
276
+ return config.provider ? summaryArgvFor(config.provider) : null;
277
+ if (detected)
278
+ return [...detected.summaryArgv];
279
+ return [...FALLBACK_COMMAND];
280
+ }
281
+ /**
282
+ * The command to use for `gmux ask`, or null for "make no model calls".
283
+ *
284
+ * Same precedence, but the catalog's `askArgv` is used where we recognise the
285
+ * provider — that's the variant permitted to run `gmux grep`.
286
+ */
287
+ export function resolveAskCommand(config, env = process.env, detected = firstDetected()) {
288
+ const override = env[SUMMARY_CMD_ENV];
289
+ if (override && override.trim() !== "")
290
+ return parseCommand(override);
291
+ if (config)
292
+ return config.provider ? askArgvFor(config.provider) : null;
293
+ if (detected)
294
+ return [...detected.askArgv];
295
+ return [...FALLBACK_COMMAND];
296
+ }
297
+ /**
298
+ * Whether background summaries are allowed.
299
+ *
300
+ * `GMUX_AUTO_SUMMARIZE=0` is checked by the caller and still wins. This
301
+ * adds the config answer: a user who said "no" in the wizard has said it once
302
+ * and for good, and must not be asked again by a token spend they declined.
303
+ *
304
+ * No config means yes — the pre-config default.
305
+ */
306
+ export function autoSummarizeAllowed(config) {
307
+ return config ? config.autoSummarize : true;
308
+ }
309
+ export function shouldRunSetupWizard(gate) {
310
+ if (gate.hasConfig)
311
+ return false;
312
+ if (!gate.isTty)
313
+ return false;
314
+ if (gate.isJson)
315
+ return false;
316
+ // `__`-prefixed commands are the hidden internals we spawn at ourselves.
317
+ // Neither has a human at the other end, and `__picker-rows` runs while fzf
318
+ // owns the terminal — a prompt there would corrupt the display.
319
+ if (gate.commandName.startsWith("__"))
320
+ return false;
321
+ if (gate.commandName === "setup")
322
+ return false;
323
+ return true;
324
+ }
325
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/services/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { WorkspaceSnapshot } from "../core/gmux-types.js";
2
+ export declare function readSnapshotFile(path?: string): Promise<{
3
+ snapshot: WorkspaceSnapshot;
4
+ ageMs: number;
5
+ } | null>;
6
+ export declare function subscribe(onSnapshot: (s: WorkspaceSnapshot) => void, opts?: {
7
+ socketPath?: string;
8
+ onError?: (e: Error) => void;
9
+ }): () => void;
@@ -0,0 +1,34 @@
1
+ import { connect } from "node:net";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { gmuxSnapshotPath, gmuxSocketPath } from "../core/paths.js";
4
+ export async function readSnapshotFile(path = gmuxSnapshotPath()) {
5
+ try {
6
+ const [raw, st] = await Promise.all([readFile(path, "utf8"), stat(path)]);
7
+ const snapshot = JSON.parse(raw);
8
+ return { snapshot, ageMs: Math.max(0, Date.now() - st.mtimeMs) };
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ export function subscribe(onSnapshot, opts = {}) {
15
+ const sock = connect(opts.socketPath ?? gmuxSocketPath());
16
+ let buf = "";
17
+ sock.on("data", (d) => {
18
+ buf += d.toString();
19
+ let nl;
20
+ while ((nl = buf.indexOf("\n")) >= 0) {
21
+ const line = buf.slice(0, nl);
22
+ buf = buf.slice(nl + 1);
23
+ if (line.trim()) {
24
+ try {
25
+ onSnapshot(JSON.parse(line));
26
+ }
27
+ catch { /* skip */ }
28
+ }
29
+ }
30
+ });
31
+ sock.on("error", (e) => opts.onError?.(e));
32
+ return () => sock.destroy();
33
+ }
34
+ //# sourceMappingURL=daemon-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-client.js","sourceRoot":"","sources":["../../src/services/daemon-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,gBAAgB,EAAE;IAEjC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;QACtD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,UAA0C,EAC1C,OAA8D,EAAE;IAEhE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,IAAI,cAAc,EAAE,CAAC,CAAC;IAC1D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;QACpB,GAAG,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QACpB,IAAI,EAAU,CAAC;QACf,OAAO,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACxB,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAAC,IAAI,CAAC;oBAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAsB,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;YAAC,CAAC;QACtG,CAAC;IACH,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { WorkspaceModel } from "./workspace.js";
2
+ export declare class ModelServer {
3
+ private readonly model;
4
+ private readonly socketPath;
5
+ private readonly snapshotPath;
6
+ private server;
7
+ private clients;
8
+ private readonly onChange;
9
+ /** Serializes snapshot-file writes so overlapping "change" bursts never
10
+ * race on the same temp path (one write's `rename` could otherwise yank
11
+ * the temp file out from under another still-in-flight write). */
12
+ private writeChain;
13
+ private writeCounter;
14
+ constructor(model: WorkspaceModel, socketPath?: string, snapshotPath?: string);
15
+ start(): Promise<void>;
16
+ private line;
17
+ private broadcast;
18
+ /**
19
+ * Serialized through `writeChain` so a burst of near-simultaneous "change"
20
+ * events (e.g. N `upsertIdentity` calls during an initial scan) never has
21
+ * two writes in flight at once — an overlapping `rename()` could otherwise
22
+ * yank the shared temp file out from under a sibling write (ENOENT) and
23
+ * crash the process via an unhandled rejection. Each call also gets its
24
+ * own temp filename as belt-and-suspenders. Never rejects: a failed write
25
+ * is swallowed rather than surfaced, so it can't become an unhandled
26
+ * rejection further up the chain either.
27
+ */
28
+ private writeSnapshot;
29
+ stop(): Promise<void>;
30
+ }
@@ -0,0 +1,76 @@
1
+ // src/services/daemon-socket.ts
2
+ import { createServer } from "node:net";
3
+ import { mkdir, rename, rm, writeFile } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { gmuxSnapshotPath, gmuxSocketPath } from "../core/paths.js";
6
+ export class ModelServer {
7
+ model;
8
+ socketPath;
9
+ snapshotPath;
10
+ server;
11
+ clients = new Set();
12
+ onChange = () => { void this.broadcast().catch(() => { }); };
13
+ /** Serializes snapshot-file writes so overlapping "change" bursts never
14
+ * race on the same temp path (one write's `rename` could otherwise yank
15
+ * the temp file out from under another still-in-flight write). */
16
+ writeChain = Promise.resolve();
17
+ writeCounter = 0;
18
+ constructor(model, socketPath = gmuxSocketPath(), snapshotPath = gmuxSnapshotPath()) {
19
+ this.model = model;
20
+ this.socketPath = socketPath;
21
+ this.snapshotPath = snapshotPath;
22
+ }
23
+ async start() {
24
+ await mkdir(dirname(this.socketPath), { recursive: true });
25
+ await rm(this.socketPath, { force: true });
26
+ this.server = createServer((sock) => {
27
+ this.clients.add(sock);
28
+ sock.on("close", () => this.clients.delete(sock));
29
+ sock.on("error", () => this.clients.delete(sock));
30
+ sock.write(this.line()); // initial snapshot
31
+ });
32
+ await new Promise((resolve, reject) => {
33
+ this.server.once("error", reject);
34
+ this.server.listen(this.socketPath, resolve);
35
+ });
36
+ this.model.on("change", this.onChange);
37
+ await this.writeSnapshot(this.line());
38
+ }
39
+ line() { return JSON.stringify(this.model.snapshot()) + "\n"; }
40
+ async broadcast() {
41
+ const line = this.line();
42
+ // Write the snapshot file first so it never lags behind what clients see
43
+ // (also avoids a race where a socket read outpaces the async fs write).
44
+ await this.writeSnapshot(line);
45
+ for (const c of this.clients)
46
+ c.write(line);
47
+ }
48
+ /**
49
+ * Serialized through `writeChain` so a burst of near-simultaneous "change"
50
+ * events (e.g. N `upsertIdentity` calls during an initial scan) never has
51
+ * two writes in flight at once — an overlapping `rename()` could otherwise
52
+ * yank the shared temp file out from under a sibling write (ENOENT) and
53
+ * crash the process via an unhandled rejection. Each call also gets its
54
+ * own temp filename as belt-and-suspenders. Never rejects: a failed write
55
+ * is swallowed rather than surfaced, so it can't become an unhandled
56
+ * rejection further up the chain either.
57
+ */
58
+ async writeSnapshot(content) {
59
+ const tmp = `${this.snapshotPath}.${process.pid}.${this.writeCounter++}.tmp`;
60
+ this.writeChain = this.writeChain.then(async () => {
61
+ await mkdir(dirname(this.snapshotPath), { recursive: true });
62
+ await writeFile(tmp, content, "utf8");
63
+ await rename(tmp, this.snapshotPath);
64
+ }).catch(() => { });
65
+ await this.writeChain;
66
+ }
67
+ async stop() {
68
+ this.model.off("change", this.onChange);
69
+ for (const c of this.clients)
70
+ c.destroy();
71
+ this.clients.clear();
72
+ await new Promise((resolve) => (this.server ? this.server.close(() => resolve()) : resolve()));
73
+ await rm(this.socketPath, { force: true });
74
+ }
75
+ }
76
+ //# sourceMappingURL=daemon-socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-socket.js","sourceRoot":"","sources":["../../src/services/daemon-socket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The daemon tick loop: registry diff → attach/detach sensors → observe →
3
+ * classify → write state to the model → evict gone panes.
4
+ *
5
+ * No LLM here (Phase 0) — `classifyState` is a pure heuristic over the raw
6
+ * `Observation`. Sensors and the clock are injected so the whole loop can be
7
+ * driven headlessly in tests, without a real tmux server.
8
+ */
9
+ import type { PaneIdentity } from "../core/gmux-types.js";
10
+ import type { TmuxGateway } from "./tmux-gateway.js";
11
+ import type { Guardian } from "./guardian.js";
12
+ import type { ResourceMonitor } from "./resources.js";
13
+ import { type Sensor } from "./sensors.js";
14
+ import type { SemanticWorker } from "./semantic.js";
15
+ import type { WorkspaceModel } from "./workspace.js";
16
+ export interface DaemonDeps {
17
+ gateway: TmuxGateway;
18
+ model: WorkspaceModel;
19
+ now: () => number;
20
+ makeSensor?: (id: PaneIdentity, gw: TmuxGateway) => Sensor;
21
+ semantic?: SemanticWorker;
22
+ resources?: ResourceMonitor;
23
+ guardian?: Guardian;
24
+ /**
25
+ * Overrides which present panes count as "agent panes" for guardian
26
+ * broadcast targeting. Defaults to panes the registry has resolved to a
27
+ * harness + session (`harness && sessionId`). This override exists only
28
+ * so tests can mark panes as agents without a full link fixture — real
29
+ * callers should leave it unset.
30
+ */
31
+ resolveAgents?: (present: PaneIdentity[]) => string[];
32
+ }
33
+ export declare class Daemon {
34
+ private readonly deps;
35
+ private registry;
36
+ /** Long-lived per pane: created on appear, torn down on vanish. This is
37
+ * what lets AgentSensor's lazy transcript-file resolution pay off over
38
+ * the pane's whole lifetime instead of retrying from scratch each tick. */
39
+ private sensors;
40
+ private make;
41
+ constructor(deps: DaemonDeps);
42
+ tickOnce(): Promise<void>;
43
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The daemon tick loop: registry diff → attach/detach sensors → observe →
3
+ * classify → write state to the model → evict gone panes.
4
+ *
5
+ * No LLM here (Phase 0) — `classifyState` is a pure heuristic over the raw
6
+ * `Observation`. Sensors and the clock are injected so the whole loop can be
7
+ * driven headlessly in tests, without a real tmux server.
8
+ */
9
+ import { classifyState } from "../core/pane-state.js";
10
+ import { PaneRegistry } from "./pane-registry.js";
11
+ import { makeSensor as defaultMakeSensor } from "./sensors.js";
12
+ export class Daemon {
13
+ deps;
14
+ registry;
15
+ /** Long-lived per pane: created on appear, torn down on vanish. This is
16
+ * what lets AgentSensor's lazy transcript-file resolution pay off over
17
+ * the pane's whole lifetime instead of retrying from scratch each tick. */
18
+ sensors = new Map();
19
+ make;
20
+ constructor(deps) {
21
+ this.deps = deps;
22
+ this.registry = new PaneRegistry(deps.gateway);
23
+ this.make = deps.makeSensor ?? defaultMakeSensor;
24
+ }
25
+ async tickOnce() {
26
+ const now = this.deps.now();
27
+ let diff;
28
+ try {
29
+ diff = await this.registry.diff();
30
+ }
31
+ catch {
32
+ // tmux hiccup (e.g. listPanes throws): idle this tick, loop survives.
33
+ return;
34
+ }
35
+ const { present, vanished } = diff;
36
+ for (const id of vanished) {
37
+ await this.sensors.get(id)?.teardown().catch(() => { });
38
+ this.sensors.delete(id);
39
+ this.deps.model.markGone(id);
40
+ }
41
+ for (const id of present) {
42
+ this.deps.model.upsertIdentity(id);
43
+ let sensor = this.sensors.get(id.paneId);
44
+ if (!sensor) {
45
+ sensor = this.make(id, this.deps.gateway);
46
+ this.sensors.set(id.paneId, sensor);
47
+ }
48
+ try {
49
+ const obs = await sensor.observe(now);
50
+ this.deps.model.applyState(id.paneId, classifyState(obs, now), obs.lastActivityTs, now);
51
+ if (this.deps.semantic) {
52
+ const entry = this.deps.model.snapshot().panes.find((p) => p.identity.paneId === id.paneId);
53
+ if (entry)
54
+ this.deps.semantic.enqueue(entry, obs, now); // fire-and-forget, never awaited
55
+ }
56
+ }
57
+ catch {
58
+ // A single sensor failure must never abort the whole tick.
59
+ }
60
+ }
61
+ if (this.deps.resources) {
62
+ try {
63
+ const panePids = new Map(present.map((p) => [p.paneId, p.pid]));
64
+ const { perPane, host } = await this.deps.resources.sample(panePids);
65
+ for (const [paneId, res] of perPane)
66
+ this.deps.model.applyResources(paneId, res);
67
+ this.deps.model.setHostPressure(host);
68
+ if (this.deps.guardian) {
69
+ const agentIds = this.deps.resolveAgents?.(present)
70
+ ?? present.filter((p) => p.harness && p.sessionId).map((p) => p.paneId);
71
+ const agentIdSet = new Set(agentIds);
72
+ // Guardian.decide treats a pane as an agent via identity.harness &&
73
+ // identity.sessionId. `resolveAgents` is a test-only override, so
74
+ // when it's set, patch those fields (decision purposes only, never
75
+ // persisted to the model) so the guardian agrees with the override.
76
+ const panes = this.deps.model.snapshot().panes.map((p) => {
77
+ if (!this.deps.resolveAgents || !agentIdSet.has(p.identity.paneId) || p.identity.harness)
78
+ return p;
79
+ return { ...p, identity: { ...p.identity, harness: "test-agent", sessionId: p.identity.paneId } };
80
+ });
81
+ const decision = this.deps.guardian.decide(host, panes, now);
82
+ if (decision.action !== "none") {
83
+ if (decision.action === "broadcast") {
84
+ for (const id of agentIds) {
85
+ await this.deps.gateway.send(id, decision.message + "\n").catch(() => { });
86
+ }
87
+ }
88
+ this.deps.model.logGuardian({
89
+ ts: now,
90
+ pressure: host.usedRatio,
91
+ culpritPaneId: decision.culpritPaneId,
92
+ culpritLabel: decision.culpritLabel,
93
+ action: decision.action,
94
+ message: decision.message,
95
+ });
96
+ }
97
+ }
98
+ }
99
+ catch {
100
+ // Resource sampling / guardian failures must never abort the tick.
101
+ }
102
+ }
103
+ this.deps.model.evictGone();
104
+ }
105
+ }
106
+ //# sourceMappingURL=daemon.js.map