skillmux 0.1.3 → 0.1.5
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/dist/{chunk-DBEVDI27.js → chunk-OY3C7VIL.js} +745 -225
- package/dist/chunk-OY3C7VIL.js.map +1 -0
- package/dist/{chunk-UMN3UJFN.js → chunk-R5V2WOZV.js} +15 -432
- package/dist/chunk-R5V2WOZV.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/launch-tui-4TJFQA3L.js +3189 -0
- package/dist/launch-tui-4TJFQA3L.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-DBEVDI27.js.map +0 -1
- package/dist/chunk-UMN3UJFN.js.map +0 -1
- package/dist/launch-tui-PHWJPIQZ.js +0 -1790
- package/dist/launch-tui-PHWJPIQZ.js.map +0 -1
|
@@ -0,0 +1,3189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ManifestValidationError,
|
|
3
|
+
buildEmptyManifest,
|
|
4
|
+
collectDoctorIssues,
|
|
5
|
+
dedupeAndSortIssues,
|
|
6
|
+
discoverAgents,
|
|
7
|
+
formatValidationIssues,
|
|
8
|
+
isPathInside,
|
|
9
|
+
loadUserConfig,
|
|
10
|
+
manifestSchema,
|
|
11
|
+
normalizeAgentId,
|
|
12
|
+
resolveSkillmuxHome,
|
|
13
|
+
runAdopt,
|
|
14
|
+
runConfigAddAgent,
|
|
15
|
+
runConfigRemoveAgent,
|
|
16
|
+
runConfigUpdateAgent,
|
|
17
|
+
runDisable,
|
|
18
|
+
runDoctor,
|
|
19
|
+
runEnable,
|
|
20
|
+
runImport,
|
|
21
|
+
runRemove,
|
|
22
|
+
runScan,
|
|
23
|
+
scanAgentSkills
|
|
24
|
+
} from "./chunk-OY3C7VIL.js";
|
|
25
|
+
|
|
26
|
+
// src/tui/launch-tui.tsx
|
|
27
|
+
import { render } from "ink";
|
|
28
|
+
|
|
29
|
+
// src/tui/app.tsx
|
|
30
|
+
import { readFileSync } from "fs";
|
|
31
|
+
import { Text as Text11, useApp, useInput } from "ink";
|
|
32
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
33
|
+
|
|
34
|
+
// src/tui/forms.ts
|
|
35
|
+
function cloneValues(values) {
|
|
36
|
+
return Object.fromEntries(
|
|
37
|
+
Object.entries(values).map(([key, value]) => [
|
|
38
|
+
key,
|
|
39
|
+
Array.isArray(value) ? [...value] : value
|
|
40
|
+
])
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function isDirty(values, initialValues) {
|
|
44
|
+
return JSON.stringify(values) !== JSON.stringify(initialValues);
|
|
45
|
+
}
|
|
46
|
+
function trimOrEmpty(value) {
|
|
47
|
+
return (value ?? "").trim();
|
|
48
|
+
}
|
|
49
|
+
function normalizePlatformList(value) {
|
|
50
|
+
const platforms = value.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
|
|
51
|
+
return platforms.length === 0 ? void 0 : [...new Set(platforms)];
|
|
52
|
+
}
|
|
53
|
+
function updateFormField(form, field, value) {
|
|
54
|
+
const nextValues = {
|
|
55
|
+
...form.values,
|
|
56
|
+
[field]: value
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
values: nextValues,
|
|
60
|
+
initialValues: form.initialValues,
|
|
61
|
+
dirty: isDirty(nextValues, form.initialValues),
|
|
62
|
+
error: null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildConfigAddAgentDefaults() {
|
|
66
|
+
return {
|
|
67
|
+
id: "",
|
|
68
|
+
root: "root",
|
|
69
|
+
skills: "skills",
|
|
70
|
+
name: "",
|
|
71
|
+
platforms: [],
|
|
72
|
+
disabledByDefault: false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function buildConfigUpdateAgentDefaults() {
|
|
76
|
+
return {
|
|
77
|
+
root: "",
|
|
78
|
+
skills: "skills",
|
|
79
|
+
name: "",
|
|
80
|
+
platforms: [],
|
|
81
|
+
enabledByDefault: false,
|
|
82
|
+
disabledByDefault: false,
|
|
83
|
+
preserveEnabledByDefault: false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function buildImportDefaults() {
|
|
87
|
+
return {
|
|
88
|
+
sourcePath: "",
|
|
89
|
+
skillName: ""
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function normalizeConfigAddAgentValues(values) {
|
|
93
|
+
return {
|
|
94
|
+
id: trimOrEmpty(values.id),
|
|
95
|
+
root: trimOrEmpty(values.root),
|
|
96
|
+
skills: trimOrEmpty(values.skills) || void 0,
|
|
97
|
+
name: trimOrEmpty(values.name) || void 0,
|
|
98
|
+
platforms: normalizePlatformList(values.platforms),
|
|
99
|
+
disabledByDefault: values.disabledByDefault ? true : void 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeConfigUpdateAgentValues(values, agentId) {
|
|
103
|
+
const enabledByDefault = values.enabledByDefault ? true : void 0;
|
|
104
|
+
const disabledByDefault = values.disabledByDefault ? true : void 0;
|
|
105
|
+
return {
|
|
106
|
+
id: agentId,
|
|
107
|
+
root: trimOrEmpty(values.root) || void 0,
|
|
108
|
+
skills: trimOrEmpty(values.skills) || void 0,
|
|
109
|
+
name: trimOrEmpty(values.name) || void 0,
|
|
110
|
+
platforms: normalizePlatformList(values.platforms),
|
|
111
|
+
...values.preserveEnabledByDefault ? {} : {
|
|
112
|
+
enabledByDefault,
|
|
113
|
+
disabledByDefault
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function normalizeImportValues(values) {
|
|
118
|
+
return {
|
|
119
|
+
sourcePath: trimOrEmpty(values.sourcePath),
|
|
120
|
+
skillName: trimOrEmpty(values.skillName)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function createConfigAddAgentForm(values = {}) {
|
|
124
|
+
const initialValues = {
|
|
125
|
+
...buildConfigAddAgentDefaults(),
|
|
126
|
+
...values
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
values: cloneValues(initialValues),
|
|
130
|
+
initialValues: cloneValues(initialValues),
|
|
131
|
+
dirty: false,
|
|
132
|
+
error: null
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function updateConfigAddAgentFormField(form, field, value) {
|
|
136
|
+
return updateFormField(form, field, value);
|
|
137
|
+
}
|
|
138
|
+
function validateConfigAddAgentForm(form) {
|
|
139
|
+
const values = normalizeConfigAddAgentValues(form.values);
|
|
140
|
+
if (values.id.length === 0 || values.root.length === 0) {
|
|
141
|
+
return "Agent id and root are required";
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function buildRunConfigAddAgentOptions(form) {
|
|
146
|
+
return normalizeConfigAddAgentValues(form.values);
|
|
147
|
+
}
|
|
148
|
+
function normalizeRunConfigAddAgentOptions(input) {
|
|
149
|
+
return {
|
|
150
|
+
id: trimOrEmpty(input.id),
|
|
151
|
+
root: trimOrEmpty(input.root),
|
|
152
|
+
skills: input.skills === void 0 ? void 0 : trimOrEmpty(input.skills),
|
|
153
|
+
name: input.name === void 0 ? void 0 : trimOrEmpty(input.name),
|
|
154
|
+
platforms: input.platforms === void 0 ? void 0 : [...new Set(input.platforms.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0))],
|
|
155
|
+
disabledByDefault: input.disabledByDefault === true ? true : void 0
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function createConfigUpdateAgentForm(values = {}) {
|
|
159
|
+
const initialValues = {
|
|
160
|
+
...buildConfigUpdateAgentDefaults(),
|
|
161
|
+
...values
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
values: cloneValues(initialValues),
|
|
165
|
+
initialValues: cloneValues(initialValues),
|
|
166
|
+
dirty: false,
|
|
167
|
+
error: null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function updateConfigUpdateAgentFormField(form, field, value) {
|
|
171
|
+
const next = field === "enabledByDefault" && value === true ? updateFormField(
|
|
172
|
+
updateFormField(form, field, value),
|
|
173
|
+
"disabledByDefault",
|
|
174
|
+
false
|
|
175
|
+
) : field === "disabledByDefault" && value === true ? updateFormField(updateFormField(form, field, value), "enabledByDefault", false) : updateFormField(form, field, value);
|
|
176
|
+
return {
|
|
177
|
+
...next,
|
|
178
|
+
values: {
|
|
179
|
+
...next.values,
|
|
180
|
+
enabledByDefault: field === "disabledByDefault" && value === true ? false : next.values.enabledByDefault,
|
|
181
|
+
disabledByDefault: field === "enabledByDefault" && value === true ? false : next.values.disabledByDefault,
|
|
182
|
+
preserveEnabledByDefault: field === "enabledByDefault" || field === "disabledByDefault" ? false : next.values.preserveEnabledByDefault
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function validateConfigUpdateAgentForm(form) {
|
|
187
|
+
const values = normalizeConfigUpdateAgentValues(form.values, "agent");
|
|
188
|
+
if (values.root !== void 0 && values.root.length === 0) {
|
|
189
|
+
return "Root path cannot be empty";
|
|
190
|
+
}
|
|
191
|
+
if (form.values.enabledByDefault && form.values.disabledByDefault) {
|
|
192
|
+
return "Enabled by default and disabled by default cannot both be set";
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function createConfigUpdateAgentFormFromSeed(seed) {
|
|
197
|
+
return createConfigUpdateAgentForm({
|
|
198
|
+
root: seed.homeRelativeRootPath ?? "",
|
|
199
|
+
skills: seed.skillsDirectoryPath ?? "",
|
|
200
|
+
name: seed.stableName ?? "",
|
|
201
|
+
platforms: [...seed.supportedPlatforms ?? []],
|
|
202
|
+
enabledByDefault: seed.overrideEnabledByDefault === true,
|
|
203
|
+
disabledByDefault: seed.overrideEnabledByDefault === false,
|
|
204
|
+
preserveEnabledByDefault: seed.overrideEnabledByDefault === void 0
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function buildRunConfigUpdateAgentOptions(form, agentId) {
|
|
208
|
+
const values = normalizeConfigUpdateAgentValues(form.values, agentId);
|
|
209
|
+
return {
|
|
210
|
+
id: values.id,
|
|
211
|
+
...values.root === void 0 ? {} : { root: values.root },
|
|
212
|
+
...values.skills === void 0 ? {} : { skills: values.skills },
|
|
213
|
+
...values.name === void 0 ? {} : { name: values.name },
|
|
214
|
+
...values.platforms === void 0 ? {} : { platforms: values.platforms },
|
|
215
|
+
...values.enabledByDefault === void 0 ? {} : { enabledByDefault: true },
|
|
216
|
+
...values.disabledByDefault === void 0 ? {} : { disabledByDefault: true }
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function normalizeRunConfigUpdateAgentOptions(input) {
|
|
220
|
+
return {
|
|
221
|
+
id: trimOrEmpty(input.id),
|
|
222
|
+
...input.root === void 0 ? {} : { root: trimOrEmpty(input.root) },
|
|
223
|
+
...input.skills === void 0 ? {} : { skills: trimOrEmpty(input.skills) },
|
|
224
|
+
...input.name === void 0 ? {} : { name: trimOrEmpty(input.name) },
|
|
225
|
+
...input.platforms === void 0 ? {} : {
|
|
226
|
+
platforms: [...new Set(input.platforms.map((entry) => entry.trim().toLowerCase()))]
|
|
227
|
+
},
|
|
228
|
+
...input.enabledByDefault === true ? { enabledByDefault: true } : {},
|
|
229
|
+
...input.disabledByDefault === true ? { disabledByDefault: true } : {}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function createImportSkillForm(values = {}) {
|
|
233
|
+
const initialValues = {
|
|
234
|
+
...buildImportDefaults(),
|
|
235
|
+
...values
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
values: cloneValues(initialValues),
|
|
239
|
+
initialValues: cloneValues(initialValues),
|
|
240
|
+
dirty: false,
|
|
241
|
+
error: null
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function updateImportSkillFormField(form, field, value) {
|
|
245
|
+
return updateFormField(form, field, value);
|
|
246
|
+
}
|
|
247
|
+
function validateImportSkillForm(form) {
|
|
248
|
+
const values = normalizeImportValues(form.values);
|
|
249
|
+
if (values.sourcePath.length === 0 || values.skillName.length === 0) {
|
|
250
|
+
return "Skill name and source path are required";
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
function buildRunImportOptions(form) {
|
|
255
|
+
return normalizeImportValues(form.values);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/tui/load-dashboard-state.ts
|
|
259
|
+
import { homedir } from "os";
|
|
260
|
+
|
|
261
|
+
// src/manifest/read-manifest-snapshot.ts
|
|
262
|
+
import * as fs from "fs/promises";
|
|
263
|
+
import { join, resolve } from "path";
|
|
264
|
+
function normalizeHomePath(home) {
|
|
265
|
+
const resolvedHome = resolve(home);
|
|
266
|
+
return process.platform === "win32" ? resolvedHome.toLowerCase() : resolvedHome;
|
|
267
|
+
}
|
|
268
|
+
async function readManifestSnapshot(home) {
|
|
269
|
+
const manifestPath = join(home, "manifest.json");
|
|
270
|
+
try {
|
|
271
|
+
const contents = await fs.readFile(manifestPath, "utf8");
|
|
272
|
+
const parsed = manifestSchema.safeParse(JSON.parse(contents));
|
|
273
|
+
if (!parsed.success) {
|
|
274
|
+
throw new ManifestValidationError(
|
|
275
|
+
`Invalid manifest at ${manifestPath}: ${formatValidationIssues(parsed.error)}`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (normalizeHomePath(parsed.data.skillmuxHome) !== normalizeHomePath(home)) {
|
|
279
|
+
throw new ManifestValidationError(
|
|
280
|
+
`Invalid manifest at ${manifestPath}: skillmuxHome must match ${home}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return { manifest: parsed.data, exists: true };
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
286
|
+
return { manifest: buildEmptyManifest(home), exists: false };
|
|
287
|
+
}
|
|
288
|
+
if (error instanceof SyntaxError) {
|
|
289
|
+
throw new ManifestValidationError(
|
|
290
|
+
`Invalid manifest at ${manifestPath}: malformed JSON`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/tui/dashboard-model.ts
|
|
298
|
+
var emptyCounts = () => ({
|
|
299
|
+
enabledCount: 0,
|
|
300
|
+
disabledCount: 0,
|
|
301
|
+
unmanagedCount: 0,
|
|
302
|
+
issueCount: 0
|
|
303
|
+
});
|
|
304
|
+
function sortById(values) {
|
|
305
|
+
return [...values].sort((left, right) => left.id.localeCompare(right.id));
|
|
306
|
+
}
|
|
307
|
+
function getSelectedAgentId(agents, requestedAgentId) {
|
|
308
|
+
if (requestedAgentId !== void 0) {
|
|
309
|
+
const requestedAgent = agents.find((agent) => agent.id === requestedAgentId);
|
|
310
|
+
if (requestedAgent !== void 0) {
|
|
311
|
+
return requestedAgent.id;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const availableAgent = agents.find(
|
|
315
|
+
(agent) => agent.exists && agent.supportedOnPlatform
|
|
316
|
+
);
|
|
317
|
+
return availableAgent?.id ?? agents[0]?.id ?? null;
|
|
318
|
+
}
|
|
319
|
+
function findEnabledActivation(manifest, skillId, agentId) {
|
|
320
|
+
return manifest.activations.find(
|
|
321
|
+
(activation) => activation.skillId === skillId && activation.agentId === agentId && activation.state === "enabled"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
function findAnyActivation(manifest, skillId, agentId) {
|
|
325
|
+
return manifest.activations.find(
|
|
326
|
+
(activation) => activation.skillId === skillId && activation.agentId === agentId
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
function buildManagedSkillRow(manifest, skill, agentId) {
|
|
330
|
+
const enabledActivation = findEnabledActivation(manifest, skill.id, agentId);
|
|
331
|
+
if (enabledActivation !== void 0) {
|
|
332
|
+
return {
|
|
333
|
+
id: skill.id,
|
|
334
|
+
kind: "enabled",
|
|
335
|
+
marker: "\u25CF",
|
|
336
|
+
skillId: skill.id,
|
|
337
|
+
name: skill.name,
|
|
338
|
+
path: skill.path,
|
|
339
|
+
agentId,
|
|
340
|
+
activationLinkPath: enabledActivation.linkPath
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
id: skill.id,
|
|
345
|
+
kind: "disabled",
|
|
346
|
+
marker: "\u25CB",
|
|
347
|
+
skillId: skill.id,
|
|
348
|
+
name: skill.name,
|
|
349
|
+
path: skill.path,
|
|
350
|
+
agentId,
|
|
351
|
+
activationLinkPath: findAnyActivation(manifest, skill.id, agentId)?.linkPath ?? null
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function isAdoptableEntry(entry) {
|
|
355
|
+
return entry.kind === "unmanaged-directory" || entry.kind === "unmanaged-link";
|
|
356
|
+
}
|
|
357
|
+
function buildUnmanagedRows(entries, agentId) {
|
|
358
|
+
return entries.filter((entry) => entry.agentId === agentId).filter(isAdoptableEntry).sort(
|
|
359
|
+
(left, right) => `${left.skillName}:${left.path}`.localeCompare(
|
|
360
|
+
`${right.skillName}:${right.path}`
|
|
361
|
+
)
|
|
362
|
+
).map((entry) => ({
|
|
363
|
+
id: `unmanaged:${entry.skillName}`,
|
|
364
|
+
kind: "unmanaged",
|
|
365
|
+
marker: "?",
|
|
366
|
+
skillName: entry.skillName,
|
|
367
|
+
name: entry.skillName,
|
|
368
|
+
path: entry.path,
|
|
369
|
+
agentId,
|
|
370
|
+
entryKind: entry.kind,
|
|
371
|
+
targetPath: entry.targetPath
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
function relatedAgentIdsForIssue(issue, agents) {
|
|
375
|
+
if (issue.path === void 0) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const issuePath2 = issue.path;
|
|
379
|
+
return agents.filter((agent) => isPathInside(agent.absoluteSkillsDirectoryPath, issuePath2)).map((agent) => agent.id);
|
|
380
|
+
}
|
|
381
|
+
function buildIssueId(issue, agentId) {
|
|
382
|
+
return `issue:${agentId}:${issue.code}:${issue.path ?? issue.message}`;
|
|
383
|
+
}
|
|
384
|
+
function buildIssueRows(issues, agents, agentId) {
|
|
385
|
+
return issues.filter((issue) => {
|
|
386
|
+
const relatedAgentIds = relatedAgentIdsForIssue(issue, agents);
|
|
387
|
+
return relatedAgentIds.includes(agentId);
|
|
388
|
+
}).map((issue) => ({
|
|
389
|
+
id: buildIssueId(issue, agentId),
|
|
390
|
+
kind: "issue",
|
|
391
|
+
marker: "!",
|
|
392
|
+
issueCode: issue.code,
|
|
393
|
+
severity: issue.severity,
|
|
394
|
+
message: issue.message,
|
|
395
|
+
path: issue.path ?? null,
|
|
396
|
+
agentId
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
function buildSkillRowsForAgent(input, agentId) {
|
|
400
|
+
const managedRows = sortById(Object.values(input.manifest.skills)).map(
|
|
401
|
+
(skill) => buildManagedSkillRow(input.manifest, skill, agentId)
|
|
402
|
+
);
|
|
403
|
+
const unmanagedRows = buildUnmanagedRows(input.entries, agentId);
|
|
404
|
+
const issueRows = buildIssueRows(input.issues, input.agents, agentId);
|
|
405
|
+
return [...managedRows, ...unmanagedRows, ...issueRows];
|
|
406
|
+
}
|
|
407
|
+
function countsForRows(rows) {
|
|
408
|
+
const counts = emptyCounts();
|
|
409
|
+
for (const row of rows) {
|
|
410
|
+
if (row.kind === "enabled") {
|
|
411
|
+
counts.enabledCount += 1;
|
|
412
|
+
} else if (row.kind === "disabled") {
|
|
413
|
+
counts.disabledCount += 1;
|
|
414
|
+
} else if (row.kind === "unmanaged") {
|
|
415
|
+
counts.unmanagedCount += 1;
|
|
416
|
+
} else {
|
|
417
|
+
counts.issueCount += 1;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return counts;
|
|
421
|
+
}
|
|
422
|
+
function countActivationsForAgent(manifest, agentId) {
|
|
423
|
+
return manifest.activations.filter((activation) => activation.agentId === agentId).length;
|
|
424
|
+
}
|
|
425
|
+
function hasUserOverride(configuredAgentIds, agentId) {
|
|
426
|
+
return configuredAgentIds.has(agentId);
|
|
427
|
+
}
|
|
428
|
+
function buildAgentRows(input) {
|
|
429
|
+
const configuredAgentIds = new Set(input.configuredAgentIds ?? []);
|
|
430
|
+
return sortById(input.agents).map((agent) => {
|
|
431
|
+
const counts = countsForRows(buildSkillRowsForAgent(input, agent.id));
|
|
432
|
+
const userOverride = hasUserOverride(configuredAgentIds, agent.id);
|
|
433
|
+
const agentOverride = input.agentOverrides?.[agent.id];
|
|
434
|
+
return {
|
|
435
|
+
id: agent.id,
|
|
436
|
+
name: agent.stableName,
|
|
437
|
+
stableName: agent.stableName,
|
|
438
|
+
path: agent.absoluteSkillsDirectoryPath,
|
|
439
|
+
homeRelativeRootPath: agent.homeRelativeRootPath,
|
|
440
|
+
skillsDirectoryPath: agent.skillsDirectoryPath,
|
|
441
|
+
supportedPlatforms: [...agent.supportedPlatforms],
|
|
442
|
+
enabledByDefault: agent.enabledByDefault,
|
|
443
|
+
...agentOverride?.stableName === void 0 ? {} : { overrideStableName: agentOverride.stableName },
|
|
444
|
+
...agentOverride?.homeRelativeRootPath === void 0 ? {} : { overrideHomeRelativeRootPath: agentOverride.homeRelativeRootPath },
|
|
445
|
+
...agentOverride?.skillsDirectoryPath === void 0 ? {} : { overrideSkillsDirectoryPath: agentOverride.skillsDirectoryPath },
|
|
446
|
+
...agentOverride?.supportedPlatforms === void 0 ? {} : { overrideSupportedPlatforms: [...agentOverride.supportedPlatforms] },
|
|
447
|
+
...agentOverride?.enabledByDefault === void 0 ? {} : { overrideEnabledByDefault: agentOverride.enabledByDefault },
|
|
448
|
+
autoDiscovered: agentOverride?.autoDiscovered === true ? true : void 0,
|
|
449
|
+
discovery: agent.discovery,
|
|
450
|
+
exists: agent.exists,
|
|
451
|
+
supported: agent.supportedOnPlatform,
|
|
452
|
+
hasUserOverride: userOverride,
|
|
453
|
+
canEditOverride: userOverride,
|
|
454
|
+
canRemoveOverride: userOverride,
|
|
455
|
+
activationCount: countActivationsForAgent(input.manifest, agent.id),
|
|
456
|
+
...counts
|
|
457
|
+
};
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
function buildDashboardModel(input) {
|
|
461
|
+
const sortedAgents = sortById(input.agents);
|
|
462
|
+
const selectedAgentId = getSelectedAgentId(
|
|
463
|
+
sortedAgents,
|
|
464
|
+
input.selectedAgentId
|
|
465
|
+
);
|
|
466
|
+
const skills = selectedAgentId === null ? [] : buildSkillRowsForAgent({ ...input, agents: sortedAgents }, selectedAgentId);
|
|
467
|
+
const selectedSkillId = skills.some((row) => row.id === input.selectedSkillId) ? input.selectedSkillId : skills[0]?.id ?? null;
|
|
468
|
+
return {
|
|
469
|
+
agents: buildAgentRows({ ...input, agents: sortedAgents }),
|
|
470
|
+
skills,
|
|
471
|
+
selectedAgentId,
|
|
472
|
+
selectedSkillId,
|
|
473
|
+
lastScanAt: input.manifest.lastScan.at,
|
|
474
|
+
issueCount: input.issues.length
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/tui/load-dashboard-state.ts
|
|
479
|
+
async function loadDashboardState(options = {}) {
|
|
480
|
+
const homeDir = options.homeDir ?? homedir();
|
|
481
|
+
const resolvedSkillmuxHome = resolveSkillmuxHome(homeDir).skillmuxHome;
|
|
482
|
+
const skillmuxHome = options.skillmuxHome ?? resolvedSkillmuxHome;
|
|
483
|
+
const userConfig = await loadUserConfig(skillmuxHome);
|
|
484
|
+
const { manifest } = await readManifestSnapshot(skillmuxHome);
|
|
485
|
+
const agents = await discoverAgents({
|
|
486
|
+
homeDir,
|
|
487
|
+
platform: options.platform,
|
|
488
|
+
skillmuxHome
|
|
489
|
+
});
|
|
490
|
+
const scanResults = [];
|
|
491
|
+
for (const agent of agents) {
|
|
492
|
+
scanResults.push(await scanAgentSkills(agent, skillmuxHome));
|
|
493
|
+
}
|
|
494
|
+
const entries = scanResults.flatMap((result) => result.entries);
|
|
495
|
+
const scanIssues = scanResults.flatMap((result) => result.issues);
|
|
496
|
+
const doctorIssues = await collectDoctorIssues({ manifest, agents, entries });
|
|
497
|
+
const issues = dedupeAndSortIssues([...scanIssues, ...doctorIssues]);
|
|
498
|
+
return buildDashboardModel({
|
|
499
|
+
manifest,
|
|
500
|
+
agents,
|
|
501
|
+
entries,
|
|
502
|
+
issues,
|
|
503
|
+
configuredAgentIds: Object.keys(userConfig.agents),
|
|
504
|
+
agentOverrides: userConfig.agents,
|
|
505
|
+
selectedAgentId: options.selectedAgentId,
|
|
506
|
+
selectedSkillId: options.selectedSkillId
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/tui/actions.ts
|
|
511
|
+
var defaultServices = {
|
|
512
|
+
runEnable,
|
|
513
|
+
runDisable,
|
|
514
|
+
runAdopt,
|
|
515
|
+
runConfigAddAgent,
|
|
516
|
+
runConfigUpdateAgent,
|
|
517
|
+
runConfigRemoveAgent,
|
|
518
|
+
runImport,
|
|
519
|
+
runDoctor,
|
|
520
|
+
runRemove,
|
|
521
|
+
runScan,
|
|
522
|
+
reload: loadDashboardState
|
|
523
|
+
};
|
|
524
|
+
function stripTrailingNewlines(output) {
|
|
525
|
+
return output.replace(/[\r\n]+$/u, "");
|
|
526
|
+
}
|
|
527
|
+
function actionLabel(action) {
|
|
528
|
+
return action.charAt(0).toUpperCase() + action.slice(1);
|
|
529
|
+
}
|
|
530
|
+
function commandLabel(kind) {
|
|
531
|
+
if (kind === "config-add-agent") {
|
|
532
|
+
return "Config add agent";
|
|
533
|
+
}
|
|
534
|
+
if (kind === "config-update-agent") {
|
|
535
|
+
return "Config update agent";
|
|
536
|
+
}
|
|
537
|
+
if (kind === "config-remove-agent") {
|
|
538
|
+
return "Config remove agent";
|
|
539
|
+
}
|
|
540
|
+
if (kind === "import-skill") {
|
|
541
|
+
return "Import skill";
|
|
542
|
+
}
|
|
543
|
+
return "Doctor";
|
|
544
|
+
}
|
|
545
|
+
function errorReason(error) {
|
|
546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
547
|
+
const firstLine = message.split(/\r?\n/u)[0]?.trim();
|
|
548
|
+
return firstLine === void 0 || firstLine.length === 0 ? "Unknown error" : firstLine;
|
|
549
|
+
}
|
|
550
|
+
function reloadOptions(input, selection = {}) {
|
|
551
|
+
const hasSelectedAgentId = Object.prototype.hasOwnProperty.call(
|
|
552
|
+
selection,
|
|
553
|
+
"selectedAgentId"
|
|
554
|
+
);
|
|
555
|
+
const hasSelectedSkillId = Object.prototype.hasOwnProperty.call(
|
|
556
|
+
selection,
|
|
557
|
+
"selectedSkillId"
|
|
558
|
+
);
|
|
559
|
+
return {
|
|
560
|
+
homeDir: input.homeDir,
|
|
561
|
+
skillmuxHome: input.skillmuxHome,
|
|
562
|
+
platform: input.platform,
|
|
563
|
+
selectedAgentId: hasSelectedAgentId ? selection.selectedAgentId ?? void 0 : input.model.selectedAgentId ?? void 0,
|
|
564
|
+
selectedSkillId: hasSelectedSkillId ? selection.selectedSkillId ?? void 0 : input.model.selectedSkillId ?? void 0
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
async function reloadAfterCommand(input, services, output, selection = {}) {
|
|
568
|
+
return {
|
|
569
|
+
model: await services.reload(reloadOptions(input, selection)),
|
|
570
|
+
statusMessage: stripTrailingNewlines(output)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function refusal(model, statusMessage) {
|
|
574
|
+
return { model, statusMessage };
|
|
575
|
+
}
|
|
576
|
+
function resolveSelectedSkill(model) {
|
|
577
|
+
if (model.selectedSkillId === null) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
return model.skills.find((row) => row.id === model.selectedSkillId) ?? null;
|
|
581
|
+
}
|
|
582
|
+
function resolveSelectedAgent(model) {
|
|
583
|
+
if (model.selectedAgentId === null) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
return model.agents.find((row) => row.id === model.selectedAgentId) ?? null;
|
|
587
|
+
}
|
|
588
|
+
function isPendingCommand(action) {
|
|
589
|
+
return typeof action === "object";
|
|
590
|
+
}
|
|
591
|
+
async function dispatchTuiAction(input) {
|
|
592
|
+
const services = { ...defaultServices, ...input.services };
|
|
593
|
+
try {
|
|
594
|
+
if (isPendingCommand(input.action)) {
|
|
595
|
+
if (input.action.kind === "config-add-agent") {
|
|
596
|
+
const normalizedInput = normalizeRunConfigAddAgentOptions(input.action.input);
|
|
597
|
+
const result3 = await services.runConfigAddAgent({
|
|
598
|
+
homeDir: input.homeDir,
|
|
599
|
+
skillmuxHome: input.skillmuxHome,
|
|
600
|
+
...normalizedInput
|
|
601
|
+
});
|
|
602
|
+
return reloadAfterCommand(
|
|
603
|
+
input,
|
|
604
|
+
services,
|
|
605
|
+
result3.output,
|
|
606
|
+
{
|
|
607
|
+
selectedAgentId: result3.agentId ?? normalizeAgentId(normalizedInput.id),
|
|
608
|
+
selectedSkillId: void 0
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (input.action.kind === "config-update-agent") {
|
|
613
|
+
const normalizedInput = normalizeRunConfigUpdateAgentOptions(input.action.input);
|
|
614
|
+
const result3 = await services.runConfigUpdateAgent({
|
|
615
|
+
homeDir: input.homeDir,
|
|
616
|
+
skillmuxHome: input.skillmuxHome,
|
|
617
|
+
...normalizedInput
|
|
618
|
+
});
|
|
619
|
+
return reloadAfterCommand(
|
|
620
|
+
input,
|
|
621
|
+
services,
|
|
622
|
+
result3.output,
|
|
623
|
+
{
|
|
624
|
+
selectedAgentId: result3.agentId ?? normalizedInput.id,
|
|
625
|
+
selectedSkillId: void 0
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (input.action.kind === "config-remove-agent") {
|
|
630
|
+
const result3 = await services.runConfigRemoveAgent({
|
|
631
|
+
homeDir: input.homeDir,
|
|
632
|
+
skillmuxHome: input.skillmuxHome,
|
|
633
|
+
...input.action.input
|
|
634
|
+
});
|
|
635
|
+
return reloadAfterCommand(
|
|
636
|
+
input,
|
|
637
|
+
services,
|
|
638
|
+
result3.output,
|
|
639
|
+
{
|
|
640
|
+
selectedAgentId: null,
|
|
641
|
+
selectedSkillId: void 0
|
|
642
|
+
}
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (input.action.kind === "import-skill") {
|
|
646
|
+
const result3 = await services.runImport({
|
|
647
|
+
homeDir: input.homeDir,
|
|
648
|
+
skillmuxHome: input.skillmuxHome,
|
|
649
|
+
...input.action.input
|
|
650
|
+
});
|
|
651
|
+
return reloadAfterCommand(input, services, result3.output);
|
|
652
|
+
}
|
|
653
|
+
const result2 = await services.runDoctor({
|
|
654
|
+
homeDir: input.homeDir,
|
|
655
|
+
skillmuxHome: input.skillmuxHome,
|
|
656
|
+
platform: input.platform
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
model: input.model,
|
|
660
|
+
statusMessage: stripTrailingNewlines(result2.output),
|
|
661
|
+
doctor: result2
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (input.action === "scan") {
|
|
665
|
+
const result2 = await services.runScan({
|
|
666
|
+
homeDir: input.homeDir,
|
|
667
|
+
skillmuxHome: input.skillmuxHome,
|
|
668
|
+
platform: input.platform
|
|
669
|
+
});
|
|
670
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
671
|
+
}
|
|
672
|
+
if (input.action === "adopt-all") {
|
|
673
|
+
const selectedAgent = resolveSelectedAgent(input.model);
|
|
674
|
+
if (selectedAgent === null) {
|
|
675
|
+
return refusal(input.model, "Select an agent first");
|
|
676
|
+
}
|
|
677
|
+
if (selectedAgent.unmanagedCount <= 0) {
|
|
678
|
+
return refusal(input.model, "No unmanaged skills to adopt for this agent");
|
|
679
|
+
}
|
|
680
|
+
const result2 = await services.runAdopt({
|
|
681
|
+
homeDir: input.homeDir,
|
|
682
|
+
skillmuxHome: input.skillmuxHome,
|
|
683
|
+
agent: selectedAgent.id
|
|
684
|
+
});
|
|
685
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
686
|
+
}
|
|
687
|
+
const selectedSkill = resolveSelectedSkill(input.model);
|
|
688
|
+
if (selectedSkill === null) {
|
|
689
|
+
return refusal(input.model, "Select a skill first");
|
|
690
|
+
}
|
|
691
|
+
if (input.action === "toggle") {
|
|
692
|
+
if (selectedSkill.kind === "enabled") {
|
|
693
|
+
const result2 = await services.runDisable({
|
|
694
|
+
homeDir: input.homeDir,
|
|
695
|
+
skillmuxHome: input.skillmuxHome,
|
|
696
|
+
skill: selectedSkill.skillId,
|
|
697
|
+
agent: selectedSkill.agentId
|
|
698
|
+
});
|
|
699
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
700
|
+
}
|
|
701
|
+
if (selectedSkill.kind === "disabled") {
|
|
702
|
+
const result2 = await services.runEnable({
|
|
703
|
+
homeDir: input.homeDir,
|
|
704
|
+
skillmuxHome: input.skillmuxHome,
|
|
705
|
+
skill: selectedSkill.skillId,
|
|
706
|
+
agent: selectedSkill.agentId
|
|
707
|
+
});
|
|
708
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
709
|
+
}
|
|
710
|
+
return refusal(input.model, "Toggle is only available for managed rows");
|
|
711
|
+
}
|
|
712
|
+
if (input.action === "adopt") {
|
|
713
|
+
if (selectedSkill.kind !== "unmanaged") {
|
|
714
|
+
return refusal(input.model, "Adopt is only available for unmanaged rows");
|
|
715
|
+
}
|
|
716
|
+
const result2 = await services.runAdopt({
|
|
717
|
+
homeDir: input.homeDir,
|
|
718
|
+
skillmuxHome: input.skillmuxHome,
|
|
719
|
+
agent: selectedSkill.agentId,
|
|
720
|
+
skill: selectedSkill.skillName
|
|
721
|
+
});
|
|
722
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
723
|
+
}
|
|
724
|
+
if (selectedSkill.kind === "enabled") {
|
|
725
|
+
return refusal(input.model, "Disable this skill before removing it");
|
|
726
|
+
}
|
|
727
|
+
if (selectedSkill.kind !== "disabled") {
|
|
728
|
+
return refusal(input.model, "Remove is only available for disabled rows");
|
|
729
|
+
}
|
|
730
|
+
const result = await services.runRemove({
|
|
731
|
+
homeDir: input.homeDir,
|
|
732
|
+
skillmuxHome: input.skillmuxHome,
|
|
733
|
+
skill: selectedSkill.skillId
|
|
734
|
+
});
|
|
735
|
+
return reloadAfterCommand(input, services, result.output);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
const label = isPendingCommand(input.action) ? commandLabel(input.action.kind) : actionLabel(input.action);
|
|
738
|
+
return {
|
|
739
|
+
model: input.model,
|
|
740
|
+
statusMessage: `${label} failed: ${errorReason(error)}`,
|
|
741
|
+
commandSucceeded: false
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/tui/components/Dashboard.tsx
|
|
747
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
748
|
+
|
|
749
|
+
// src/tui/state.ts
|
|
750
|
+
var focusOrder = ["agents", "skills"];
|
|
751
|
+
function clampCursor(cursor, rowCount) {
|
|
752
|
+
if (rowCount <= 0) {
|
|
753
|
+
return 0;
|
|
754
|
+
}
|
|
755
|
+
return Math.min(Math.max(cursor, 0), rowCount - 1);
|
|
756
|
+
}
|
|
757
|
+
function normalizeQuery(query) {
|
|
758
|
+
return query.trim().toLocaleLowerCase();
|
|
759
|
+
}
|
|
760
|
+
function includesQuery(values, query) {
|
|
761
|
+
const normalizedQuery = normalizeQuery(query);
|
|
762
|
+
if (normalizedQuery.length === 0) {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
return values.some(
|
|
766
|
+
(value) => (value ?? "").toLocaleLowerCase().includes(normalizedQuery)
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
function isRelevantAgentRow(row, selectedAgentId) {
|
|
770
|
+
return row.id === selectedAgentId || row.hasUserOverride === true || row.exists || (row.activationCount ?? 0) > 0 || row.enabledCount > 0 || row.unmanagedCount > 0 || row.issueCount > 0;
|
|
771
|
+
}
|
|
772
|
+
function skillMatchesQuery(row, query) {
|
|
773
|
+
if (row.kind === "issue") {
|
|
774
|
+
return includesQuery(
|
|
775
|
+
[row.id, row.issueCode, row.message, row.path, row.agentId],
|
|
776
|
+
query
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
if (row.kind === "unmanaged") {
|
|
780
|
+
return includesQuery(
|
|
781
|
+
[row.id, row.skillName, row.name, row.path, row.agentId],
|
|
782
|
+
query
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return includesQuery([row.id, row.skillId, row.name, row.path, row.agentId], query);
|
|
786
|
+
}
|
|
787
|
+
function replaceModelSelection(state, selection) {
|
|
788
|
+
return {
|
|
789
|
+
...state,
|
|
790
|
+
model: {
|
|
791
|
+
...state.model,
|
|
792
|
+
...selection
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function clearTransientIntent(state) {
|
|
797
|
+
return {
|
|
798
|
+
...state,
|
|
799
|
+
pendingAction: null,
|
|
800
|
+
pendingCommand: null,
|
|
801
|
+
statusMessage: null
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function isFormModal(modal) {
|
|
805
|
+
return modal?.kind === "add-agent" || modal?.kind === "edit-agent" || modal?.kind === "import";
|
|
806
|
+
}
|
|
807
|
+
function restoreDismissedForm(modal) {
|
|
808
|
+
if (modal.kind !== "confirm-discard-dirty-form") {
|
|
809
|
+
return modal;
|
|
810
|
+
}
|
|
811
|
+
return modal.modal;
|
|
812
|
+
}
|
|
813
|
+
function restoreSearchSelection(state) {
|
|
814
|
+
if (state.search === null) {
|
|
815
|
+
return state;
|
|
816
|
+
}
|
|
817
|
+
const previousSelection = state.search.previousSelection;
|
|
818
|
+
return {
|
|
819
|
+
...state,
|
|
820
|
+
model: {
|
|
821
|
+
...state.model,
|
|
822
|
+
selectedAgentId: previousSelection.selectedAgentId,
|
|
823
|
+
selectedSkillId: previousSelection.selectedSkillId
|
|
824
|
+
},
|
|
825
|
+
agentCursor: previousSelection.agentCursor,
|
|
826
|
+
skillCursor: previousSelection.skillCursor,
|
|
827
|
+
pendingAgentId: previousSelection.pendingAgentId
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function searchHasVisibleResults(state) {
|
|
831
|
+
if (state.search === null) {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
return state.search.panel === "agents" ? getVisibleAgents(state).length > 0 : getVisibleSkills(state).length > 0;
|
|
835
|
+
}
|
|
836
|
+
function selectedAgentIndex(model) {
|
|
837
|
+
if (model.selectedAgentId === null) {
|
|
838
|
+
return 0;
|
|
839
|
+
}
|
|
840
|
+
const index = model.agents.findIndex((row) => row.id === model.selectedAgentId);
|
|
841
|
+
return index < 0 ? 0 : index;
|
|
842
|
+
}
|
|
843
|
+
function selectedSkillIndex(model) {
|
|
844
|
+
if (model.selectedSkillId === null) {
|
|
845
|
+
return 0;
|
|
846
|
+
}
|
|
847
|
+
const index = model.skills.findIndex((row) => row.id === model.selectedSkillId);
|
|
848
|
+
return index < 0 ? 0 : index;
|
|
849
|
+
}
|
|
850
|
+
function selectedAgentSkill(skills, agentId) {
|
|
851
|
+
if (agentId === null) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
return skills.find((row) => row.agentId === agentId) ?? null;
|
|
855
|
+
}
|
|
856
|
+
function selectedAgentRow(state) {
|
|
857
|
+
if (state.model.selectedAgentId === null) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
return state.model.agents.find((row) => row.id === state.model.selectedAgentId) ?? null;
|
|
861
|
+
}
|
|
862
|
+
function moveFocus(focus, direction) {
|
|
863
|
+
const currentIndex = focusOrder.indexOf(focus);
|
|
864
|
+
const nextIndex = (currentIndex + direction + focusOrder.length) % focusOrder.length;
|
|
865
|
+
return focusOrder[nextIndex] ?? "agents";
|
|
866
|
+
}
|
|
867
|
+
function stateWithAgentCursor(state, cursor) {
|
|
868
|
+
const visibleAgents = getVisibleAgents(state);
|
|
869
|
+
const agentCursor = clampCursor(cursor, visibleAgents.length);
|
|
870
|
+
const selectedAgent = visibleAgents[agentCursor] ?? null;
|
|
871
|
+
const selectedSkill = selectedAgentSkill(state.model.skills, selectedAgent?.id ?? null);
|
|
872
|
+
const previousAgentId = state.model.selectedAgentId;
|
|
873
|
+
const selectedAgentId = selectedAgent?.id ?? null;
|
|
874
|
+
const model = {
|
|
875
|
+
...state.model,
|
|
876
|
+
selectedAgentId,
|
|
877
|
+
selectedSkillId: selectedSkill?.id ?? null
|
|
878
|
+
};
|
|
879
|
+
const skillCursor = selectedSkill === null ? 0 : clampCursor(
|
|
880
|
+
getVisibleSkills({ ...state, model }).findIndex(
|
|
881
|
+
(row) => row.id === selectedSkill.id
|
|
882
|
+
),
|
|
883
|
+
getVisibleSkills({ ...state, model }).length
|
|
884
|
+
);
|
|
885
|
+
return {
|
|
886
|
+
...state,
|
|
887
|
+
model,
|
|
888
|
+
agentCursor,
|
|
889
|
+
skillCursor,
|
|
890
|
+
pendingAgentId: selectedAgentId !== null && selectedAgentId !== previousAgentId ? selectedAgentId : state.pendingAgentId
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
function stateWithSkillCursor(state, cursor) {
|
|
894
|
+
const visibleSkills = getVisibleSkills(state);
|
|
895
|
+
const skillCursor = clampCursor(cursor, visibleSkills.length);
|
|
896
|
+
const selectedSkill = visibleSkills[skillCursor] ?? null;
|
|
897
|
+
return replaceModelSelection(
|
|
898
|
+
{
|
|
899
|
+
...state,
|
|
900
|
+
skillCursor
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
904
|
+
selectedSkillId: selectedSkill?.id ?? null
|
|
905
|
+
}
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
function moveCursor(state, cursor) {
|
|
909
|
+
if (state.focus === "agents") {
|
|
910
|
+
return stateWithAgentCursor(state, cursor);
|
|
911
|
+
}
|
|
912
|
+
if (state.focus === "skills") {
|
|
913
|
+
return stateWithSkillCursor(state, cursor);
|
|
914
|
+
}
|
|
915
|
+
return state;
|
|
916
|
+
}
|
|
917
|
+
function isModalBackgroundEvent(event) {
|
|
918
|
+
return event.type === "focus-next" || event.type === "focus-previous" || event.type === "next-row" || event.type === "previous-row" || event.type === "first-row" || event.type === "last-row" || event.type === "open-search" || event.type === "search-query-changed" || event.type === "open-help" || event.type === "open-add-agent" || event.type === "open-edit-agent" || event.type === "open-remove-agent" || event.type === "open-import" || event.type === "open-doctor" || event.type === "open-discard-dirty-form" || event.type === "submit-remove-agent" || event.type === "request-adopt" || event.type === "request-adopt-all" || event.type === "request-remove" || event.type === "request-toggle" || event.type === "request-scan" || event.type === "clear-pending-action";
|
|
919
|
+
}
|
|
920
|
+
function getVisibleAgents(state) {
|
|
921
|
+
if (state.search?.panel !== "agents") {
|
|
922
|
+
return state.model.agents.filter(
|
|
923
|
+
(row) => isRelevantAgentRow(row, state.model.selectedAgentId)
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return state.model.agents.filter(
|
|
927
|
+
(row) => includesQuery([row.id, row.name, row.path, row.discovery], state.search?.query ?? "")
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
function getVisibleSkills(state) {
|
|
931
|
+
if (state.model.selectedAgentId === null) {
|
|
932
|
+
return [];
|
|
933
|
+
}
|
|
934
|
+
const agentSkills = state.model.skills.filter((row) => row.agentId === state.model.selectedAgentId);
|
|
935
|
+
if (state.search?.panel !== "skills") {
|
|
936
|
+
return agentSkills;
|
|
937
|
+
}
|
|
938
|
+
return agentSkills.filter((row) => skillMatchesQuery(row, state.search?.query ?? ""));
|
|
939
|
+
}
|
|
940
|
+
function getSelectedSkill(state) {
|
|
941
|
+
if (state.model.selectedSkillId === null) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
return state.model.skills.find((row) => row.id === state.model.selectedSkillId) ?? null;
|
|
945
|
+
}
|
|
946
|
+
function getAvailableActions(state) {
|
|
947
|
+
const selectedSkill = getSelectedSkill(state);
|
|
948
|
+
const selectedAgent = selectedAgentRow(state);
|
|
949
|
+
const canAcceptActions = state.modal === null && !state.busy;
|
|
950
|
+
const hasFocusedSkill = canAcceptActions && state.focus === "skills";
|
|
951
|
+
const canEditSelectedAgent = selectedAgent?.canEditOverride === true;
|
|
952
|
+
const canRemoveSelectedAgent = selectedAgent?.canRemoveOverride === true;
|
|
953
|
+
return {
|
|
954
|
+
addAgent: canAcceptActions,
|
|
955
|
+
editAgent: canAcceptActions && canEditSelectedAgent,
|
|
956
|
+
removeAgent: canAcceptActions && canRemoveSelectedAgent,
|
|
957
|
+
importSkill: canAcceptActions,
|
|
958
|
+
doctor: canAcceptActions,
|
|
959
|
+
toggle: hasFocusedSkill && (selectedSkill?.kind === "enabled" || selectedSkill?.kind === "disabled"),
|
|
960
|
+
adopt: hasFocusedSkill && selectedSkill?.kind === "unmanaged",
|
|
961
|
+
adoptAll: canAcceptActions && (selectedAgent?.unmanagedCount ?? 0) > 0,
|
|
962
|
+
remove: hasFocusedSkill && selectedSkill?.kind === "disabled",
|
|
963
|
+
scan: canAcceptActions,
|
|
964
|
+
help: canAcceptActions
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function consumeActionIntent(state) {
|
|
968
|
+
return {
|
|
969
|
+
state: {
|
|
970
|
+
...state,
|
|
971
|
+
pendingAction: null
|
|
972
|
+
},
|
|
973
|
+
action: state.pendingAction
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function consumePendingCommandIntent(state) {
|
|
977
|
+
return {
|
|
978
|
+
state: {
|
|
979
|
+
...state,
|
|
980
|
+
pendingCommand: null
|
|
981
|
+
},
|
|
982
|
+
command: state.pendingCommand
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function consumeAgentSelectionIntent(state) {
|
|
986
|
+
return {
|
|
987
|
+
state: {
|
|
988
|
+
...state,
|
|
989
|
+
pendingAgentId: null
|
|
990
|
+
},
|
|
991
|
+
agentId: state.pendingAgentId
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function createInitialTuiState(model) {
|
|
995
|
+
const state = {
|
|
996
|
+
model,
|
|
997
|
+
focus: "agents",
|
|
998
|
+
agentCursor: selectedAgentIndex(model),
|
|
999
|
+
skillCursor: selectedSkillIndex(model),
|
|
1000
|
+
search: null,
|
|
1001
|
+
statusMessage: null,
|
|
1002
|
+
modal: null,
|
|
1003
|
+
busy: false,
|
|
1004
|
+
pendingAction: null,
|
|
1005
|
+
pendingCommand: null,
|
|
1006
|
+
pendingAgentId: null
|
|
1007
|
+
};
|
|
1008
|
+
return {
|
|
1009
|
+
...state,
|
|
1010
|
+
agentCursor: clampCursor(state.agentCursor, getVisibleAgents(state).length),
|
|
1011
|
+
skillCursor: clampCursor(state.skillCursor, getVisibleSkills(state).length)
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function updateTuiState(state, event) {
|
|
1015
|
+
if (state.modal !== null) {
|
|
1016
|
+
if (event.type === "confirm-discard-dirty-form") {
|
|
1017
|
+
if (state.modal.kind !== "confirm-discard-dirty-form") {
|
|
1018
|
+
return state;
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
...state,
|
|
1022
|
+
modal: null,
|
|
1023
|
+
pendingCommand: null
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
if (event.type === "close") {
|
|
1027
|
+
if (state.modal.kind === "confirm-discard-dirty-form") {
|
|
1028
|
+
return {
|
|
1029
|
+
...state,
|
|
1030
|
+
modal: restoreDismissedForm(state.modal)
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
if (isFormModal(state.modal) && state.modal.form.dirty) {
|
|
1034
|
+
return {
|
|
1035
|
+
...state,
|
|
1036
|
+
modal: {
|
|
1037
|
+
kind: "confirm-discard-dirty-form",
|
|
1038
|
+
modal: state.modal
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
...state,
|
|
1044
|
+
modal: null,
|
|
1045
|
+
pendingAction: null,
|
|
1046
|
+
pendingCommand: null
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (event.type === "set-busy") {
|
|
1050
|
+
return {
|
|
1051
|
+
...state,
|
|
1052
|
+
busy: event.busy
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
if (event.type === "set-status") {
|
|
1056
|
+
return {
|
|
1057
|
+
...state,
|
|
1058
|
+
statusMessage: event.message
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
if (state.modal.kind === "doctor") {
|
|
1062
|
+
if (event.type === "doctor-result-loaded") {
|
|
1063
|
+
return {
|
|
1064
|
+
...state,
|
|
1065
|
+
modal: {
|
|
1066
|
+
kind: "doctor",
|
|
1067
|
+
status: "ready",
|
|
1068
|
+
report: event.report
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (event.type === "doctor-result-failed") {
|
|
1073
|
+
return {
|
|
1074
|
+
...state,
|
|
1075
|
+
modal: {
|
|
1076
|
+
kind: "doctor",
|
|
1077
|
+
status: "error",
|
|
1078
|
+
errorMessage: event.errorMessage
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (state.modal.kind === "add-agent") {
|
|
1084
|
+
if (event.type === "add-agent-form-field-changed") {
|
|
1085
|
+
return {
|
|
1086
|
+
...state,
|
|
1087
|
+
modal: {
|
|
1088
|
+
...state.modal,
|
|
1089
|
+
form: updateConfigAddAgentFormField(
|
|
1090
|
+
state.modal.form,
|
|
1091
|
+
event.field,
|
|
1092
|
+
event.value
|
|
1093
|
+
)
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
if (event.type === "submit-add-agent-form") {
|
|
1098
|
+
const error = validateConfigAddAgentForm(state.modal.form);
|
|
1099
|
+
if (error !== null) {
|
|
1100
|
+
return {
|
|
1101
|
+
...state,
|
|
1102
|
+
modal: {
|
|
1103
|
+
...state.modal,
|
|
1104
|
+
form: {
|
|
1105
|
+
...state.modal.form,
|
|
1106
|
+
error
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
...state,
|
|
1113
|
+
pendingCommand: {
|
|
1114
|
+
kind: "config-add-agent",
|
|
1115
|
+
input: buildRunConfigAddAgentOptions(state.modal.form)
|
|
1116
|
+
},
|
|
1117
|
+
modal: {
|
|
1118
|
+
...state.modal,
|
|
1119
|
+
form: {
|
|
1120
|
+
...state.modal.form,
|
|
1121
|
+
error: null
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (state.modal.kind === "edit-agent") {
|
|
1128
|
+
if (event.type === "edit-agent-form-field-changed") {
|
|
1129
|
+
return {
|
|
1130
|
+
...state,
|
|
1131
|
+
modal: {
|
|
1132
|
+
...state.modal,
|
|
1133
|
+
form: updateConfigUpdateAgentFormField(
|
|
1134
|
+
state.modal.form,
|
|
1135
|
+
event.field,
|
|
1136
|
+
event.value
|
|
1137
|
+
)
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (event.type === "submit-edit-agent-form") {
|
|
1142
|
+
const error = validateConfigUpdateAgentForm(state.modal.form);
|
|
1143
|
+
if (error !== null) {
|
|
1144
|
+
return {
|
|
1145
|
+
...state,
|
|
1146
|
+
modal: {
|
|
1147
|
+
...state.modal,
|
|
1148
|
+
form: {
|
|
1149
|
+
...state.modal.form,
|
|
1150
|
+
error
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
...state,
|
|
1157
|
+
pendingCommand: {
|
|
1158
|
+
kind: "config-update-agent",
|
|
1159
|
+
input: buildRunConfigUpdateAgentOptions(state.modal.form, state.modal.agentId)
|
|
1160
|
+
},
|
|
1161
|
+
modal: {
|
|
1162
|
+
...state.modal,
|
|
1163
|
+
form: {
|
|
1164
|
+
...state.modal.form,
|
|
1165
|
+
error: null
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (state.modal.kind === "confirm-remove-agent") {
|
|
1172
|
+
if (event.type === "submit-remove-agent") {
|
|
1173
|
+
return {
|
|
1174
|
+
...state,
|
|
1175
|
+
pendingCommand: {
|
|
1176
|
+
kind: "config-remove-agent",
|
|
1177
|
+
input: { id: state.modal.agentId }
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (state.modal.kind === "import") {
|
|
1183
|
+
if (event.type === "import-form-field-changed") {
|
|
1184
|
+
return {
|
|
1185
|
+
...state,
|
|
1186
|
+
modal: {
|
|
1187
|
+
...state.modal,
|
|
1188
|
+
form: updateImportSkillFormField(
|
|
1189
|
+
state.modal.form,
|
|
1190
|
+
event.field,
|
|
1191
|
+
event.value
|
|
1192
|
+
)
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
if (event.type === "submit-import-form") {
|
|
1197
|
+
const error = validateImportSkillForm(state.modal.form);
|
|
1198
|
+
if (error !== null) {
|
|
1199
|
+
return {
|
|
1200
|
+
...state,
|
|
1201
|
+
modal: {
|
|
1202
|
+
...state.modal,
|
|
1203
|
+
form: {
|
|
1204
|
+
...state.modal.form,
|
|
1205
|
+
error
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
...state,
|
|
1212
|
+
pendingCommand: {
|
|
1213
|
+
kind: "import-skill",
|
|
1214
|
+
input: buildRunImportOptions(state.modal.form)
|
|
1215
|
+
},
|
|
1216
|
+
modal: {
|
|
1217
|
+
...state.modal,
|
|
1218
|
+
form: {
|
|
1219
|
+
...state.modal.form,
|
|
1220
|
+
error: null
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (isModalBackgroundEvent(event)) {
|
|
1227
|
+
return state;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (state.busy) {
|
|
1231
|
+
if (event.type === "set-busy") {
|
|
1232
|
+
return {
|
|
1233
|
+
...state,
|
|
1234
|
+
busy: event.busy
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
if (event.type === "set-status") {
|
|
1238
|
+
return {
|
|
1239
|
+
...state,
|
|
1240
|
+
statusMessage: event.message
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
if (event.type === "request-toggle" || event.type === "request-adopt" || event.type === "request-remove" || event.type === "request-scan" || event.type === "open-help" || event.type === "open-add-agent" || event.type === "open-edit-agent" || event.type === "open-remove-agent" || event.type === "open-import" || event.type === "open-doctor" || event.type === "open-discard-dirty-form") {
|
|
1244
|
+
return {
|
|
1245
|
+
...state,
|
|
1246
|
+
pendingAction: null
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const readyState = clearTransientIntent(state);
|
|
1251
|
+
if (event.type === "focus-next") {
|
|
1252
|
+
return {
|
|
1253
|
+
...readyState,
|
|
1254
|
+
focus: moveFocus(state.focus, 1)
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
if (event.type === "focus-previous") {
|
|
1258
|
+
return {
|
|
1259
|
+
...readyState,
|
|
1260
|
+
focus: moveFocus(state.focus, -1)
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
if (event.type === "next-row") {
|
|
1264
|
+
return moveCursor(
|
|
1265
|
+
readyState,
|
|
1266
|
+
state.focus === "agents" ? state.agentCursor + 1 : state.skillCursor + 1
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
if (event.type === "previous-row") {
|
|
1270
|
+
return moveCursor(
|
|
1271
|
+
readyState,
|
|
1272
|
+
state.focus === "agents" ? state.agentCursor - 1 : state.skillCursor - 1
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
if (event.type === "first-row") {
|
|
1276
|
+
return moveCursor(readyState, 0);
|
|
1277
|
+
}
|
|
1278
|
+
if (event.type === "last-row") {
|
|
1279
|
+
const rowCount = state.focus === "agents" ? getVisibleAgents(state).length : getVisibleSkills(state).length;
|
|
1280
|
+
return moveCursor(readyState, rowCount - 1);
|
|
1281
|
+
}
|
|
1282
|
+
if (event.type === "open-search") {
|
|
1283
|
+
if (state.focus !== "agents" && state.focus !== "skills") {
|
|
1284
|
+
return {
|
|
1285
|
+
...readyState,
|
|
1286
|
+
statusMessage: "Search is available for agents and skills"
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
return {
|
|
1290
|
+
...readyState,
|
|
1291
|
+
search: {
|
|
1292
|
+
panel: state.focus,
|
|
1293
|
+
query: "",
|
|
1294
|
+
previousSelection: {
|
|
1295
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
1296
|
+
selectedSkillId: state.model.selectedSkillId,
|
|
1297
|
+
agentCursor: state.agentCursor,
|
|
1298
|
+
skillCursor: state.skillCursor,
|
|
1299
|
+
pendingAgentId: state.pendingAgentId
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
if (event.type === "search-query-changed") {
|
|
1305
|
+
if (state.search === null) {
|
|
1306
|
+
return readyState;
|
|
1307
|
+
}
|
|
1308
|
+
const searchedState = {
|
|
1309
|
+
...readyState,
|
|
1310
|
+
search: {
|
|
1311
|
+
...state.search,
|
|
1312
|
+
query: event.query
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
const rowCount = searchedState.search.panel === "agents" ? getVisibleAgents(searchedState).length : getVisibleSkills(searchedState).length;
|
|
1316
|
+
return searchedState.search.panel === "agents" ? stateWithAgentCursor(searchedState, clampCursor(searchedState.agentCursor, rowCount)) : stateWithSkillCursor(searchedState, clampCursor(searchedState.skillCursor, rowCount));
|
|
1317
|
+
}
|
|
1318
|
+
if (event.type === "close") {
|
|
1319
|
+
if (state.search !== null) {
|
|
1320
|
+
return {
|
|
1321
|
+
...restoreSearchSelection(readyState),
|
|
1322
|
+
search: null
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
if (state.modal !== null) {
|
|
1326
|
+
return {
|
|
1327
|
+
...readyState,
|
|
1328
|
+
modal: null
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
return readyState;
|
|
1332
|
+
}
|
|
1333
|
+
if (event.type === "submit-search") {
|
|
1334
|
+
if (state.search !== null) {
|
|
1335
|
+
if (!searchHasVisibleResults(state)) {
|
|
1336
|
+
return {
|
|
1337
|
+
...restoreSearchSelection(readyState),
|
|
1338
|
+
search: null
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
return {
|
|
1342
|
+
...readyState,
|
|
1343
|
+
search: null
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
return readyState;
|
|
1347
|
+
}
|
|
1348
|
+
if (event.type === "open-help") {
|
|
1349
|
+
return {
|
|
1350
|
+
...readyState,
|
|
1351
|
+
modal: { kind: "help" }
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
if (event.type === "open-add-agent") {
|
|
1355
|
+
return {
|
|
1356
|
+
...readyState,
|
|
1357
|
+
modal: {
|
|
1358
|
+
kind: "add-agent",
|
|
1359
|
+
form: createConfigAddAgentForm()
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
if (event.type === "open-edit-agent") {
|
|
1364
|
+
const selectedAgent = selectedAgentRow(state);
|
|
1365
|
+
if (selectedAgent?.canEditOverride !== true) {
|
|
1366
|
+
return {
|
|
1367
|
+
...readyState,
|
|
1368
|
+
statusMessage: "Select an agent override first"
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return {
|
|
1372
|
+
...readyState,
|
|
1373
|
+
modal: {
|
|
1374
|
+
kind: "edit-agent",
|
|
1375
|
+
agentId: selectedAgent.id,
|
|
1376
|
+
form: createConfigUpdateAgentFormFromSeed({
|
|
1377
|
+
id: selectedAgent.id,
|
|
1378
|
+
stableName: selectedAgent.overrideStableName,
|
|
1379
|
+
homeRelativeRootPath: selectedAgent.overrideHomeRelativeRootPath,
|
|
1380
|
+
skillsDirectoryPath: selectedAgent.overrideSkillsDirectoryPath,
|
|
1381
|
+
supportedPlatforms: selectedAgent.overrideSupportedPlatforms,
|
|
1382
|
+
overrideEnabledByDefault: selectedAgent.overrideEnabledByDefault
|
|
1383
|
+
})
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (event.type === "open-remove-agent") {
|
|
1388
|
+
const selectedAgent = selectedAgentRow(state);
|
|
1389
|
+
if (selectedAgent?.canRemoveOverride !== true) {
|
|
1390
|
+
return {
|
|
1391
|
+
...readyState,
|
|
1392
|
+
statusMessage: "Select an agent override first"
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
...readyState,
|
|
1397
|
+
modal: { kind: "confirm-remove-agent", agentId: selectedAgent.id }
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
if (event.type === "open-import") {
|
|
1401
|
+
return {
|
|
1402
|
+
...readyState,
|
|
1403
|
+
modal: {
|
|
1404
|
+
kind: "import",
|
|
1405
|
+
form: createImportSkillForm()
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
if (event.type === "open-doctor") {
|
|
1410
|
+
return {
|
|
1411
|
+
...readyState,
|
|
1412
|
+
modal: {
|
|
1413
|
+
kind: "doctor",
|
|
1414
|
+
status: "loading"
|
|
1415
|
+
},
|
|
1416
|
+
pendingCommand: {
|
|
1417
|
+
kind: "doctor"
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
if (event.type === "open-discard-dirty-form") {
|
|
1422
|
+
return {
|
|
1423
|
+
...readyState,
|
|
1424
|
+
modal: {
|
|
1425
|
+
kind: "confirm-discard-dirty-form",
|
|
1426
|
+
modal: {
|
|
1427
|
+
kind: "add-agent",
|
|
1428
|
+
form: createConfigAddAgentForm()
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
if (event.type === "request-adopt") {
|
|
1434
|
+
if (state.focus !== "skills") {
|
|
1435
|
+
return readyState;
|
|
1436
|
+
}
|
|
1437
|
+
const selectedSkill = getSelectedSkill(state);
|
|
1438
|
+
if (selectedSkill?.kind !== "unmanaged") {
|
|
1439
|
+
return {
|
|
1440
|
+
...readyState,
|
|
1441
|
+
statusMessage: "Adopt is only available for unmanaged rows"
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
return {
|
|
1445
|
+
...readyState,
|
|
1446
|
+
modal: {
|
|
1447
|
+
kind: "confirm-adopt",
|
|
1448
|
+
skillId: selectedSkill.skillName,
|
|
1449
|
+
agentId: selectedSkill.agentId
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
if (event.type === "request-adopt-all") {
|
|
1454
|
+
const selectedAgent = selectedAgentRow(state);
|
|
1455
|
+
if (selectedAgent === null) {
|
|
1456
|
+
return {
|
|
1457
|
+
...readyState,
|
|
1458
|
+
statusMessage: "Select an agent first"
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
if (selectedAgent.unmanagedCount <= 0) {
|
|
1462
|
+
return {
|
|
1463
|
+
...readyState,
|
|
1464
|
+
statusMessage: "No unmanaged skills to adopt for this agent"
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
return {
|
|
1468
|
+
...readyState,
|
|
1469
|
+
modal: {
|
|
1470
|
+
kind: "confirm-adopt-all",
|
|
1471
|
+
agentId: selectedAgent.id,
|
|
1472
|
+
unmanagedCount: selectedAgent.unmanagedCount
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
if (event.type === "request-remove") {
|
|
1477
|
+
if (state.focus !== "skills") {
|
|
1478
|
+
return readyState;
|
|
1479
|
+
}
|
|
1480
|
+
const selectedSkill = getSelectedSkill(state);
|
|
1481
|
+
if (selectedSkill?.kind !== "disabled") {
|
|
1482
|
+
return {
|
|
1483
|
+
...readyState,
|
|
1484
|
+
statusMessage: selectedSkill?.kind === "enabled" ? "Disable this skill before removing it" : "Remove is only available for disabled rows"
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
...readyState,
|
|
1489
|
+
modal: {
|
|
1490
|
+
kind: "confirm-remove",
|
|
1491
|
+
skillId: selectedSkill.skillId
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
if (event.type === "request-toggle") {
|
|
1496
|
+
if (state.focus !== "skills") {
|
|
1497
|
+
return readyState;
|
|
1498
|
+
}
|
|
1499
|
+
const selectedSkill = getSelectedSkill(state);
|
|
1500
|
+
if (selectedSkill?.kind !== "enabled" && selectedSkill?.kind !== "disabled") {
|
|
1501
|
+
return {
|
|
1502
|
+
...readyState,
|
|
1503
|
+
statusMessage: "Toggle is only available for managed rows"
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
...readyState,
|
|
1508
|
+
pendingAction: "toggle"
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
if (event.type === "request-scan") {
|
|
1512
|
+
return {
|
|
1513
|
+
...readyState,
|
|
1514
|
+
pendingAction: "scan"
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
if (event.type === "set-busy") {
|
|
1518
|
+
return {
|
|
1519
|
+
...readyState,
|
|
1520
|
+
busy: event.busy
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
if (event.type === "set-status") {
|
|
1524
|
+
return {
|
|
1525
|
+
...readyState,
|
|
1526
|
+
statusMessage: event.message
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
return readyState;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/tui/components/AgentList.tsx
|
|
1533
|
+
import { Box, Text } from "ink";
|
|
1534
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1535
|
+
function statusMarker(agent) {
|
|
1536
|
+
if (!agent.supported) {
|
|
1537
|
+
return "!";
|
|
1538
|
+
}
|
|
1539
|
+
if (!agent.exists) {
|
|
1540
|
+
return "?";
|
|
1541
|
+
}
|
|
1542
|
+
return "*";
|
|
1543
|
+
}
|
|
1544
|
+
function statusColor(agent) {
|
|
1545
|
+
if (!agent.supported) {
|
|
1546
|
+
return "red";
|
|
1547
|
+
}
|
|
1548
|
+
if (!agent.exists || agent.issueCount > 0) {
|
|
1549
|
+
return "yellow";
|
|
1550
|
+
}
|
|
1551
|
+
return "green";
|
|
1552
|
+
}
|
|
1553
|
+
function AgentList({
|
|
1554
|
+
agents,
|
|
1555
|
+
selectedAgentId,
|
|
1556
|
+
focused,
|
|
1557
|
+
searchQuery,
|
|
1558
|
+
width = 24,
|
|
1559
|
+
height = 18
|
|
1560
|
+
}) {
|
|
1561
|
+
const emptyMessage = searchQuery !== void 0 && searchQuery.trim().length > 0 ? "No matching agents" : "No agents found";
|
|
1562
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height, children: [
|
|
1563
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: focused ? "cyan" : void 0, children: "Agents" }),
|
|
1564
|
+
agents.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: emptyMessage }) : agents.map((agent) => {
|
|
1565
|
+
const selected = agent.id === selectedAgentId;
|
|
1566
|
+
const selectionPrefix = selected ? ">" : " ";
|
|
1567
|
+
return /* @__PURE__ */ jsxs(Text, { inverse: selected, children: [
|
|
1568
|
+
/* @__PURE__ */ jsx(Text, { color: statusColor(agent), children: statusMarker(agent) }),
|
|
1569
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1570
|
+
selectionPrefix,
|
|
1571
|
+
" ",
|
|
1572
|
+
agent.name
|
|
1573
|
+
] })
|
|
1574
|
+
] }, agent.id);
|
|
1575
|
+
})
|
|
1576
|
+
] });
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// src/tui/components/ConfirmDialog.tsx
|
|
1580
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1581
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1582
|
+
var confirmDialogHeight = 4;
|
|
1583
|
+
function confirmationText(modal) {
|
|
1584
|
+
if (modal.kind === "confirm-adopt") {
|
|
1585
|
+
return `Adopt ${modal.skillId} for ${modal.agentId}?`;
|
|
1586
|
+
}
|
|
1587
|
+
if (modal.kind === "confirm-adopt-all") {
|
|
1588
|
+
return `Adopt all unmanaged skills for ${modal.agentId}?`;
|
|
1589
|
+
}
|
|
1590
|
+
if (modal.kind === "confirm-remove-agent") {
|
|
1591
|
+
return `Remove agent override for ${modal.agentId}?`;
|
|
1592
|
+
}
|
|
1593
|
+
if (modal.kind === "confirm-discard-dirty-form") {
|
|
1594
|
+
return "Discard unsaved changes?";
|
|
1595
|
+
}
|
|
1596
|
+
return `Remove ${modal.skillId} from SkillMux?`;
|
|
1597
|
+
}
|
|
1598
|
+
function confirmationDetails(modal) {
|
|
1599
|
+
if (modal.kind !== "confirm-adopt-all") {
|
|
1600
|
+
if (modal.kind === "confirm-remove-agent") {
|
|
1601
|
+
return "This will remove the selected agent override from SkillMux.";
|
|
1602
|
+
}
|
|
1603
|
+
if (modal.kind === "confirm-discard-dirty-form") {
|
|
1604
|
+
return "This will close the form and discard the current changes.";
|
|
1605
|
+
}
|
|
1606
|
+
return null;
|
|
1607
|
+
}
|
|
1608
|
+
return `${modal.unmanagedCount} unmanaged skills will be moved under SkillMux management.`;
|
|
1609
|
+
}
|
|
1610
|
+
function ConfirmDialog({ modal }) {
|
|
1611
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: confirmDialogHeight, children: [
|
|
1612
|
+
/* @__PURE__ */ jsx2(
|
|
1613
|
+
Text2,
|
|
1614
|
+
{
|
|
1615
|
+
bold: true,
|
|
1616
|
+
color: modal.kind === "confirm-remove" || modal.kind === "confirm-remove-agent" ? "yellow" : "cyan",
|
|
1617
|
+
children: "Confirm"
|
|
1618
|
+
}
|
|
1619
|
+
),
|
|
1620
|
+
/* @__PURE__ */ jsx2(Text2, { children: confirmationText(modal) }),
|
|
1621
|
+
confirmationDetails(modal) === null ? null : /* @__PURE__ */ jsx2(Text2, { children: confirmationDetails(modal) }),
|
|
1622
|
+
/* @__PURE__ */ jsx2(Text2, { children: "[y] confirm [Esc] cancel" })
|
|
1623
|
+
] });
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/tui/components/DoctorDialog.tsx
|
|
1627
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1628
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1629
|
+
function issueLabel(issue) {
|
|
1630
|
+
return `${issue.severity} ${issue.code}`;
|
|
1631
|
+
}
|
|
1632
|
+
function issuePath(issue) {
|
|
1633
|
+
return issue.path ?? null;
|
|
1634
|
+
}
|
|
1635
|
+
function visibleIssueCount(height) {
|
|
1636
|
+
return Math.max(height - 5, 1);
|
|
1637
|
+
}
|
|
1638
|
+
function DoctorDialog({
|
|
1639
|
+
modal,
|
|
1640
|
+
scrollOffset = 0,
|
|
1641
|
+
width = 72,
|
|
1642
|
+
height = 14
|
|
1643
|
+
}) {
|
|
1644
|
+
if (modal.status === "loading") {
|
|
1645
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width, height, children: [
|
|
1646
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Doctor" }),
|
|
1647
|
+
/* @__PURE__ */ jsx3(Text3, { children: "Loading doctor diagnostics..." }),
|
|
1648
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[Esc] close" })
|
|
1649
|
+
] });
|
|
1650
|
+
}
|
|
1651
|
+
if (modal.status === "error") {
|
|
1652
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width, height, children: [
|
|
1653
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Doctor" }),
|
|
1654
|
+
/* @__PURE__ */ jsx3(Text3, { color: "red", children: modal.errorMessage }),
|
|
1655
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[Esc] close" })
|
|
1656
|
+
] });
|
|
1657
|
+
}
|
|
1658
|
+
const issues = modal.report.issues;
|
|
1659
|
+
const maxIssues = visibleIssueCount(height);
|
|
1660
|
+
const maxOffset = Math.max(issues.length - maxIssues, 0);
|
|
1661
|
+
const clampedOffset = Math.min(Math.max(scrollOffset, 0), maxOffset);
|
|
1662
|
+
const visibleIssues = issues.slice(clampedOffset, clampedOffset + maxIssues);
|
|
1663
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width, height, children: [
|
|
1664
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Doctor" }),
|
|
1665
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: issues.length === 0 ? "No doctor issues found." : `${issues.length} issue(s) found` }),
|
|
1666
|
+
issues.length === 0 ? null : /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1667
|
+
visibleIssues.map((issue) => /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1668
|
+
/* @__PURE__ */ jsx3(Text3, { color: issue.severity === "error" ? "red" : "yellow", children: "! " }),
|
|
1669
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1670
|
+
issueLabel(issue),
|
|
1671
|
+
" - ",
|
|
1672
|
+
issue.message,
|
|
1673
|
+
issuePath(issue) === null ? "" : ` - ${issuePath(issue)}`
|
|
1674
|
+
] })
|
|
1675
|
+
] }, `${issue.code}:${issue.path ?? issue.message}`)),
|
|
1676
|
+
issues.length > visibleIssues.length ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1677
|
+
"Showing ",
|
|
1678
|
+
clampedOffset + 1,
|
|
1679
|
+
"-",
|
|
1680
|
+
clampedOffset + visibleIssues.length,
|
|
1681
|
+
" of ",
|
|
1682
|
+
issues.length
|
|
1683
|
+
] }) : null
|
|
1684
|
+
] }),
|
|
1685
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[Up/Down] scroll [Esc] close" })
|
|
1686
|
+
] });
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// src/tui/components/DetailPane.tsx
|
|
1690
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1691
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1692
|
+
function compactPath(value, maxLength) {
|
|
1693
|
+
if (value.length <= maxLength) {
|
|
1694
|
+
return value;
|
|
1695
|
+
}
|
|
1696
|
+
const separator = value.includes("\\") ? "\\" : "/";
|
|
1697
|
+
const parts = value.split(/[\\/]+/).filter((part) => part.length > 0);
|
|
1698
|
+
let suffix = parts.at(-1) ?? value;
|
|
1699
|
+
for (let index = parts.length - 2; index >= 0; index -= 1) {
|
|
1700
|
+
const candidate = `${parts[index]}${separator}${suffix}`;
|
|
1701
|
+
if (`...${separator}${candidate}`.length > maxLength) {
|
|
1702
|
+
break;
|
|
1703
|
+
}
|
|
1704
|
+
suffix = candidate;
|
|
1705
|
+
}
|
|
1706
|
+
const shortened = `...${separator}${suffix}`;
|
|
1707
|
+
if (shortened.length <= maxLength) {
|
|
1708
|
+
return shortened;
|
|
1709
|
+
}
|
|
1710
|
+
if (maxLength <= 3) {
|
|
1711
|
+
return ".".repeat(maxLength);
|
|
1712
|
+
}
|
|
1713
|
+
return `...${suffix.slice(-(maxLength - 3))}`;
|
|
1714
|
+
}
|
|
1715
|
+
function detailLines(skill) {
|
|
1716
|
+
if (skill.kind === "enabled") {
|
|
1717
|
+
return [
|
|
1718
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
1719
|
+
{ label: "Status", value: "enabled", compact: false },
|
|
1720
|
+
{ label: "Store", value: skill.path, compact: true },
|
|
1721
|
+
{ label: "Link", value: skill.activationLinkPath, compact: true }
|
|
1722
|
+
];
|
|
1723
|
+
}
|
|
1724
|
+
if (skill.kind === "disabled") {
|
|
1725
|
+
return [
|
|
1726
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
1727
|
+
{ label: "Status", value: "disabled", compact: false },
|
|
1728
|
+
{ label: "Store", value: skill.path, compact: true },
|
|
1729
|
+
{
|
|
1730
|
+
label: "Link",
|
|
1731
|
+
value: skill.activationLinkPath ?? "not linked",
|
|
1732
|
+
compact: skill.activationLinkPath !== null
|
|
1733
|
+
}
|
|
1734
|
+
];
|
|
1735
|
+
}
|
|
1736
|
+
if (skill.kind === "unmanaged") {
|
|
1737
|
+
return [
|
|
1738
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
1739
|
+
{ label: "Status", value: "unmanaged", compact: false },
|
|
1740
|
+
{ label: "Entry", value: skill.entryKind, compact: false },
|
|
1741
|
+
{ label: "Path", value: skill.path, compact: true }
|
|
1742
|
+
];
|
|
1743
|
+
}
|
|
1744
|
+
return [
|
|
1745
|
+
{ label: "Status", value: "issue", compact: false },
|
|
1746
|
+
{ label: "Code", value: skill.issueCode, compact: false },
|
|
1747
|
+
{ label: "Severity", value: skill.severity, compact: false },
|
|
1748
|
+
{ label: "Message", value: skill.message, compact: false },
|
|
1749
|
+
{ label: "Path", value: skill.path ?? "none", compact: skill.path !== null }
|
|
1750
|
+
];
|
|
1751
|
+
}
|
|
1752
|
+
function DetailPane({
|
|
1753
|
+
selectedAgent,
|
|
1754
|
+
selectedSkill,
|
|
1755
|
+
focused: _focused,
|
|
1756
|
+
loadingAgentName = null,
|
|
1757
|
+
width = 28,
|
|
1758
|
+
height = 18
|
|
1759
|
+
}) {
|
|
1760
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, height, children: [
|
|
1761
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Detail" }),
|
|
1762
|
+
selectedAgent === null ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select an agent" }) : /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1763
|
+
"Agent: ",
|
|
1764
|
+
selectedAgent.name
|
|
1765
|
+
] }),
|
|
1766
|
+
selectedSkill === null ? loadingAgentName !== null ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1767
|
+
"Loading details for ",
|
|
1768
|
+
loadingAgentName,
|
|
1769
|
+
"..."
|
|
1770
|
+
] }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select a skill row" }) : detailLines(selectedSkill).map(({ label, value, compact }) => {
|
|
1771
|
+
const valueWidth = Math.max(width - (label.length + 2), 8);
|
|
1772
|
+
const renderedValue = compact ? compactPath(value, valueWidth) : value;
|
|
1773
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1774
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1775
|
+
label,
|
|
1776
|
+
": "
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsx4(Text4, { children: renderedValue })
|
|
1779
|
+
] }, label);
|
|
1780
|
+
})
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/tui/components/Footer.tsx
|
|
1785
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1786
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1787
|
+
var agentLegend = "Agent icons: * ready yellow * issues ? missing ! unsupported";
|
|
1788
|
+
var skillLegend = "Skill markers: \u25CF enabled \u25CB disabled ? unmanaged ! issue";
|
|
1789
|
+
function Footer({ actions, search }) {
|
|
1790
|
+
const shortcuts = [
|
|
1791
|
+
actions.addAgent ? "[n]add agent" : null,
|
|
1792
|
+
actions.editAgent ? "[e]edit agent" : null,
|
|
1793
|
+
actions.removeAgent ? "[X]remove agent" : null,
|
|
1794
|
+
actions.importSkill ? "[i]import" : null,
|
|
1795
|
+
actions.doctor ? "[d]doctor" : null,
|
|
1796
|
+
"[Left/Right]focus",
|
|
1797
|
+
actions.toggle ? "[Space]toggle" : null,
|
|
1798
|
+
actions.adopt ? "[a]adopt" : null,
|
|
1799
|
+
actions.adoptAll ? "[Shift+A]adopt all" : null,
|
|
1800
|
+
actions.remove ? "[r]remove" : null,
|
|
1801
|
+
actions.scan ? "[s]scan" : null,
|
|
1802
|
+
actions.help ? "[?]help" : null,
|
|
1803
|
+
"[q]quit"
|
|
1804
|
+
].filter((shortcut) => shortcut !== null);
|
|
1805
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", height: 3, children: search === null ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1806
|
+
/* @__PURE__ */ jsx5(Text5, { children: shortcuts.join(" ") }),
|
|
1807
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: agentLegend }),
|
|
1808
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: skillLegend })
|
|
1809
|
+
] }) : /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1810
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "/" }),
|
|
1811
|
+
/* @__PURE__ */ jsx5(Text5, { children: search.query }),
|
|
1812
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " [Enter]keep [Esc]cancel" })
|
|
1813
|
+
] }) });
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/tui/components/HelpOverlay.tsx
|
|
1817
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1818
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1819
|
+
function HelpOverlay() {
|
|
1820
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: 8, children: [
|
|
1821
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Help" }),
|
|
1822
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1823
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Navigation" }),
|
|
1824
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": Left/Right switch panels, j/k or Up/Down move, g/G jump." })
|
|
1825
|
+
] }),
|
|
1826
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1827
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Actions" }),
|
|
1828
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": Space toggles, a adopts, Shift+A current-agent bulk adopt, r removes, s scans, n add agent, e edit selected override, X remove selected override, i import, d doctor." })
|
|
1829
|
+
] }),
|
|
1830
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1831
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Search" }),
|
|
1832
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": / filters the focused list, Enter keeps the result, Esc cancels." })
|
|
1833
|
+
] }),
|
|
1834
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1835
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Agent icons" }),
|
|
1836
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": * ready, yellow * issues, ? missing, ! unsupported." })
|
|
1837
|
+
] }),
|
|
1838
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1839
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Skill markers" }),
|
|
1840
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": \u25CF enabled, \u25CB disabled, ? unmanaged, ! issue." })
|
|
1841
|
+
] }),
|
|
1842
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1843
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Safety" }),
|
|
1844
|
+
/* @__PURE__ */ jsx6(Text6, { children: ": Toggle, adopt, remove, and scan can update SkillMux state and agent links." })
|
|
1845
|
+
] })
|
|
1846
|
+
] });
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// src/tui/components/FormDialog.tsx
|
|
1850
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1851
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1852
|
+
var platformOptions = ["win32", "linux", "darwin"];
|
|
1853
|
+
function checkbox(value) {
|
|
1854
|
+
return value ? "[x]" : "[ ]";
|
|
1855
|
+
}
|
|
1856
|
+
function renderTextField(label, value, active) {
|
|
1857
|
+
return /* @__PURE__ */ jsxs7(Text7, { inverse: active, children: [
|
|
1858
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, children: [
|
|
1859
|
+
label,
|
|
1860
|
+
": "
|
|
1861
|
+
] }),
|
|
1862
|
+
/* @__PURE__ */ jsx7(Text7, { children: value.length > 0 ? value : " " })
|
|
1863
|
+
] }, label);
|
|
1864
|
+
}
|
|
1865
|
+
function renderBooleanField(label, value, active) {
|
|
1866
|
+
return /* @__PURE__ */ jsxs7(Text7, { inverse: active, children: [
|
|
1867
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, children: [
|
|
1868
|
+
label,
|
|
1869
|
+
": "
|
|
1870
|
+
] }),
|
|
1871
|
+
/* @__PURE__ */ jsx7(Text7, { children: checkbox(value) })
|
|
1872
|
+
] }, label);
|
|
1873
|
+
}
|
|
1874
|
+
function renderPlatformField(selectedPlatforms, activePlatformIndex, active) {
|
|
1875
|
+
return platformOptions.map((platform, index) => {
|
|
1876
|
+
const selected = selectedPlatforms.includes(platform);
|
|
1877
|
+
const isCurrent = active && index === activePlatformIndex;
|
|
1878
|
+
return /* @__PURE__ */ jsxs7(Text7, { inverse: isCurrent, children: [
|
|
1879
|
+
/* @__PURE__ */ jsx7(Text7, { children: isCurrent ? "> " : " " }),
|
|
1880
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1881
|
+
checkbox(selected),
|
|
1882
|
+
" "
|
|
1883
|
+
] }),
|
|
1884
|
+
/* @__PURE__ */ jsx7(Text7, { children: platform })
|
|
1885
|
+
] }, platform);
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
function FormDialog({
|
|
1889
|
+
modal,
|
|
1890
|
+
fieldIndex = 0,
|
|
1891
|
+
platformIndex = 0,
|
|
1892
|
+
width = 72,
|
|
1893
|
+
height = 14
|
|
1894
|
+
}) {
|
|
1895
|
+
const activeField = fieldIndex;
|
|
1896
|
+
const submitFieldIndex = modal.kind === "import" ? 2 : 6;
|
|
1897
|
+
if (modal.kind === "import") {
|
|
1898
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width, height, children: [
|
|
1899
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: "Import skill" }),
|
|
1900
|
+
modal.form.error === null ? null : /* @__PURE__ */ jsx7(Text7, { color: "red", children: modal.form.error }),
|
|
1901
|
+
renderTextField(
|
|
1902
|
+
"Source path",
|
|
1903
|
+
modal.form.values.sourcePath,
|
|
1904
|
+
activeField === 0
|
|
1905
|
+
),
|
|
1906
|
+
renderTextField(
|
|
1907
|
+
"Skill name",
|
|
1908
|
+
modal.form.values.skillName,
|
|
1909
|
+
activeField === 1
|
|
1910
|
+
),
|
|
1911
|
+
/* @__PURE__ */ jsx7(Text7, { inverse: activeField === submitFieldIndex, children: "Submit" }),
|
|
1912
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[Up/Down] move [Enter] submit selected row [Esc] cancel" })
|
|
1913
|
+
] });
|
|
1914
|
+
}
|
|
1915
|
+
const title = modal.kind === "add-agent" ? "Add agent" : `Edit agent ${modal.agentId}`;
|
|
1916
|
+
const fields = modal.kind === "add-agent" ? [
|
|
1917
|
+
renderTextField("Agent id", modal.form.values.id, activeField === 0),
|
|
1918
|
+
renderTextField("Root path", modal.form.values.root, activeField === 1),
|
|
1919
|
+
renderTextField("Skills path", modal.form.values.skills, activeField === 2),
|
|
1920
|
+
renderTextField("Display name", modal.form.values.name, activeField === 3)
|
|
1921
|
+
] : [
|
|
1922
|
+
renderTextField("Root path", modal.form.values.root, activeField === 0),
|
|
1923
|
+
renderTextField("Skills path", modal.form.values.skills, activeField === 1),
|
|
1924
|
+
renderTextField("Display name", modal.form.values.name, activeField === 2)
|
|
1925
|
+
];
|
|
1926
|
+
const platformFieldIndex = modal.kind === "add-agent" ? 4 : 3;
|
|
1927
|
+
const booleanFieldIndex = modal.kind === "add-agent" ? 5 : 4;
|
|
1928
|
+
const platformLines = renderPlatformField(
|
|
1929
|
+
modal.form.values.platforms,
|
|
1930
|
+
platformIndex,
|
|
1931
|
+
activeField === platformFieldIndex
|
|
1932
|
+
);
|
|
1933
|
+
const booleanLabel = modal.kind === "add-agent" ? "Disabled by default" : "Enabled by default";
|
|
1934
|
+
const booleanValue = modal.kind === "add-agent" ? modal.form.values.disabledByDefault : modal.form.values.enabledByDefault;
|
|
1935
|
+
const secondaryBooleanLabel = modal.kind === "add-agent" ? null : "Disabled by default";
|
|
1936
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width, height, children: [
|
|
1937
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: title }),
|
|
1938
|
+
modal.form.error === null ? null : /* @__PURE__ */ jsx7(Text7, { color: "red", children: modal.form.error }),
|
|
1939
|
+
fields,
|
|
1940
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, inverse: activeField === platformFieldIndex, children: "Platforms" }),
|
|
1941
|
+
platformLines,
|
|
1942
|
+
renderBooleanField(
|
|
1943
|
+
booleanLabel,
|
|
1944
|
+
booleanValue,
|
|
1945
|
+
activeField === booleanFieldIndex
|
|
1946
|
+
),
|
|
1947
|
+
secondaryBooleanLabel === null ? null : renderBooleanField(
|
|
1948
|
+
secondaryBooleanLabel,
|
|
1949
|
+
modal.form.values.disabledByDefault,
|
|
1950
|
+
activeField === booleanFieldIndex + 1
|
|
1951
|
+
),
|
|
1952
|
+
/* @__PURE__ */ jsx7(Text7, { inverse: activeField === submitFieldIndex, children: "Submit" }),
|
|
1953
|
+
modal.kind === "edit-agent" ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Leaving both defaults unchecked preserves the current setting." }) : null,
|
|
1954
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "[Up/Down] move [Enter] submit selected row [Esc] cancel" })
|
|
1955
|
+
] });
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/tui/components/SkillList.tsx
|
|
1959
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
1960
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1961
|
+
function markerColor(skill) {
|
|
1962
|
+
if (skill.kind === "enabled") {
|
|
1963
|
+
return "green";
|
|
1964
|
+
}
|
|
1965
|
+
if (skill.kind === "issue") {
|
|
1966
|
+
return skill.severity === "error" ? "red" : "yellow";
|
|
1967
|
+
}
|
|
1968
|
+
if (skill.kind === "unmanaged") {
|
|
1969
|
+
return "yellow";
|
|
1970
|
+
}
|
|
1971
|
+
return "gray";
|
|
1972
|
+
}
|
|
1973
|
+
function skillLabel(skill) {
|
|
1974
|
+
if (skill.kind === "issue") {
|
|
1975
|
+
return skill.issueCode;
|
|
1976
|
+
}
|
|
1977
|
+
return skill.name;
|
|
1978
|
+
}
|
|
1979
|
+
function SkillList({
|
|
1980
|
+
agentId,
|
|
1981
|
+
skills,
|
|
1982
|
+
selectedSkillId,
|
|
1983
|
+
focused,
|
|
1984
|
+
searchQuery,
|
|
1985
|
+
loadingAgentName = null,
|
|
1986
|
+
width = 28,
|
|
1987
|
+
height = 18
|
|
1988
|
+
}) {
|
|
1989
|
+
const emptyMessage = loadingAgentName !== null ? `Loading skills for ${loadingAgentName}...` : agentId === null ? "Select an agent" : searchQuery !== void 0 && searchQuery.trim().length > 0 ? "No matching skills" : "No skills for this agent";
|
|
1990
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width, height, children: [
|
|
1991
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: focused ? "cyan" : void 0, children: [
|
|
1992
|
+
"Skills for ",
|
|
1993
|
+
agentId ?? "none"
|
|
1994
|
+
] }),
|
|
1995
|
+
skills.length === 0 ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: emptyMessage }) : skills.map((skill) => {
|
|
1996
|
+
const selected = skill.id === selectedSkillId;
|
|
1997
|
+
return /* @__PURE__ */ jsxs8(Text8, { inverse: focused && selected, children: [
|
|
1998
|
+
/* @__PURE__ */ jsx8(Text8, { color: markerColor(skill), children: skill.marker }),
|
|
1999
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
2000
|
+
" ",
|
|
2001
|
+
skillLabel(skill)
|
|
2002
|
+
] })
|
|
2003
|
+
] }, skill.id);
|
|
2004
|
+
})
|
|
2005
|
+
] });
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// src/tui/components/StatusLine.tsx
|
|
2009
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
2010
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2011
|
+
function StatusLine({
|
|
2012
|
+
busy,
|
|
2013
|
+
statusMessage,
|
|
2014
|
+
lastScanAt,
|
|
2015
|
+
issueCount
|
|
2016
|
+
}) {
|
|
2017
|
+
const message = statusMessage ?? (busy ? "scanning..." : `Last scan: ${lastScanAt ?? "never"} | issues: ${issueCount}`);
|
|
2018
|
+
return /* @__PURE__ */ jsx9(Box9, { height: 1, children: /* @__PURE__ */ jsx9(Text9, { color: busy ? "cyan" : void 0, children: message }) });
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/tui/components/Dashboard.tsx
|
|
2022
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2023
|
+
var minimumWidth = 80;
|
|
2024
|
+
var minimumHeight = 24;
|
|
2025
|
+
var agentRatio = 0.26;
|
|
2026
|
+
var skillRatio = 0.3;
|
|
2027
|
+
var detailRatio = 0.44;
|
|
2028
|
+
var agentMinimumWidth = 20;
|
|
2029
|
+
var skillMinimumWidth = 24;
|
|
2030
|
+
var detailMinimumWidth = 28;
|
|
2031
|
+
var largeModalWidth = 72;
|
|
2032
|
+
var largeModalHeight = 14;
|
|
2033
|
+
function paneWidths(width) {
|
|
2034
|
+
const agentWidth = Math.max(agentMinimumWidth, Math.round(width * agentRatio));
|
|
2035
|
+
const skillWidth = Math.max(skillMinimumWidth, Math.round(width * skillRatio));
|
|
2036
|
+
const detailWidth = Math.max(
|
|
2037
|
+
detailMinimumWidth,
|
|
2038
|
+
Math.round(width * detailRatio)
|
|
2039
|
+
);
|
|
2040
|
+
const widthDelta = width - (agentWidth + skillWidth + detailWidth);
|
|
2041
|
+
if (widthDelta === 0) {
|
|
2042
|
+
return { agentWidth, skillWidth, detailWidth };
|
|
2043
|
+
}
|
|
2044
|
+
return {
|
|
2045
|
+
agentWidth,
|
|
2046
|
+
skillWidth,
|
|
2047
|
+
detailWidth: detailWidth + widthDelta
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
function Dashboard({
|
|
2051
|
+
state,
|
|
2052
|
+
width,
|
|
2053
|
+
height,
|
|
2054
|
+
modalInteraction
|
|
2055
|
+
}) {
|
|
2056
|
+
const interaction = modalInteraction ?? {
|
|
2057
|
+
fieldIndex: 0,
|
|
2058
|
+
platformIndex: 0,
|
|
2059
|
+
doctorScrollOffset: 0
|
|
2060
|
+
};
|
|
2061
|
+
if (width < minimumWidth || height < minimumHeight) {
|
|
2062
|
+
return /* @__PURE__ */ jsx10(
|
|
2063
|
+
Box10,
|
|
2064
|
+
{
|
|
2065
|
+
flexDirection: "column",
|
|
2066
|
+
width,
|
|
2067
|
+
height,
|
|
2068
|
+
justifyContent: "center",
|
|
2069
|
+
alignItems: "center",
|
|
2070
|
+
children: /* @__PURE__ */ jsx10(Text10, { children: "Terminal too small. Resize to at least 80x24." })
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
const visibleAgents = getVisibleAgents(state);
|
|
2075
|
+
const visibleSkills = getVisibleSkills(state);
|
|
2076
|
+
const selectedAgent = state.model.agents.find((agent) => agent.id === state.model.selectedAgentId) ?? null;
|
|
2077
|
+
const selectedSkill = getSelectedSkill(state);
|
|
2078
|
+
const loadingAgentName = state.pendingAgentId ?? (state.busy && state.statusMessage === "loading agent..." ? state.model.selectedAgentId : null);
|
|
2079
|
+
const loadingAgent = loadingAgentName === null ? null : state.model.agents.find((agent) => agent.id === loadingAgentName) ?? null;
|
|
2080
|
+
const actions = getAvailableActions(state);
|
|
2081
|
+
const footerHeight = 3;
|
|
2082
|
+
const largeModal = state.modal?.kind === "add-agent" || state.modal?.kind === "edit-agent" || state.modal?.kind === "import" || state.modal?.kind === "doctor" || state.modal?.kind === "confirm-remove-agent";
|
|
2083
|
+
const overlayHeight = largeModal ? 0 : state.modal?.kind === "help" ? 8 : state.modal?.kind === "confirm-adopt" || state.modal?.kind === "confirm-adopt-all" || state.modal?.kind === "confirm-remove" ? confirmDialogHeight : 0;
|
|
2084
|
+
const footerSpace = state.modal === null ? footerHeight : largeModal ? 0 : footerHeight;
|
|
2085
|
+
const bodyHeight = Math.max(height - 1 - footerSpace - overlayHeight, 0);
|
|
2086
|
+
const { agentWidth, skillWidth, detailWidth } = paneWidths(width);
|
|
2087
|
+
const modalWidth = Math.min(width - 4, largeModalWidth);
|
|
2088
|
+
const modalHeight = Math.min(bodyHeight, largeModalHeight);
|
|
2089
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", width, height, children: [
|
|
2090
|
+
/* @__PURE__ */ jsx10(
|
|
2091
|
+
StatusLine,
|
|
2092
|
+
{
|
|
2093
|
+
busy: state.busy,
|
|
2094
|
+
statusMessage: state.statusMessage,
|
|
2095
|
+
lastScanAt: state.model.lastScanAt,
|
|
2096
|
+
issueCount: state.model.issueCount
|
|
2097
|
+
}
|
|
2098
|
+
),
|
|
2099
|
+
largeModal ? /* @__PURE__ */ jsx10(
|
|
2100
|
+
Box10,
|
|
2101
|
+
{
|
|
2102
|
+
flexDirection: "column",
|
|
2103
|
+
width,
|
|
2104
|
+
height: bodyHeight,
|
|
2105
|
+
justifyContent: "center",
|
|
2106
|
+
alignItems: "center",
|
|
2107
|
+
children: /* @__PURE__ */ jsx10(Box10, { width: modalWidth, height: modalHeight, children: state.modal?.kind === "add-agent" || state.modal?.kind === "edit-agent" || state.modal?.kind === "import" ? /* @__PURE__ */ jsx10(
|
|
2108
|
+
FormDialog,
|
|
2109
|
+
{
|
|
2110
|
+
modal: state.modal,
|
|
2111
|
+
fieldIndex: interaction.fieldIndex,
|
|
2112
|
+
platformIndex: interaction.platformIndex,
|
|
2113
|
+
width: modalWidth,
|
|
2114
|
+
height: modalHeight
|
|
2115
|
+
}
|
|
2116
|
+
) : state.modal?.kind === "doctor" ? /* @__PURE__ */ jsx10(
|
|
2117
|
+
DoctorDialog,
|
|
2118
|
+
{
|
|
2119
|
+
modal: state.modal,
|
|
2120
|
+
scrollOffset: interaction.doctorScrollOffset,
|
|
2121
|
+
width: modalWidth,
|
|
2122
|
+
height: modalHeight
|
|
2123
|
+
}
|
|
2124
|
+
) : state.modal?.kind === "confirm-remove-agent" ? /* @__PURE__ */ jsx10(ConfirmDialog, { modal: state.modal }) : null })
|
|
2125
|
+
}
|
|
2126
|
+
) : /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", width, height: bodyHeight, children: [
|
|
2127
|
+
/* @__PURE__ */ jsx10(
|
|
2128
|
+
AgentList,
|
|
2129
|
+
{
|
|
2130
|
+
agents: visibleAgents,
|
|
2131
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
2132
|
+
focused: state.focus === "agents",
|
|
2133
|
+
searchQuery: state.search?.panel === "agents" ? state.search.query : void 0,
|
|
2134
|
+
width: agentWidth,
|
|
2135
|
+
height: bodyHeight
|
|
2136
|
+
}
|
|
2137
|
+
),
|
|
2138
|
+
/* @__PURE__ */ jsx10(
|
|
2139
|
+
SkillList,
|
|
2140
|
+
{
|
|
2141
|
+
agentId: state.model.selectedAgentId,
|
|
2142
|
+
skills: visibleSkills,
|
|
2143
|
+
selectedSkillId: state.model.selectedSkillId,
|
|
2144
|
+
focused: state.focus === "skills",
|
|
2145
|
+
searchQuery: state.search?.panel === "skills" ? state.search.query : void 0,
|
|
2146
|
+
loadingAgentName: loadingAgent?.name ?? null,
|
|
2147
|
+
width: skillWidth,
|
|
2148
|
+
height: bodyHeight
|
|
2149
|
+
}
|
|
2150
|
+
),
|
|
2151
|
+
/* @__PURE__ */ jsx10(
|
|
2152
|
+
DetailPane,
|
|
2153
|
+
{
|
|
2154
|
+
selectedAgent,
|
|
2155
|
+
selectedSkill,
|
|
2156
|
+
focused: state.focus === "detail",
|
|
2157
|
+
loadingAgentName: loadingAgent?.name ?? null,
|
|
2158
|
+
width: detailWidth,
|
|
2159
|
+
height: bodyHeight
|
|
2160
|
+
}
|
|
2161
|
+
)
|
|
2162
|
+
] }),
|
|
2163
|
+
state.modal?.kind === "help" ? /* @__PURE__ */ jsx10(HelpOverlay, {}) : null,
|
|
2164
|
+
state.modal?.kind === "confirm-adopt" || state.modal?.kind === "confirm-adopt-all" || state.modal?.kind === "confirm-remove" || state.modal?.kind === "confirm-discard-dirty-form" ? /* @__PURE__ */ jsx10(ConfirmDialog, { modal: state.modal }) : null,
|
|
2165
|
+
state.modal === null ? /* @__PURE__ */ jsx10(Footer, { actions, search: state.search }) : largeModal ? null : /* @__PURE__ */ jsx10(Box10, { height: 3 })
|
|
2166
|
+
] });
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// src/tui/app.tsx
|
|
2170
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2171
|
+
var defaultServices2 = {
|
|
2172
|
+
loadDashboardState,
|
|
2173
|
+
dispatchTuiAction
|
|
2174
|
+
};
|
|
2175
|
+
var addAgentFieldOrder = [
|
|
2176
|
+
"id",
|
|
2177
|
+
"root",
|
|
2178
|
+
"skills",
|
|
2179
|
+
"name",
|
|
2180
|
+
"platforms",
|
|
2181
|
+
"disabledByDefault"
|
|
2182
|
+
];
|
|
2183
|
+
var editAgentFieldOrder = [
|
|
2184
|
+
"root",
|
|
2185
|
+
"skills",
|
|
2186
|
+
"name",
|
|
2187
|
+
"platforms",
|
|
2188
|
+
"enabledByDefault",
|
|
2189
|
+
"disabledByDefault"
|
|
2190
|
+
];
|
|
2191
|
+
var importFieldOrder = ["sourcePath", "skillName"];
|
|
2192
|
+
var platformOptions2 = ["win32", "linux", "darwin"];
|
|
2193
|
+
function clampIndex(index, count) {
|
|
2194
|
+
if (count <= 0) {
|
|
2195
|
+
return 0;
|
|
2196
|
+
}
|
|
2197
|
+
return (index % count + count) % count;
|
|
2198
|
+
}
|
|
2199
|
+
function nextText(value, input) {
|
|
2200
|
+
return `${value}${input}`;
|
|
2201
|
+
}
|
|
2202
|
+
function trimLast(value) {
|
|
2203
|
+
return value.slice(0, Math.max(value.length - 1, 0));
|
|
2204
|
+
}
|
|
2205
|
+
function togglePlatform(values, platform) {
|
|
2206
|
+
return values.includes(platform) ? values.filter((entry) => entry !== platform) : [...values, platform];
|
|
2207
|
+
}
|
|
2208
|
+
function commandFailureMessage(error) {
|
|
2209
|
+
return `Action failed: ${errorReason2(error)}`;
|
|
2210
|
+
}
|
|
2211
|
+
function restoreFailedFormModal(modal, message) {
|
|
2212
|
+
switch (modal.kind) {
|
|
2213
|
+
case "add-agent":
|
|
2214
|
+
return {
|
|
2215
|
+
kind: "add-agent",
|
|
2216
|
+
form: {
|
|
2217
|
+
...modal.form,
|
|
2218
|
+
error: message
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
case "edit-agent":
|
|
2222
|
+
return {
|
|
2223
|
+
kind: "edit-agent",
|
|
2224
|
+
agentId: modal.agentId,
|
|
2225
|
+
form: {
|
|
2226
|
+
...modal.form,
|
|
2227
|
+
error: message
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
case "import":
|
|
2231
|
+
return {
|
|
2232
|
+
kind: "import",
|
|
2233
|
+
form: {
|
|
2234
|
+
...modal.form,
|
|
2235
|
+
error: message
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
function errorReason2(error) {
|
|
2241
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2242
|
+
const firstLine = message.split(/\r?\n/u)[0]?.trim();
|
|
2243
|
+
return firstLine === void 0 || firstLine.length === 0 ? "Unknown error" : firstLine;
|
|
2244
|
+
}
|
|
2245
|
+
function loadOptions(props, selectedAgentId, selectedSkillId) {
|
|
2246
|
+
return {
|
|
2247
|
+
homeDir: props.homeDir,
|
|
2248
|
+
skillmuxHome: props.skillmuxHome,
|
|
2249
|
+
platform: props.platform,
|
|
2250
|
+
selectedAgentId,
|
|
2251
|
+
selectedSkillId
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
function replaceStateModel(previous, model, statusMessage) {
|
|
2255
|
+
const next = createInitialTuiState(model);
|
|
2256
|
+
return {
|
|
2257
|
+
...next,
|
|
2258
|
+
focus: previous.focus,
|
|
2259
|
+
search: previous.search,
|
|
2260
|
+
statusMessage,
|
|
2261
|
+
modal: null,
|
|
2262
|
+
busy: false
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
function isTextInput(input) {
|
|
2266
|
+
return input.length > 0 && !/[\u0000-\u001F\u007F]/u.test(input);
|
|
2267
|
+
}
|
|
2268
|
+
function parseBridgedSize(value) {
|
|
2269
|
+
try {
|
|
2270
|
+
const parsed = JSON.parse(value);
|
|
2271
|
+
if (typeof parsed.columns === "number" && typeof parsed.rows === "number" && Number.isFinite(parsed.columns) && Number.isFinite(parsed.rows)) {
|
|
2272
|
+
return {
|
|
2273
|
+
columns: parsed.columns,
|
|
2274
|
+
rows: parsed.rows
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
} catch {
|
|
2278
|
+
return null;
|
|
2279
|
+
}
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
function liveTerminalSize() {
|
|
2283
|
+
return {
|
|
2284
|
+
columns: process.stdout.columns ?? 80,
|
|
2285
|
+
rows: process.stdout.rows ?? 24
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
function readSizeFile(path) {
|
|
2289
|
+
try {
|
|
2290
|
+
return parseBridgedSize(readFileSync(path, "utf8"));
|
|
2291
|
+
} catch {
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
function useBridgedTerminalSize(path) {
|
|
2296
|
+
const [size, setSize] = useState(
|
|
2297
|
+
() => path === null ? null : readSizeFile(path)
|
|
2298
|
+
);
|
|
2299
|
+
useEffect(() => {
|
|
2300
|
+
if (path === null) {
|
|
2301
|
+
setSize(null);
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
let cancelled = false;
|
|
2305
|
+
const refresh = () => {
|
|
2306
|
+
if (cancelled) {
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
const nextSize = readSizeFile(path);
|
|
2310
|
+
setSize(
|
|
2311
|
+
(current) => nextSize === null ? current : current !== null && current.columns === nextSize.columns && current.rows === nextSize.rows ? current : nextSize
|
|
2312
|
+
);
|
|
2313
|
+
};
|
|
2314
|
+
refresh();
|
|
2315
|
+
const timer = setInterval(refresh, 50);
|
|
2316
|
+
return () => {
|
|
2317
|
+
cancelled = true;
|
|
2318
|
+
clearInterval(timer);
|
|
2319
|
+
};
|
|
2320
|
+
}, [path]);
|
|
2321
|
+
return size;
|
|
2322
|
+
}
|
|
2323
|
+
function LiveDashboardViewport({
|
|
2324
|
+
state,
|
|
2325
|
+
terminalWidth,
|
|
2326
|
+
terminalHeight,
|
|
2327
|
+
modalInteraction
|
|
2328
|
+
}) {
|
|
2329
|
+
return /* @__PURE__ */ jsx11(
|
|
2330
|
+
Dashboard,
|
|
2331
|
+
{
|
|
2332
|
+
state,
|
|
2333
|
+
width: terminalWidth ?? liveTerminalSize().columns,
|
|
2334
|
+
height: terminalHeight ?? liveTerminalSize().rows,
|
|
2335
|
+
modalInteraction
|
|
2336
|
+
}
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
function BridgedDashboardViewport({
|
|
2340
|
+
state,
|
|
2341
|
+
terminalWidth,
|
|
2342
|
+
terminalHeight,
|
|
2343
|
+
bridgePath,
|
|
2344
|
+
modalInteraction
|
|
2345
|
+
}) {
|
|
2346
|
+
const bridgedTerminalSize = useBridgedTerminalSize(bridgePath ?? null);
|
|
2347
|
+
const fallbackSize = liveTerminalSize();
|
|
2348
|
+
return /* @__PURE__ */ jsx11(
|
|
2349
|
+
Dashboard,
|
|
2350
|
+
{
|
|
2351
|
+
state,
|
|
2352
|
+
width: terminalWidth ?? bridgedTerminalSize?.columns ?? fallbackSize.columns,
|
|
2353
|
+
height: terminalHeight ?? bridgedTerminalSize?.rows ?? fallbackSize.rows,
|
|
2354
|
+
modalInteraction
|
|
2355
|
+
}
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
function App({
|
|
2359
|
+
homeDir,
|
|
2360
|
+
skillmuxHome,
|
|
2361
|
+
platform,
|
|
2362
|
+
terminalWidth,
|
|
2363
|
+
terminalHeight,
|
|
2364
|
+
services: serviceOverrides
|
|
2365
|
+
}) {
|
|
2366
|
+
const { exit } = useApp();
|
|
2367
|
+
const [state, setState] = useState(null);
|
|
2368
|
+
const [loadError, setLoadError] = useState(null);
|
|
2369
|
+
const requestSequence = useRef(0);
|
|
2370
|
+
const latestRequest = useRef(0);
|
|
2371
|
+
const activeActionRequest = useRef(null);
|
|
2372
|
+
const stableModel = useRef(null);
|
|
2373
|
+
const [modalInteraction, setModalInteraction] = useState({
|
|
2374
|
+
fieldIndex: 0,
|
|
2375
|
+
platformIndex: 0,
|
|
2376
|
+
doctorScrollOffset: 0
|
|
2377
|
+
});
|
|
2378
|
+
const services = useMemo(
|
|
2379
|
+
() => ({ ...defaultServices2, ...serviceOverrides }),
|
|
2380
|
+
[serviceOverrides]
|
|
2381
|
+
);
|
|
2382
|
+
const sizeBridgePath = process.env.SKILLMUX_TUI_PTY_SIZE_FILE?.trim() ?? null;
|
|
2383
|
+
const sizeBridgeEnabled = sizeBridgePath !== null && sizeBridgePath.length > 0;
|
|
2384
|
+
const modalKind = state?.modal?.kind ?? null;
|
|
2385
|
+
const beginRequest = useCallback(() => {
|
|
2386
|
+
requestSequence.current += 1;
|
|
2387
|
+
latestRequest.current = requestSequence.current;
|
|
2388
|
+
return requestSequence.current;
|
|
2389
|
+
}, []);
|
|
2390
|
+
const isLatestRequest = useCallback((requestId) => {
|
|
2391
|
+
return latestRequest.current === requestId;
|
|
2392
|
+
}, []);
|
|
2393
|
+
const startBusyState = useCallback(
|
|
2394
|
+
(baseState, action) => updateTuiState(
|
|
2395
|
+
updateTuiState(baseState, { type: "set-busy", busy: true }),
|
|
2396
|
+
{
|
|
2397
|
+
type: "set-status",
|
|
2398
|
+
message: action === "scan" ? "scanning..." : "working..."
|
|
2399
|
+
}
|
|
2400
|
+
),
|
|
2401
|
+
[]
|
|
2402
|
+
);
|
|
2403
|
+
useEffect(() => {
|
|
2404
|
+
let cancelled = false;
|
|
2405
|
+
services.loadDashboardState(loadOptions({ homeDir, skillmuxHome, platform })).then((model) => {
|
|
2406
|
+
if (!cancelled) {
|
|
2407
|
+
stableModel.current = model;
|
|
2408
|
+
setState(createInitialTuiState(model));
|
|
2409
|
+
setLoadError(null);
|
|
2410
|
+
}
|
|
2411
|
+
}).catch((error) => {
|
|
2412
|
+
if (!cancelled) {
|
|
2413
|
+
setLoadError(`Failed to load dashboard: ${errorReason2(error)}`);
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
return () => {
|
|
2417
|
+
cancelled = true;
|
|
2418
|
+
};
|
|
2419
|
+
}, [homeDir, platform, services, skillmuxHome]);
|
|
2420
|
+
useEffect(() => {
|
|
2421
|
+
setModalInteraction({
|
|
2422
|
+
fieldIndex: 0,
|
|
2423
|
+
platformIndex: 0,
|
|
2424
|
+
doctorScrollOffset: 0
|
|
2425
|
+
});
|
|
2426
|
+
}, [modalKind]);
|
|
2427
|
+
const runAction = useCallback(
|
|
2428
|
+
(action, model, baseState) => {
|
|
2429
|
+
if (activeActionRequest.current !== null) {
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const requestId = beginRequest();
|
|
2433
|
+
activeActionRequest.current = requestId;
|
|
2434
|
+
setState(
|
|
2435
|
+
(current) => current === null ? current : startBusyState(baseState ?? current, action)
|
|
2436
|
+
);
|
|
2437
|
+
services.dispatchTuiAction({
|
|
2438
|
+
action,
|
|
2439
|
+
model,
|
|
2440
|
+
homeDir,
|
|
2441
|
+
skillmuxHome,
|
|
2442
|
+
platform
|
|
2443
|
+
}).then((result) => {
|
|
2444
|
+
if (!isLatestRequest(requestId)) {
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
stableModel.current = result.model;
|
|
2448
|
+
setState(
|
|
2449
|
+
(current) => current === null ? current : replaceStateModel(current, result.model, result.statusMessage)
|
|
2450
|
+
);
|
|
2451
|
+
}).catch((error) => {
|
|
2452
|
+
if (!isLatestRequest(requestId)) {
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
setState(
|
|
2456
|
+
(current) => current === null ? current : updateTuiState(
|
|
2457
|
+
updateTuiState(current, { type: "set-busy", busy: false }),
|
|
2458
|
+
{
|
|
2459
|
+
type: "set-status",
|
|
2460
|
+
message: `Action failed: ${errorReason2(error)}`
|
|
2461
|
+
}
|
|
2462
|
+
)
|
|
2463
|
+
);
|
|
2464
|
+
}).finally(() => {
|
|
2465
|
+
if (activeActionRequest.current === requestId) {
|
|
2466
|
+
activeActionRequest.current = null;
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
},
|
|
2470
|
+
[
|
|
2471
|
+
beginRequest,
|
|
2472
|
+
homeDir,
|
|
2473
|
+
isLatestRequest,
|
|
2474
|
+
platform,
|
|
2475
|
+
services,
|
|
2476
|
+
skillmuxHome,
|
|
2477
|
+
startBusyState
|
|
2478
|
+
]
|
|
2479
|
+
);
|
|
2480
|
+
const reloadAgent = useCallback(
|
|
2481
|
+
(agentId) => {
|
|
2482
|
+
const requestId = beginRequest();
|
|
2483
|
+
setState(
|
|
2484
|
+
(current) => current === null ? current : updateTuiState(
|
|
2485
|
+
updateTuiState(current, { type: "set-busy", busy: true }),
|
|
2486
|
+
{ type: "set-status", message: "loading agent..." }
|
|
2487
|
+
)
|
|
2488
|
+
);
|
|
2489
|
+
services.loadDashboardState(loadOptions({ homeDir, skillmuxHome, platform }, agentId)).then((model) => {
|
|
2490
|
+
if (!isLatestRequest(requestId)) {
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
stableModel.current = model;
|
|
2494
|
+
setState(
|
|
2495
|
+
(current) => current === null ? current : replaceStateModel(current, model, null)
|
|
2496
|
+
);
|
|
2497
|
+
}).catch((error) => {
|
|
2498
|
+
if (!isLatestRequest(requestId)) {
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
setState(
|
|
2502
|
+
(current) => current === null ? current : stableModel.current === null ? updateTuiState(
|
|
2503
|
+
updateTuiState(current, { type: "set-busy", busy: false }),
|
|
2504
|
+
{
|
|
2505
|
+
type: "set-status",
|
|
2506
|
+
message: `Load failed: ${errorReason2(error)}`
|
|
2507
|
+
}
|
|
2508
|
+
) : replaceStateModel(
|
|
2509
|
+
current,
|
|
2510
|
+
stableModel.current,
|
|
2511
|
+
`Load failed: ${errorReason2(error)}`
|
|
2512
|
+
)
|
|
2513
|
+
);
|
|
2514
|
+
});
|
|
2515
|
+
},
|
|
2516
|
+
[beginRequest, homeDir, isLatestRequest, platform, services, skillmuxHome]
|
|
2517
|
+
);
|
|
2518
|
+
useEffect(() => {
|
|
2519
|
+
if (state?.pendingAction === null || state?.pendingAction === void 0) {
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
const consumed = consumeActionIntent(state);
|
|
2523
|
+
setState(consumed.state);
|
|
2524
|
+
if (consumed.action !== null) {
|
|
2525
|
+
runAction(consumed.action, consumed.state.model);
|
|
2526
|
+
}
|
|
2527
|
+
}, [runAction, state]);
|
|
2528
|
+
useEffect(() => {
|
|
2529
|
+
if (state?.pendingAgentId === null || state?.pendingAgentId === void 0) {
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
const consumed = consumeAgentSelectionIntent(state);
|
|
2533
|
+
setState(consumed.state);
|
|
2534
|
+
if (consumed.agentId !== null) {
|
|
2535
|
+
reloadAgent(consumed.agentId);
|
|
2536
|
+
}
|
|
2537
|
+
}, [reloadAgent, state]);
|
|
2538
|
+
useEffect(() => {
|
|
2539
|
+
if (state?.pendingCommand === null || state?.pendingCommand === void 0) {
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
const consumed = consumePendingCommandIntent(state);
|
|
2543
|
+
setState(consumed.state);
|
|
2544
|
+
if (consumed.command === null) {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
if (consumed.command.kind === "doctor") {
|
|
2548
|
+
const requestId2 = beginRequest();
|
|
2549
|
+
services.dispatchTuiAction({
|
|
2550
|
+
action: consumed.command,
|
|
2551
|
+
model: consumed.state.model,
|
|
2552
|
+
homeDir,
|
|
2553
|
+
skillmuxHome,
|
|
2554
|
+
platform
|
|
2555
|
+
}).then((result) => {
|
|
2556
|
+
if (!isLatestRequest(requestId2)) {
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
if (result.commandSucceeded === false) {
|
|
2560
|
+
const failureMessage = result.statusMessage;
|
|
2561
|
+
setState(
|
|
2562
|
+
(current) => current === null ? current : updateTuiState(
|
|
2563
|
+
updateTuiState(current, {
|
|
2564
|
+
type: "doctor-result-failed",
|
|
2565
|
+
errorMessage: failureMessage
|
|
2566
|
+
}),
|
|
2567
|
+
{ type: "set-status", message: failureMessage }
|
|
2568
|
+
)
|
|
2569
|
+
);
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const doctorReport = result.doctor;
|
|
2573
|
+
if (doctorReport === void 0) {
|
|
2574
|
+
throw new Error("Doctor report missing");
|
|
2575
|
+
}
|
|
2576
|
+
stableModel.current = result.model;
|
|
2577
|
+
setState(
|
|
2578
|
+
(current) => current === null ? current : updateTuiState(
|
|
2579
|
+
updateTuiState(current, {
|
|
2580
|
+
type: "doctor-result-loaded",
|
|
2581
|
+
report: doctorReport
|
|
2582
|
+
}),
|
|
2583
|
+
{ type: "set-status", message: result.statusMessage }
|
|
2584
|
+
)
|
|
2585
|
+
);
|
|
2586
|
+
}).catch((error) => {
|
|
2587
|
+
if (!isLatestRequest(requestId2)) {
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
setState(
|
|
2591
|
+
(current) => current === null ? current : updateTuiState(
|
|
2592
|
+
updateTuiState(current, {
|
|
2593
|
+
type: "doctor-result-failed",
|
|
2594
|
+
errorMessage: `Doctor failed: ${errorReason2(error)}`
|
|
2595
|
+
}),
|
|
2596
|
+
{ type: "set-status", message: `Doctor failed: ${errorReason2(error)}` }
|
|
2597
|
+
)
|
|
2598
|
+
);
|
|
2599
|
+
});
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (activeActionRequest.current !== null) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const requestId = beginRequest();
|
|
2606
|
+
activeActionRequest.current = requestId;
|
|
2607
|
+
const busyState = {
|
|
2608
|
+
...consumed.state,
|
|
2609
|
+
modal: null,
|
|
2610
|
+
busy: true,
|
|
2611
|
+
statusMessage: "working..."
|
|
2612
|
+
};
|
|
2613
|
+
setState(busyState);
|
|
2614
|
+
services.dispatchTuiAction({
|
|
2615
|
+
action: consumed.command,
|
|
2616
|
+
model: busyState.model,
|
|
2617
|
+
homeDir,
|
|
2618
|
+
skillmuxHome,
|
|
2619
|
+
platform
|
|
2620
|
+
}).then((result) => {
|
|
2621
|
+
if (!isLatestRequest(requestId)) {
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
if (result.commandSucceeded === false) {
|
|
2625
|
+
const failureMessage = result.statusMessage;
|
|
2626
|
+
if (consumed.command?.kind === "doctor") {
|
|
2627
|
+
setState({
|
|
2628
|
+
...consumed.state,
|
|
2629
|
+
busy: false,
|
|
2630
|
+
statusMessage: failureMessage,
|
|
2631
|
+
modal: {
|
|
2632
|
+
kind: "doctor",
|
|
2633
|
+
status: "error",
|
|
2634
|
+
errorMessage: failureMessage
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
if (consumed.state.modal !== null && "form" in consumed.state.modal) {
|
|
2640
|
+
setState({
|
|
2641
|
+
...consumed.state,
|
|
2642
|
+
busy: false,
|
|
2643
|
+
statusMessage: failureMessage,
|
|
2644
|
+
modal: restoreFailedFormModal(consumed.state.modal, failureMessage)
|
|
2645
|
+
});
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
setState({
|
|
2649
|
+
...consumed.state,
|
|
2650
|
+
busy: false,
|
|
2651
|
+
statusMessage: failureMessage
|
|
2652
|
+
});
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
stableModel.current = result.model;
|
|
2656
|
+
setState(
|
|
2657
|
+
(current) => current === null ? current : replaceStateModel(current, result.model, result.statusMessage)
|
|
2658
|
+
);
|
|
2659
|
+
}).catch((error) => {
|
|
2660
|
+
if (!isLatestRequest(requestId)) {
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
const failedModal = consumed.state.modal;
|
|
2664
|
+
const failureMessage = commandFailureMessage(error);
|
|
2665
|
+
if (failedModal === null || failedModal.kind !== "add-agent" && failedModal.kind !== "edit-agent" && failedModal.kind !== "import") {
|
|
2666
|
+
setState({
|
|
2667
|
+
...consumed.state,
|
|
2668
|
+
busy: false,
|
|
2669
|
+
statusMessage: failureMessage
|
|
2670
|
+
});
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
setState({
|
|
2674
|
+
...consumed.state,
|
|
2675
|
+
busy: false,
|
|
2676
|
+
statusMessage: failureMessage,
|
|
2677
|
+
modal: restoreFailedFormModal(failedModal, failureMessage)
|
|
2678
|
+
});
|
|
2679
|
+
}).finally(() => {
|
|
2680
|
+
if (activeActionRequest.current === requestId) {
|
|
2681
|
+
activeActionRequest.current = null;
|
|
2682
|
+
}
|
|
2683
|
+
});
|
|
2684
|
+
}, [
|
|
2685
|
+
beginRequest,
|
|
2686
|
+
homeDir,
|
|
2687
|
+
isLatestRequest,
|
|
2688
|
+
platform,
|
|
2689
|
+
services,
|
|
2690
|
+
skillmuxHome,
|
|
2691
|
+
state
|
|
2692
|
+
]);
|
|
2693
|
+
useInput((input, key) => {
|
|
2694
|
+
if (key.ctrl && input === "c") {
|
|
2695
|
+
exit();
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
if (state === null) {
|
|
2699
|
+
if (input === "q") {
|
|
2700
|
+
exit();
|
|
2701
|
+
}
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (state.search !== null) {
|
|
2705
|
+
if (key.escape || input === "\x1B") {
|
|
2706
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
if (key.return) {
|
|
2710
|
+
setState(updateTuiState(state, { type: "submit-search" }));
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
if (key.backspace || key.delete) {
|
|
2714
|
+
setState(
|
|
2715
|
+
updateTuiState(state, {
|
|
2716
|
+
type: "search-query-changed",
|
|
2717
|
+
query: state.search.query.slice(0, -1)
|
|
2718
|
+
})
|
|
2719
|
+
);
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
if (!key.ctrl && !key.meta && isTextInput(input)) {
|
|
2723
|
+
setState(
|
|
2724
|
+
updateTuiState(state, {
|
|
2725
|
+
type: "search-query-changed",
|
|
2726
|
+
query: `${state.search.query}${input}`
|
|
2727
|
+
})
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (state.modal !== null) {
|
|
2733
|
+
if (input === "q") {
|
|
2734
|
+
if (state.modal.kind === "confirm-discard-dirty-form" || "form" in state.modal && state.modal.form.dirty) {
|
|
2735
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
exit();
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
if (activeActionRequest.current !== null) {
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (state.modal.kind === "help") {
|
|
2745
|
+
if (key.escape || input === "\x1B") {
|
|
2746
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2747
|
+
}
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
if (state.modal.kind === "confirm-discard-dirty-form") {
|
|
2751
|
+
if (input.toLocaleLowerCase() === "y") {
|
|
2752
|
+
setState(updateTuiState(state, { type: "confirm-discard-dirty-form" }));
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
if (key.escape || input === "\x1B" || input.toLocaleLowerCase() === "n") {
|
|
2756
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2757
|
+
}
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (state.modal.kind === "confirm-adopt" || state.modal.kind === "confirm-adopt-all" || state.modal.kind === "confirm-remove" || state.modal.kind === "confirm-remove-agent") {
|
|
2761
|
+
if (input.toLocaleLowerCase() === "y") {
|
|
2762
|
+
const closedState = updateTuiState(state, { type: "close" });
|
|
2763
|
+
if (state.modal.kind === "confirm-adopt") {
|
|
2764
|
+
runAction("adopt", closedState.model, closedState);
|
|
2765
|
+
} else if (state.modal.kind === "confirm-adopt-all") {
|
|
2766
|
+
runAction("adopt-all", closedState.model, closedState);
|
|
2767
|
+
} else if (state.modal.kind === "confirm-remove") {
|
|
2768
|
+
runAction("remove", closedState.model, closedState);
|
|
2769
|
+
} else {
|
|
2770
|
+
setState(updateTuiState(state, { type: "submit-remove-agent" }));
|
|
2771
|
+
}
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
if (key.escape || input === "\x1B" || input.toLocaleLowerCase() === "n") {
|
|
2775
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2776
|
+
}
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
if (state.modal.kind === "doctor") {
|
|
2780
|
+
if (key.escape || input === "\x1B") {
|
|
2781
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
if (key.downArrow || key.rightArrow || input === "j") {
|
|
2785
|
+
setModalInteraction((current) => ({
|
|
2786
|
+
...current,
|
|
2787
|
+
doctorScrollOffset: current.doctorScrollOffset + 1
|
|
2788
|
+
}));
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
if (key.upArrow || key.leftArrow || input === "k") {
|
|
2792
|
+
setModalInteraction((current) => ({
|
|
2793
|
+
...current,
|
|
2794
|
+
doctorScrollOffset: Math.max(current.doctorScrollOffset - 1, 0)
|
|
2795
|
+
}));
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
if (state.modal.kind === "add-agent" || state.modal.kind === "edit-agent" || state.modal.kind === "import") {
|
|
2801
|
+
const fieldOrder = state.modal.kind === "add-agent" ? addAgentFieldOrder : state.modal.kind === "edit-agent" ? editAgentFieldOrder : importFieldOrder;
|
|
2802
|
+
const submitFieldIndex = fieldOrder.length;
|
|
2803
|
+
const currentFieldIndex = clampIndex(
|
|
2804
|
+
modalInteraction.fieldIndex,
|
|
2805
|
+
fieldOrder.length + 1
|
|
2806
|
+
);
|
|
2807
|
+
const currentField = fieldOrder[currentFieldIndex];
|
|
2808
|
+
const moveField = (direction) => {
|
|
2809
|
+
setModalInteraction((current) => ({
|
|
2810
|
+
...current,
|
|
2811
|
+
fieldIndex: clampIndex(
|
|
2812
|
+
current.fieldIndex + direction,
|
|
2813
|
+
fieldOrder.length + 1
|
|
2814
|
+
),
|
|
2815
|
+
platformIndex: 0
|
|
2816
|
+
}));
|
|
2817
|
+
};
|
|
2818
|
+
const updateAddAgent = (field, value) => {
|
|
2819
|
+
setState(
|
|
2820
|
+
updateTuiState(state, {
|
|
2821
|
+
type: "add-agent-form-field-changed",
|
|
2822
|
+
field,
|
|
2823
|
+
value
|
|
2824
|
+
})
|
|
2825
|
+
);
|
|
2826
|
+
};
|
|
2827
|
+
const updateEditAgent = (field, value) => {
|
|
2828
|
+
setState(
|
|
2829
|
+
updateTuiState(state, {
|
|
2830
|
+
type: "edit-agent-form-field-changed",
|
|
2831
|
+
field,
|
|
2832
|
+
value
|
|
2833
|
+
})
|
|
2834
|
+
);
|
|
2835
|
+
};
|
|
2836
|
+
const updateImport = (field, value) => {
|
|
2837
|
+
setState(
|
|
2838
|
+
updateTuiState(state, {
|
|
2839
|
+
type: "import-form-field-changed",
|
|
2840
|
+
field,
|
|
2841
|
+
value
|
|
2842
|
+
})
|
|
2843
|
+
);
|
|
2844
|
+
};
|
|
2845
|
+
if (key.escape || input === "\x1B") {
|
|
2846
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
if (key.return && currentFieldIndex === submitFieldIndex) {
|
|
2850
|
+
if (state.modal.kind === "add-agent") {
|
|
2851
|
+
setState(updateTuiState(state, { type: "submit-add-agent-form" }));
|
|
2852
|
+
} else if (state.modal.kind === "edit-agent") {
|
|
2853
|
+
setState(updateTuiState(state, { type: "submit-edit-agent-form" }));
|
|
2854
|
+
} else {
|
|
2855
|
+
setState(updateTuiState(state, { type: "submit-import-form" }));
|
|
2856
|
+
}
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
if (key.downArrow) {
|
|
2860
|
+
moveField(1);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (key.upArrow) {
|
|
2864
|
+
moveField(-1);
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (currentField === void 0) {
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
if (currentField === "platforms") {
|
|
2871
|
+
const platform2 = platformOptions2[modalInteraction.platformIndex] ?? platformOptions2[0];
|
|
2872
|
+
if (key.leftArrow) {
|
|
2873
|
+
setModalInteraction((current) => ({
|
|
2874
|
+
...current,
|
|
2875
|
+
platformIndex: clampIndex(current.platformIndex - 1, platformOptions2.length)
|
|
2876
|
+
}));
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (key.rightArrow) {
|
|
2880
|
+
setModalInteraction((current) => ({
|
|
2881
|
+
...current,
|
|
2882
|
+
platformIndex: clampIndex(current.platformIndex + 1, platformOptions2.length)
|
|
2883
|
+
}));
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
if (input === " ") {
|
|
2887
|
+
if (state.modal.kind === "add-agent") {
|
|
2888
|
+
updateAddAgent(
|
|
2889
|
+
"platforms",
|
|
2890
|
+
togglePlatform(state.modal.form.values.platforms, platform2)
|
|
2891
|
+
);
|
|
2892
|
+
} else if (state.modal.kind === "edit-agent") {
|
|
2893
|
+
updateEditAgent(
|
|
2894
|
+
"platforms",
|
|
2895
|
+
togglePlatform(state.modal.form.values.platforms, platform2)
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
if (currentField === "disabledByDefault" && state.modal.kind === "add-agent") {
|
|
2902
|
+
if (input === " ") {
|
|
2903
|
+
updateAddAgent("disabledByDefault", !state.modal.form.values.disabledByDefault);
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
if (state.modal.kind === "edit-agent") {
|
|
2908
|
+
if (currentField === "enabledByDefault") {
|
|
2909
|
+
if (input === " ") {
|
|
2910
|
+
updateEditAgent("enabledByDefault", !state.modal.form.values.enabledByDefault);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
if (currentField === "disabledByDefault") {
|
|
2915
|
+
if (input === " ") {
|
|
2916
|
+
updateEditAgent(
|
|
2917
|
+
"disabledByDefault",
|
|
2918
|
+
!state.modal.form.values.disabledByDefault
|
|
2919
|
+
);
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
if (isTextInput(input)) {
|
|
2925
|
+
if (state.modal.kind === "add-agent") {
|
|
2926
|
+
if (currentField === "id") {
|
|
2927
|
+
updateAddAgent("id", nextText(state.modal.form.values.id, input));
|
|
2928
|
+
return;
|
|
2929
|
+
}
|
|
2930
|
+
if (currentField === "root") {
|
|
2931
|
+
updateAddAgent("root", nextText(state.modal.form.values.root, input));
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
if (currentField === "skills") {
|
|
2935
|
+
updateAddAgent("skills", nextText(state.modal.form.values.skills, input));
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2938
|
+
if (currentField === "name") {
|
|
2939
|
+
updateAddAgent("name", nextText(state.modal.form.values.name, input));
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
} else if (state.modal.kind === "edit-agent") {
|
|
2943
|
+
if (currentField === "root") {
|
|
2944
|
+
updateEditAgent("root", nextText(state.modal.form.values.root, input));
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (currentField === "skills") {
|
|
2948
|
+
updateEditAgent("skills", nextText(state.modal.form.values.skills, input));
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
if (currentField === "name") {
|
|
2952
|
+
updateEditAgent("name", nextText(state.modal.form.values.name, input));
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
} else if (state.modal.kind === "import") {
|
|
2956
|
+
if (currentField === "sourcePath") {
|
|
2957
|
+
updateImport("sourcePath", nextText(state.modal.form.values.sourcePath, input));
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
if (currentField === "skillName") {
|
|
2961
|
+
updateImport("skillName", nextText(state.modal.form.values.skillName, input));
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
if (key.backspace || key.delete) {
|
|
2967
|
+
if (state.modal.kind === "add-agent") {
|
|
2968
|
+
if (currentField === "id") {
|
|
2969
|
+
updateAddAgent("id", trimLast(state.modal.form.values.id));
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
if (currentField === "root") {
|
|
2973
|
+
updateAddAgent("root", trimLast(state.modal.form.values.root));
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
if (currentField === "skills") {
|
|
2977
|
+
updateAddAgent("skills", trimLast(state.modal.form.values.skills));
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
if (currentField === "name") {
|
|
2981
|
+
updateAddAgent("name", trimLast(state.modal.form.values.name));
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
} else if (state.modal.kind === "edit-agent") {
|
|
2985
|
+
if (currentField === "root") {
|
|
2986
|
+
updateEditAgent("root", trimLast(state.modal.form.values.root));
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (currentField === "skills") {
|
|
2990
|
+
updateEditAgent("skills", trimLast(state.modal.form.values.skills));
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
if (currentField === "name") {
|
|
2994
|
+
updateEditAgent("name", trimLast(state.modal.form.values.name));
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
} else if (state.modal.kind === "import") {
|
|
2998
|
+
if (currentField === "sourcePath") {
|
|
2999
|
+
updateImport("sourcePath", trimLast(state.modal.form.values.sourcePath));
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
if (currentField === "skillName") {
|
|
3003
|
+
updateImport("skillName", trimLast(state.modal.form.values.skillName));
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
if (input === "q") {
|
|
3013
|
+
exit();
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
if (activeActionRequest.current !== null) {
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
if (key.rightArrow) {
|
|
3020
|
+
setState(
|
|
3021
|
+
updateTuiState(state, {
|
|
3022
|
+
type: "focus-next"
|
|
3023
|
+
})
|
|
3024
|
+
);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
if (key.leftArrow) {
|
|
3028
|
+
setState(
|
|
3029
|
+
updateTuiState(state, {
|
|
3030
|
+
type: "focus-previous"
|
|
3031
|
+
})
|
|
3032
|
+
);
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
if (key.downArrow || input === "j") {
|
|
3036
|
+
setState(updateTuiState(state, { type: "next-row" }));
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
if (key.upArrow || input === "k") {
|
|
3040
|
+
setState(updateTuiState(state, { type: "previous-row" }));
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
if (input === "g") {
|
|
3044
|
+
setState(updateTuiState(state, { type: "first-row" }));
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
if (input === "G") {
|
|
3048
|
+
setState(updateTuiState(state, { type: "last-row" }));
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
if (input === "/") {
|
|
3052
|
+
setState(updateTuiState(state, { type: "open-search" }));
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
if (input === "?") {
|
|
3056
|
+
setState(updateTuiState(state, { type: "open-help" }));
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
if (input === "n") {
|
|
3060
|
+
setState(updateTuiState(state, { type: "open-add-agent" }));
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
if (input === "e") {
|
|
3064
|
+
setState(updateTuiState(state, { type: "open-edit-agent" }));
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
if (input === "X" || key.shift && input === "x") {
|
|
3068
|
+
setState(updateTuiState(state, { type: "open-remove-agent" }));
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
if (input === "i") {
|
|
3072
|
+
setState(updateTuiState(state, { type: "open-import" }));
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
if (input === "d") {
|
|
3076
|
+
setState(updateTuiState(state, { type: "open-doctor" }));
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
if (key.escape || input === "\x1B") {
|
|
3080
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
if (input === " ") {
|
|
3084
|
+
setState(updateTuiState(state, { type: "request-toggle" }));
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
if (input === "A" || key.shift && input === "a") {
|
|
3088
|
+
setState(updateTuiState(state, { type: "request-adopt-all" }));
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
if (input === "a") {
|
|
3092
|
+
setState(updateTuiState(state, { type: "request-adopt" }));
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
if (input === "r") {
|
|
3096
|
+
setState(updateTuiState(state, { type: "request-remove" }));
|
|
3097
|
+
return;
|
|
3098
|
+
}
|
|
3099
|
+
if (input === "s") {
|
|
3100
|
+
setState(updateTuiState(state, { type: "request-scan" }));
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
if (loadError !== null) {
|
|
3104
|
+
return /* @__PURE__ */ jsx11(Text11, { color: "red", children: loadError });
|
|
3105
|
+
}
|
|
3106
|
+
if (state === null) {
|
|
3107
|
+
return /* @__PURE__ */ jsx11(Text11, { children: "loading dashboard..." });
|
|
3108
|
+
}
|
|
3109
|
+
return sizeBridgeEnabled ? /* @__PURE__ */ jsx11(
|
|
3110
|
+
BridgedDashboardViewport,
|
|
3111
|
+
{
|
|
3112
|
+
state,
|
|
3113
|
+
terminalWidth,
|
|
3114
|
+
terminalHeight,
|
|
3115
|
+
bridgePath: sizeBridgePath,
|
|
3116
|
+
modalInteraction
|
|
3117
|
+
}
|
|
3118
|
+
) : /* @__PURE__ */ jsx11(
|
|
3119
|
+
LiveDashboardViewport,
|
|
3120
|
+
{
|
|
3121
|
+
state,
|
|
3122
|
+
terminalWidth,
|
|
3123
|
+
terminalHeight,
|
|
3124
|
+
modalInteraction
|
|
3125
|
+
}
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// src/tui/launch-tui.tsx
|
|
3130
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
3131
|
+
var alternateScreenEnter = "\x1B[?1049h";
|
|
3132
|
+
var alternateScreenExit = "\x1B[?1049l";
|
|
3133
|
+
var cursorHide = "\x1B[?25l";
|
|
3134
|
+
var cursorShow = "\x1B[?25h";
|
|
3135
|
+
var lifecycleTraceEnabled = process.env.SKILLMUX_TUI_PTY_TRACE === "1";
|
|
3136
|
+
async function launchTui(options = {}) {
|
|
3137
|
+
let failure;
|
|
3138
|
+
let instance = null;
|
|
3139
|
+
let sigintRequested = false;
|
|
3140
|
+
const handleSigint = () => {
|
|
3141
|
+
sigintRequested = true;
|
|
3142
|
+
instance?.unmount();
|
|
3143
|
+
};
|
|
3144
|
+
try {
|
|
3145
|
+
process.once("SIGINT", handleSigint);
|
|
3146
|
+
process.stdout.write(alternateScreenEnter);
|
|
3147
|
+
process.stdout.write(cursorHide);
|
|
3148
|
+
await writeLifecycleTrace("alt-screen-enter");
|
|
3149
|
+
instance = render(/* @__PURE__ */ jsx12(App, { ...options }));
|
|
3150
|
+
if (sigintRequested) {
|
|
3151
|
+
instance.unmount();
|
|
3152
|
+
}
|
|
3153
|
+
await instance.waitUntilExit();
|
|
3154
|
+
await writeLifecycleTrace("session-exit-clean");
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
failure = error;
|
|
3157
|
+
} finally {
|
|
3158
|
+
process.removeListener("SIGINT", handleSigint);
|
|
3159
|
+
failure = await runCleanup(failure, () => writeLifecycleTrace("alt-screen-exit"));
|
|
3160
|
+
failure = await runCleanup(failure, () => process.stdout.write(alternateScreenExit));
|
|
3161
|
+
failure = await runCleanup(failure, () => process.stdout.write(cursorShow));
|
|
3162
|
+
}
|
|
3163
|
+
if (failure !== void 0) {
|
|
3164
|
+
throw failure;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
function writeLifecycleTrace(stage) {
|
|
3168
|
+
if (!lifecycleTraceEnabled) {
|
|
3169
|
+
return Promise.resolve();
|
|
3170
|
+
}
|
|
3171
|
+
process.stderr.write(`[skillmux:${stage}]
|
|
3172
|
+
`);
|
|
3173
|
+
return stage === "session-exit-clean" ? sleep(0) : Promise.resolve();
|
|
3174
|
+
}
|
|
3175
|
+
async function runCleanup(failure, cleanup) {
|
|
3176
|
+
try {
|
|
3177
|
+
await cleanup();
|
|
3178
|
+
return failure;
|
|
3179
|
+
} catch (error) {
|
|
3180
|
+
return failure === void 0 ? error : failure;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
function sleep(ms) {
|
|
3184
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3185
|
+
}
|
|
3186
|
+
export {
|
|
3187
|
+
launchTui
|
|
3188
|
+
};
|
|
3189
|
+
//# sourceMappingURL=launch-tui-4TJFQA3L.js.map
|