skillmux 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/chunk-DBEVDI27.js +1826 -0
- package/dist/chunk-DBEVDI27.js.map +1 -0
- package/dist/chunk-UMN3UJFN.js +836 -0
- package/dist/chunk-UMN3UJFN.js.map +1 -0
- package/dist/cli.js +4 -2540
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -2539
- package/dist/index.js.map +1 -1
- package/dist/launch-tui-PHWJPIQZ.js +1790 -0
- package/dist/launch-tui-PHWJPIQZ.js.map +1 -0
- package/package.json +11 -2
|
@@ -0,0 +1,1790 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ManifestValidationError,
|
|
3
|
+
buildEmptyManifest,
|
|
4
|
+
collectDoctorIssues,
|
|
5
|
+
dedupeAndSortIssues,
|
|
6
|
+
discoverAgents,
|
|
7
|
+
formatValidationIssues,
|
|
8
|
+
isPathInside,
|
|
9
|
+
manifestSchema,
|
|
10
|
+
resolveSkillmuxHome,
|
|
11
|
+
runAdopt,
|
|
12
|
+
runDisable,
|
|
13
|
+
runEnable,
|
|
14
|
+
runRemove,
|
|
15
|
+
runScan,
|
|
16
|
+
scanAgentSkills
|
|
17
|
+
} from "./chunk-DBEVDI27.js";
|
|
18
|
+
|
|
19
|
+
// src/tui/launch-tui.tsx
|
|
20
|
+
import { render } from "ink";
|
|
21
|
+
|
|
22
|
+
// src/tui/app.tsx
|
|
23
|
+
import { readFileSync } from "fs";
|
|
24
|
+
import { Text as Text9, useApp, useInput } from "ink";
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
26
|
+
|
|
27
|
+
// src/tui/load-dashboard-state.ts
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
|
|
30
|
+
// src/manifest/read-manifest-snapshot.ts
|
|
31
|
+
import * as fs from "fs/promises";
|
|
32
|
+
import { join, resolve } from "path";
|
|
33
|
+
function normalizeHomePath(home) {
|
|
34
|
+
const resolvedHome = resolve(home);
|
|
35
|
+
return process.platform === "win32" ? resolvedHome.toLowerCase() : resolvedHome;
|
|
36
|
+
}
|
|
37
|
+
async function readManifestSnapshot(home) {
|
|
38
|
+
const manifestPath = join(home, "manifest.json");
|
|
39
|
+
try {
|
|
40
|
+
const contents = await fs.readFile(manifestPath, "utf8");
|
|
41
|
+
const parsed = manifestSchema.safeParse(JSON.parse(contents));
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
throw new ManifestValidationError(
|
|
44
|
+
`Invalid manifest at ${manifestPath}: ${formatValidationIssues(parsed.error)}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (normalizeHomePath(parsed.data.skillmuxHome) !== normalizeHomePath(home)) {
|
|
48
|
+
throw new ManifestValidationError(
|
|
49
|
+
`Invalid manifest at ${manifestPath}: skillmuxHome must match ${home}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return { manifest: parsed.data, exists: true };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
55
|
+
return { manifest: buildEmptyManifest(home), exists: false };
|
|
56
|
+
}
|
|
57
|
+
if (error instanceof SyntaxError) {
|
|
58
|
+
throw new ManifestValidationError(
|
|
59
|
+
`Invalid manifest at ${manifestPath}: malformed JSON`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/tui/dashboard-model.ts
|
|
67
|
+
var emptyCounts = () => ({
|
|
68
|
+
enabledCount: 0,
|
|
69
|
+
disabledCount: 0,
|
|
70
|
+
unmanagedCount: 0,
|
|
71
|
+
issueCount: 0
|
|
72
|
+
});
|
|
73
|
+
function sortById(values) {
|
|
74
|
+
return [...values].sort((left, right) => left.id.localeCompare(right.id));
|
|
75
|
+
}
|
|
76
|
+
function getSelectedAgentId(agents, requestedAgentId) {
|
|
77
|
+
if (requestedAgentId !== void 0) {
|
|
78
|
+
const requestedAgent = agents.find((agent) => agent.id === requestedAgentId);
|
|
79
|
+
if (requestedAgent !== void 0) {
|
|
80
|
+
return requestedAgent.id;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const availableAgent = agents.find(
|
|
84
|
+
(agent) => agent.exists && agent.supportedOnPlatform
|
|
85
|
+
);
|
|
86
|
+
return availableAgent?.id ?? agents[0]?.id ?? null;
|
|
87
|
+
}
|
|
88
|
+
function findEnabledActivation(manifest, skillId, agentId) {
|
|
89
|
+
return manifest.activations.find(
|
|
90
|
+
(activation) => activation.skillId === skillId && activation.agentId === agentId && activation.state === "enabled"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function findAnyActivation(manifest, skillId, agentId) {
|
|
94
|
+
return manifest.activations.find(
|
|
95
|
+
(activation) => activation.skillId === skillId && activation.agentId === agentId
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function buildManagedSkillRow(manifest, skill, agentId) {
|
|
99
|
+
const enabledActivation = findEnabledActivation(manifest, skill.id, agentId);
|
|
100
|
+
if (enabledActivation !== void 0) {
|
|
101
|
+
return {
|
|
102
|
+
id: skill.id,
|
|
103
|
+
kind: "enabled",
|
|
104
|
+
marker: "\u25CF",
|
|
105
|
+
skillId: skill.id,
|
|
106
|
+
name: skill.name,
|
|
107
|
+
path: skill.path,
|
|
108
|
+
agentId,
|
|
109
|
+
activationLinkPath: enabledActivation.linkPath
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
id: skill.id,
|
|
114
|
+
kind: "disabled",
|
|
115
|
+
marker: "\u25CB",
|
|
116
|
+
skillId: skill.id,
|
|
117
|
+
name: skill.name,
|
|
118
|
+
path: skill.path,
|
|
119
|
+
agentId,
|
|
120
|
+
activationLinkPath: findAnyActivation(manifest, skill.id, agentId)?.linkPath ?? null
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function isAdoptableEntry(entry) {
|
|
124
|
+
return entry.kind === "unmanaged-directory" || entry.kind === "unmanaged-link";
|
|
125
|
+
}
|
|
126
|
+
function buildUnmanagedRows(entries, agentId) {
|
|
127
|
+
return entries.filter((entry) => entry.agentId === agentId).filter(isAdoptableEntry).sort(
|
|
128
|
+
(left, right) => `${left.skillName}:${left.path}`.localeCompare(
|
|
129
|
+
`${right.skillName}:${right.path}`
|
|
130
|
+
)
|
|
131
|
+
).map((entry) => ({
|
|
132
|
+
id: `unmanaged:${entry.skillName}`,
|
|
133
|
+
kind: "unmanaged",
|
|
134
|
+
marker: "?",
|
|
135
|
+
skillName: entry.skillName,
|
|
136
|
+
name: entry.skillName,
|
|
137
|
+
path: entry.path,
|
|
138
|
+
agentId,
|
|
139
|
+
entryKind: entry.kind,
|
|
140
|
+
targetPath: entry.targetPath
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
function relatedAgentIdsForIssue(issue, agents) {
|
|
144
|
+
if (issue.path === void 0) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
const issuePath = issue.path;
|
|
148
|
+
return agents.filter((agent) => isPathInside(agent.absoluteSkillsDirectoryPath, issuePath)).map((agent) => agent.id);
|
|
149
|
+
}
|
|
150
|
+
function buildIssueId(issue, agentId) {
|
|
151
|
+
return `issue:${agentId}:${issue.code}:${issue.path ?? issue.message}`;
|
|
152
|
+
}
|
|
153
|
+
function buildIssueRows(issues, agents, agentId) {
|
|
154
|
+
return issues.filter((issue) => {
|
|
155
|
+
const relatedAgentIds = relatedAgentIdsForIssue(issue, agents);
|
|
156
|
+
return relatedAgentIds.includes(agentId);
|
|
157
|
+
}).map((issue) => ({
|
|
158
|
+
id: buildIssueId(issue, agentId),
|
|
159
|
+
kind: "issue",
|
|
160
|
+
marker: "!",
|
|
161
|
+
issueCode: issue.code,
|
|
162
|
+
severity: issue.severity,
|
|
163
|
+
message: issue.message,
|
|
164
|
+
path: issue.path ?? null,
|
|
165
|
+
agentId
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
function buildSkillRowsForAgent(input, agentId) {
|
|
169
|
+
const managedRows = sortById(Object.values(input.manifest.skills)).map(
|
|
170
|
+
(skill) => buildManagedSkillRow(input.manifest, skill, agentId)
|
|
171
|
+
);
|
|
172
|
+
const unmanagedRows = buildUnmanagedRows(input.entries, agentId);
|
|
173
|
+
const issueRows = buildIssueRows(input.issues, input.agents, agentId);
|
|
174
|
+
return [...managedRows, ...unmanagedRows, ...issueRows];
|
|
175
|
+
}
|
|
176
|
+
function countsForRows(rows) {
|
|
177
|
+
const counts = emptyCounts();
|
|
178
|
+
for (const row of rows) {
|
|
179
|
+
if (row.kind === "enabled") {
|
|
180
|
+
counts.enabledCount += 1;
|
|
181
|
+
} else if (row.kind === "disabled") {
|
|
182
|
+
counts.disabledCount += 1;
|
|
183
|
+
} else if (row.kind === "unmanaged") {
|
|
184
|
+
counts.unmanagedCount += 1;
|
|
185
|
+
} else {
|
|
186
|
+
counts.issueCount += 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return counts;
|
|
190
|
+
}
|
|
191
|
+
function countActivationsForAgent(manifest, agentId) {
|
|
192
|
+
return manifest.activations.filter((activation) => activation.agentId === agentId).length;
|
|
193
|
+
}
|
|
194
|
+
function buildAgentRows(input) {
|
|
195
|
+
return sortById(input.agents).map((agent) => {
|
|
196
|
+
const counts = countsForRows(buildSkillRowsForAgent(input, agent.id));
|
|
197
|
+
return {
|
|
198
|
+
id: agent.id,
|
|
199
|
+
name: agent.stableName,
|
|
200
|
+
path: agent.absoluteSkillsDirectoryPath,
|
|
201
|
+
discovery: agent.discovery,
|
|
202
|
+
exists: agent.exists,
|
|
203
|
+
supported: agent.supportedOnPlatform,
|
|
204
|
+
activationCount: countActivationsForAgent(input.manifest, agent.id),
|
|
205
|
+
...counts
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function buildDashboardModel(input) {
|
|
210
|
+
const sortedAgents = sortById(input.agents);
|
|
211
|
+
const selectedAgentId = getSelectedAgentId(
|
|
212
|
+
sortedAgents,
|
|
213
|
+
input.selectedAgentId
|
|
214
|
+
);
|
|
215
|
+
const skills = selectedAgentId === null ? [] : buildSkillRowsForAgent({ ...input, agents: sortedAgents }, selectedAgentId);
|
|
216
|
+
const selectedSkillId = skills.some((row) => row.id === input.selectedSkillId) ? input.selectedSkillId : skills[0]?.id ?? null;
|
|
217
|
+
return {
|
|
218
|
+
agents: buildAgentRows({ ...input, agents: sortedAgents }),
|
|
219
|
+
skills,
|
|
220
|
+
selectedAgentId,
|
|
221
|
+
selectedSkillId,
|
|
222
|
+
lastScanAt: input.manifest.lastScan.at,
|
|
223
|
+
issueCount: input.issues.length
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/tui/load-dashboard-state.ts
|
|
228
|
+
async function loadDashboardState(options = {}) {
|
|
229
|
+
const homeDir = options.homeDir ?? homedir();
|
|
230
|
+
const resolvedSkillmuxHome = resolveSkillmuxHome(homeDir).skillmuxHome;
|
|
231
|
+
const skillmuxHome = options.skillmuxHome ?? resolvedSkillmuxHome;
|
|
232
|
+
const { manifest } = await readManifestSnapshot(skillmuxHome);
|
|
233
|
+
const agents = await discoverAgents({
|
|
234
|
+
homeDir,
|
|
235
|
+
platform: options.platform,
|
|
236
|
+
skillmuxHome
|
|
237
|
+
});
|
|
238
|
+
const scanResults = [];
|
|
239
|
+
for (const agent of agents) {
|
|
240
|
+
scanResults.push(await scanAgentSkills(agent, skillmuxHome));
|
|
241
|
+
}
|
|
242
|
+
const entries = scanResults.flatMap((result) => result.entries);
|
|
243
|
+
const scanIssues = scanResults.flatMap((result) => result.issues);
|
|
244
|
+
const doctorIssues = await collectDoctorIssues({ manifest, agents, entries });
|
|
245
|
+
const issues = dedupeAndSortIssues([...scanIssues, ...doctorIssues]);
|
|
246
|
+
return buildDashboardModel({
|
|
247
|
+
manifest,
|
|
248
|
+
agents,
|
|
249
|
+
entries,
|
|
250
|
+
issues,
|
|
251
|
+
selectedAgentId: options.selectedAgentId,
|
|
252
|
+
selectedSkillId: options.selectedSkillId
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/tui/actions.ts
|
|
257
|
+
var defaultServices = {
|
|
258
|
+
runEnable,
|
|
259
|
+
runDisable,
|
|
260
|
+
runAdopt,
|
|
261
|
+
runRemove,
|
|
262
|
+
runScan,
|
|
263
|
+
reload: loadDashboardState
|
|
264
|
+
};
|
|
265
|
+
function stripTrailingNewlines(output) {
|
|
266
|
+
return output.replace(/[\r\n]+$/u, "");
|
|
267
|
+
}
|
|
268
|
+
function actionLabel(action) {
|
|
269
|
+
return action.charAt(0).toUpperCase() + action.slice(1);
|
|
270
|
+
}
|
|
271
|
+
function errorReason(error) {
|
|
272
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
273
|
+
const firstLine = message.split(/\r?\n/u)[0]?.trim();
|
|
274
|
+
return firstLine === void 0 || firstLine.length === 0 ? "Unknown error" : firstLine;
|
|
275
|
+
}
|
|
276
|
+
function reloadOptions(input) {
|
|
277
|
+
return {
|
|
278
|
+
homeDir: input.homeDir,
|
|
279
|
+
skillmuxHome: input.skillmuxHome,
|
|
280
|
+
platform: input.platform,
|
|
281
|
+
selectedAgentId: input.model.selectedAgentId ?? void 0,
|
|
282
|
+
selectedSkillId: input.model.selectedSkillId ?? void 0
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async function reloadAfterCommand(input, services, output) {
|
|
286
|
+
return {
|
|
287
|
+
model: await services.reload(reloadOptions(input)),
|
|
288
|
+
statusMessage: stripTrailingNewlines(output)
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function refusal(model, statusMessage) {
|
|
292
|
+
return { model, statusMessage };
|
|
293
|
+
}
|
|
294
|
+
function resolveSelectedSkill(model) {
|
|
295
|
+
if (model.selectedSkillId === null) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return model.skills.find((row) => row.id === model.selectedSkillId) ?? null;
|
|
299
|
+
}
|
|
300
|
+
function resolveSelectedAgent(model) {
|
|
301
|
+
if (model.selectedAgentId === null) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
return model.agents.find((row) => row.id === model.selectedAgentId) ?? null;
|
|
305
|
+
}
|
|
306
|
+
async function dispatchTuiAction(input) {
|
|
307
|
+
const services = { ...defaultServices, ...input.services };
|
|
308
|
+
try {
|
|
309
|
+
if (input.action === "scan") {
|
|
310
|
+
const result2 = await services.runScan({
|
|
311
|
+
homeDir: input.homeDir,
|
|
312
|
+
skillmuxHome: input.skillmuxHome,
|
|
313
|
+
platform: input.platform
|
|
314
|
+
});
|
|
315
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
316
|
+
}
|
|
317
|
+
if (input.action === "adopt-all") {
|
|
318
|
+
const selectedAgent = resolveSelectedAgent(input.model);
|
|
319
|
+
if (selectedAgent === null) {
|
|
320
|
+
return refusal(input.model, "Select an agent first");
|
|
321
|
+
}
|
|
322
|
+
if (selectedAgent.unmanagedCount <= 0) {
|
|
323
|
+
return refusal(input.model, "No unmanaged skills to adopt for this agent");
|
|
324
|
+
}
|
|
325
|
+
const result2 = await services.runAdopt({
|
|
326
|
+
homeDir: input.homeDir,
|
|
327
|
+
skillmuxHome: input.skillmuxHome,
|
|
328
|
+
agent: selectedAgent.id
|
|
329
|
+
});
|
|
330
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
331
|
+
}
|
|
332
|
+
const selectedSkill = resolveSelectedSkill(input.model);
|
|
333
|
+
if (selectedSkill === null) {
|
|
334
|
+
return refusal(input.model, "Select a skill first");
|
|
335
|
+
}
|
|
336
|
+
if (input.action === "toggle") {
|
|
337
|
+
if (selectedSkill.kind === "enabled") {
|
|
338
|
+
const result2 = await services.runDisable({
|
|
339
|
+
homeDir: input.homeDir,
|
|
340
|
+
skillmuxHome: input.skillmuxHome,
|
|
341
|
+
skill: selectedSkill.skillId,
|
|
342
|
+
agent: selectedSkill.agentId
|
|
343
|
+
});
|
|
344
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
345
|
+
}
|
|
346
|
+
if (selectedSkill.kind === "disabled") {
|
|
347
|
+
const result2 = await services.runEnable({
|
|
348
|
+
homeDir: input.homeDir,
|
|
349
|
+
skillmuxHome: input.skillmuxHome,
|
|
350
|
+
skill: selectedSkill.skillId,
|
|
351
|
+
agent: selectedSkill.agentId
|
|
352
|
+
});
|
|
353
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
354
|
+
}
|
|
355
|
+
return refusal(input.model, "Toggle is only available for managed rows");
|
|
356
|
+
}
|
|
357
|
+
if (input.action === "adopt") {
|
|
358
|
+
if (selectedSkill.kind !== "unmanaged") {
|
|
359
|
+
return refusal(input.model, "Adopt is only available for unmanaged rows");
|
|
360
|
+
}
|
|
361
|
+
const result2 = await services.runAdopt({
|
|
362
|
+
homeDir: input.homeDir,
|
|
363
|
+
skillmuxHome: input.skillmuxHome,
|
|
364
|
+
agent: selectedSkill.agentId,
|
|
365
|
+
skill: selectedSkill.skillName
|
|
366
|
+
});
|
|
367
|
+
return reloadAfterCommand(input, services, result2.output);
|
|
368
|
+
}
|
|
369
|
+
if (selectedSkill.kind === "enabled") {
|
|
370
|
+
return refusal(input.model, "Disable this skill before removing it");
|
|
371
|
+
}
|
|
372
|
+
if (selectedSkill.kind !== "disabled") {
|
|
373
|
+
return refusal(input.model, "Remove is only available for disabled rows");
|
|
374
|
+
}
|
|
375
|
+
const result = await services.runRemove({
|
|
376
|
+
homeDir: input.homeDir,
|
|
377
|
+
skillmuxHome: input.skillmuxHome,
|
|
378
|
+
skill: selectedSkill.skillId
|
|
379
|
+
});
|
|
380
|
+
return reloadAfterCommand(input, services, result.output);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
return {
|
|
383
|
+
model: input.model,
|
|
384
|
+
statusMessage: `${actionLabel(input.action)} failed: ${errorReason(error)}`
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/tui/components/Dashboard.tsx
|
|
390
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
391
|
+
|
|
392
|
+
// src/tui/state.ts
|
|
393
|
+
var focusOrder = ["agents", "skills"];
|
|
394
|
+
function clampCursor(cursor, rowCount) {
|
|
395
|
+
if (rowCount <= 0) {
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
return Math.min(Math.max(cursor, 0), rowCount - 1);
|
|
399
|
+
}
|
|
400
|
+
function normalizeQuery(query) {
|
|
401
|
+
return query.trim().toLocaleLowerCase();
|
|
402
|
+
}
|
|
403
|
+
function includesQuery(values, query) {
|
|
404
|
+
const normalizedQuery = normalizeQuery(query);
|
|
405
|
+
if (normalizedQuery.length === 0) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
return values.some(
|
|
409
|
+
(value) => (value ?? "").toLocaleLowerCase().includes(normalizedQuery)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
function isRelevantAgentRow(row, selectedAgentId) {
|
|
413
|
+
return row.id === selectedAgentId || row.exists || (row.activationCount ?? 0) > 0 || row.enabledCount > 0 || row.unmanagedCount > 0 || row.issueCount > 0;
|
|
414
|
+
}
|
|
415
|
+
function skillMatchesQuery(row, query) {
|
|
416
|
+
if (row.kind === "issue") {
|
|
417
|
+
return includesQuery(
|
|
418
|
+
[row.id, row.issueCode, row.message, row.path, row.agentId],
|
|
419
|
+
query
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (row.kind === "unmanaged") {
|
|
423
|
+
return includesQuery(
|
|
424
|
+
[row.id, row.skillName, row.name, row.path, row.agentId],
|
|
425
|
+
query
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
return includesQuery([row.id, row.skillId, row.name, row.path, row.agentId], query);
|
|
429
|
+
}
|
|
430
|
+
function replaceModelSelection(state, selection) {
|
|
431
|
+
return {
|
|
432
|
+
...state,
|
|
433
|
+
model: {
|
|
434
|
+
...state.model,
|
|
435
|
+
...selection
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function clearTransientIntent(state) {
|
|
440
|
+
return {
|
|
441
|
+
...state,
|
|
442
|
+
pendingAction: null,
|
|
443
|
+
statusMessage: null
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function restoreSearchSelection(state) {
|
|
447
|
+
if (state.search === null) {
|
|
448
|
+
return state;
|
|
449
|
+
}
|
|
450
|
+
const previousSelection = state.search.previousSelection;
|
|
451
|
+
return {
|
|
452
|
+
...state,
|
|
453
|
+
model: {
|
|
454
|
+
...state.model,
|
|
455
|
+
selectedAgentId: previousSelection.selectedAgentId,
|
|
456
|
+
selectedSkillId: previousSelection.selectedSkillId
|
|
457
|
+
},
|
|
458
|
+
agentCursor: previousSelection.agentCursor,
|
|
459
|
+
skillCursor: previousSelection.skillCursor,
|
|
460
|
+
pendingAgentId: previousSelection.pendingAgentId
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function searchHasVisibleResults(state) {
|
|
464
|
+
if (state.search === null) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
return state.search.panel === "agents" ? getVisibleAgents(state).length > 0 : getVisibleSkills(state).length > 0;
|
|
468
|
+
}
|
|
469
|
+
function selectedAgentIndex(model) {
|
|
470
|
+
if (model.selectedAgentId === null) {
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
const index = model.agents.findIndex((row) => row.id === model.selectedAgentId);
|
|
474
|
+
return index < 0 ? 0 : index;
|
|
475
|
+
}
|
|
476
|
+
function selectedSkillIndex(model) {
|
|
477
|
+
if (model.selectedSkillId === null) {
|
|
478
|
+
return 0;
|
|
479
|
+
}
|
|
480
|
+
const index = model.skills.findIndex((row) => row.id === model.selectedSkillId);
|
|
481
|
+
return index < 0 ? 0 : index;
|
|
482
|
+
}
|
|
483
|
+
function selectedAgentSkill(skills, agentId) {
|
|
484
|
+
if (agentId === null) {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
return skills.find((row) => row.agentId === agentId) ?? null;
|
|
488
|
+
}
|
|
489
|
+
function selectedAgentRow(state) {
|
|
490
|
+
if (state.model.selectedAgentId === null) {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
return state.model.agents.find((row) => row.id === state.model.selectedAgentId) ?? null;
|
|
494
|
+
}
|
|
495
|
+
function moveFocus(focus, direction) {
|
|
496
|
+
const currentIndex = focusOrder.indexOf(focus);
|
|
497
|
+
const nextIndex = (currentIndex + direction + focusOrder.length) % focusOrder.length;
|
|
498
|
+
return focusOrder[nextIndex] ?? "agents";
|
|
499
|
+
}
|
|
500
|
+
function stateWithAgentCursor(state, cursor) {
|
|
501
|
+
const visibleAgents = getVisibleAgents(state);
|
|
502
|
+
const agentCursor = clampCursor(cursor, visibleAgents.length);
|
|
503
|
+
const selectedAgent = visibleAgents[agentCursor] ?? null;
|
|
504
|
+
const selectedSkill = selectedAgentSkill(state.model.skills, selectedAgent?.id ?? null);
|
|
505
|
+
const previousAgentId = state.model.selectedAgentId;
|
|
506
|
+
const selectedAgentId = selectedAgent?.id ?? null;
|
|
507
|
+
const model = {
|
|
508
|
+
...state.model,
|
|
509
|
+
selectedAgentId,
|
|
510
|
+
selectedSkillId: selectedSkill?.id ?? null
|
|
511
|
+
};
|
|
512
|
+
const skillCursor = selectedSkill === null ? 0 : clampCursor(
|
|
513
|
+
getVisibleSkills({ ...state, model }).findIndex(
|
|
514
|
+
(row) => row.id === selectedSkill.id
|
|
515
|
+
),
|
|
516
|
+
getVisibleSkills({ ...state, model }).length
|
|
517
|
+
);
|
|
518
|
+
return {
|
|
519
|
+
...state,
|
|
520
|
+
model,
|
|
521
|
+
agentCursor,
|
|
522
|
+
skillCursor,
|
|
523
|
+
pendingAgentId: selectedAgentId !== null && selectedAgentId !== previousAgentId ? selectedAgentId : state.pendingAgentId
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function stateWithSkillCursor(state, cursor) {
|
|
527
|
+
const visibleSkills = getVisibleSkills(state);
|
|
528
|
+
const skillCursor = clampCursor(cursor, visibleSkills.length);
|
|
529
|
+
const selectedSkill = visibleSkills[skillCursor] ?? null;
|
|
530
|
+
return replaceModelSelection(
|
|
531
|
+
{
|
|
532
|
+
...state,
|
|
533
|
+
skillCursor
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
537
|
+
selectedSkillId: selectedSkill?.id ?? null
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
function moveCursor(state, cursor) {
|
|
542
|
+
if (state.focus === "agents") {
|
|
543
|
+
return stateWithAgentCursor(state, cursor);
|
|
544
|
+
}
|
|
545
|
+
if (state.focus === "skills") {
|
|
546
|
+
return stateWithSkillCursor(state, cursor);
|
|
547
|
+
}
|
|
548
|
+
return state;
|
|
549
|
+
}
|
|
550
|
+
function isModalBackgroundEvent(event) {
|
|
551
|
+
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 === "request-adopt" || event.type === "request-adopt-all" || event.type === "request-remove" || event.type === "request-toggle" || event.type === "request-scan" || event.type === "clear-pending-action";
|
|
552
|
+
}
|
|
553
|
+
function getVisibleAgents(state) {
|
|
554
|
+
if (state.search?.panel !== "agents") {
|
|
555
|
+
return state.model.agents.filter(
|
|
556
|
+
(row) => isRelevantAgentRow(row, state.model.selectedAgentId)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return state.model.agents.filter(
|
|
560
|
+
(row) => includesQuery([row.id, row.name, row.path, row.discovery], state.search?.query ?? "")
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
function getVisibleSkills(state) {
|
|
564
|
+
if (state.model.selectedAgentId === null) {
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
const agentSkills = state.model.skills.filter((row) => row.agentId === state.model.selectedAgentId);
|
|
568
|
+
if (state.search?.panel !== "skills") {
|
|
569
|
+
return agentSkills;
|
|
570
|
+
}
|
|
571
|
+
return agentSkills.filter((row) => skillMatchesQuery(row, state.search?.query ?? ""));
|
|
572
|
+
}
|
|
573
|
+
function getSelectedSkill(state) {
|
|
574
|
+
if (state.model.selectedSkillId === null) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return state.model.skills.find((row) => row.id === state.model.selectedSkillId) ?? null;
|
|
578
|
+
}
|
|
579
|
+
function getAvailableActions(state) {
|
|
580
|
+
const selectedSkill = getSelectedSkill(state);
|
|
581
|
+
const selectedAgent = selectedAgentRow(state);
|
|
582
|
+
const canAcceptActions = state.modal === null && !state.busy;
|
|
583
|
+
const hasFocusedSkill = canAcceptActions && state.focus === "skills";
|
|
584
|
+
return {
|
|
585
|
+
toggle: hasFocusedSkill && (selectedSkill?.kind === "enabled" || selectedSkill?.kind === "disabled"),
|
|
586
|
+
adopt: hasFocusedSkill && selectedSkill?.kind === "unmanaged",
|
|
587
|
+
adoptAll: canAcceptActions && (selectedAgent?.unmanagedCount ?? 0) > 0,
|
|
588
|
+
remove: hasFocusedSkill && selectedSkill?.kind === "disabled",
|
|
589
|
+
scan: canAcceptActions,
|
|
590
|
+
help: canAcceptActions
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function consumeActionIntent(state) {
|
|
594
|
+
return {
|
|
595
|
+
state: {
|
|
596
|
+
...state,
|
|
597
|
+
pendingAction: null
|
|
598
|
+
},
|
|
599
|
+
action: state.pendingAction
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function consumeAgentSelectionIntent(state) {
|
|
603
|
+
return {
|
|
604
|
+
state: {
|
|
605
|
+
...state,
|
|
606
|
+
pendingAgentId: null
|
|
607
|
+
},
|
|
608
|
+
agentId: state.pendingAgentId
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function createInitialTuiState(model) {
|
|
612
|
+
const state = {
|
|
613
|
+
model,
|
|
614
|
+
focus: "agents",
|
|
615
|
+
agentCursor: selectedAgentIndex(model),
|
|
616
|
+
skillCursor: selectedSkillIndex(model),
|
|
617
|
+
search: null,
|
|
618
|
+
statusMessage: null,
|
|
619
|
+
modal: null,
|
|
620
|
+
busy: false,
|
|
621
|
+
pendingAction: null,
|
|
622
|
+
pendingAgentId: null
|
|
623
|
+
};
|
|
624
|
+
return {
|
|
625
|
+
...state,
|
|
626
|
+
agentCursor: clampCursor(state.agentCursor, getVisibleAgents(state).length),
|
|
627
|
+
skillCursor: clampCursor(state.skillCursor, getVisibleSkills(state).length)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function updateTuiState(state, event) {
|
|
631
|
+
if (state.modal !== null) {
|
|
632
|
+
if (event.type === "close") {
|
|
633
|
+
return {
|
|
634
|
+
...state,
|
|
635
|
+
modal: null,
|
|
636
|
+
pendingAction: null
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (event.type === "set-busy") {
|
|
640
|
+
return {
|
|
641
|
+
...state,
|
|
642
|
+
busy: event.busy
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (event.type === "set-status") {
|
|
646
|
+
return {
|
|
647
|
+
...state,
|
|
648
|
+
statusMessage: event.message
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (isModalBackgroundEvent(event)) {
|
|
652
|
+
return state;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (state.busy) {
|
|
656
|
+
if (event.type === "set-busy") {
|
|
657
|
+
return {
|
|
658
|
+
...state,
|
|
659
|
+
busy: event.busy
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (event.type === "set-status") {
|
|
663
|
+
return {
|
|
664
|
+
...state,
|
|
665
|
+
statusMessage: event.message
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (event.type === "request-toggle" || event.type === "request-adopt" || event.type === "request-remove" || event.type === "request-scan" || event.type === "open-help") {
|
|
669
|
+
return {
|
|
670
|
+
...state,
|
|
671
|
+
pendingAction: null
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const readyState = clearTransientIntent(state);
|
|
676
|
+
if (event.type === "focus-next") {
|
|
677
|
+
return {
|
|
678
|
+
...readyState,
|
|
679
|
+
focus: moveFocus(state.focus, 1)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
if (event.type === "focus-previous") {
|
|
683
|
+
return {
|
|
684
|
+
...readyState,
|
|
685
|
+
focus: moveFocus(state.focus, -1)
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (event.type === "next-row") {
|
|
689
|
+
return moveCursor(
|
|
690
|
+
readyState,
|
|
691
|
+
state.focus === "agents" ? state.agentCursor + 1 : state.skillCursor + 1
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (event.type === "previous-row") {
|
|
695
|
+
return moveCursor(
|
|
696
|
+
readyState,
|
|
697
|
+
state.focus === "agents" ? state.agentCursor - 1 : state.skillCursor - 1
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
if (event.type === "first-row") {
|
|
701
|
+
return moveCursor(readyState, 0);
|
|
702
|
+
}
|
|
703
|
+
if (event.type === "last-row") {
|
|
704
|
+
const rowCount = state.focus === "agents" ? getVisibleAgents(state).length : getVisibleSkills(state).length;
|
|
705
|
+
return moveCursor(readyState, rowCount - 1);
|
|
706
|
+
}
|
|
707
|
+
if (event.type === "open-search") {
|
|
708
|
+
if (state.focus !== "agents" && state.focus !== "skills") {
|
|
709
|
+
return {
|
|
710
|
+
...readyState,
|
|
711
|
+
statusMessage: "Search is available for agents and skills"
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
...readyState,
|
|
716
|
+
search: {
|
|
717
|
+
panel: state.focus,
|
|
718
|
+
query: "",
|
|
719
|
+
previousSelection: {
|
|
720
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
721
|
+
selectedSkillId: state.model.selectedSkillId,
|
|
722
|
+
agentCursor: state.agentCursor,
|
|
723
|
+
skillCursor: state.skillCursor,
|
|
724
|
+
pendingAgentId: state.pendingAgentId
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
if (event.type === "search-query-changed") {
|
|
730
|
+
if (state.search === null) {
|
|
731
|
+
return readyState;
|
|
732
|
+
}
|
|
733
|
+
const searchedState = {
|
|
734
|
+
...readyState,
|
|
735
|
+
search: {
|
|
736
|
+
...state.search,
|
|
737
|
+
query: event.query
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
const rowCount = searchedState.search.panel === "agents" ? getVisibleAgents(searchedState).length : getVisibleSkills(searchedState).length;
|
|
741
|
+
return searchedState.search.panel === "agents" ? stateWithAgentCursor(searchedState, clampCursor(searchedState.agentCursor, rowCount)) : stateWithSkillCursor(searchedState, clampCursor(searchedState.skillCursor, rowCount));
|
|
742
|
+
}
|
|
743
|
+
if (event.type === "close") {
|
|
744
|
+
if (state.search !== null) {
|
|
745
|
+
return {
|
|
746
|
+
...restoreSearchSelection(readyState),
|
|
747
|
+
search: null
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
if (state.modal !== null) {
|
|
751
|
+
return {
|
|
752
|
+
...readyState,
|
|
753
|
+
modal: null
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return readyState;
|
|
757
|
+
}
|
|
758
|
+
if (event.type === "submit-search") {
|
|
759
|
+
if (state.search !== null) {
|
|
760
|
+
if (!searchHasVisibleResults(state)) {
|
|
761
|
+
return {
|
|
762
|
+
...restoreSearchSelection(readyState),
|
|
763
|
+
search: null
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
return {
|
|
767
|
+
...readyState,
|
|
768
|
+
search: null
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
return readyState;
|
|
772
|
+
}
|
|
773
|
+
if (event.type === "open-help") {
|
|
774
|
+
return {
|
|
775
|
+
...readyState,
|
|
776
|
+
modal: { kind: "help" }
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
if (event.type === "request-adopt") {
|
|
780
|
+
if (state.focus !== "skills") {
|
|
781
|
+
return readyState;
|
|
782
|
+
}
|
|
783
|
+
const selectedSkill = getSelectedSkill(state);
|
|
784
|
+
if (selectedSkill?.kind !== "unmanaged") {
|
|
785
|
+
return {
|
|
786
|
+
...readyState,
|
|
787
|
+
statusMessage: "Adopt is only available for unmanaged rows"
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
...readyState,
|
|
792
|
+
modal: {
|
|
793
|
+
kind: "confirm-adopt",
|
|
794
|
+
skillId: selectedSkill.skillName,
|
|
795
|
+
agentId: selectedSkill.agentId
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (event.type === "request-adopt-all") {
|
|
800
|
+
const selectedAgent = selectedAgentRow(state);
|
|
801
|
+
if (selectedAgent === null) {
|
|
802
|
+
return {
|
|
803
|
+
...readyState,
|
|
804
|
+
statusMessage: "Select an agent first"
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (selectedAgent.unmanagedCount <= 0) {
|
|
808
|
+
return {
|
|
809
|
+
...readyState,
|
|
810
|
+
statusMessage: "No unmanaged skills to adopt for this agent"
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
...readyState,
|
|
815
|
+
modal: {
|
|
816
|
+
kind: "confirm-adopt-all",
|
|
817
|
+
agentId: selectedAgent.id,
|
|
818
|
+
unmanagedCount: selectedAgent.unmanagedCount
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
if (event.type === "request-remove") {
|
|
823
|
+
if (state.focus !== "skills") {
|
|
824
|
+
return readyState;
|
|
825
|
+
}
|
|
826
|
+
const selectedSkill = getSelectedSkill(state);
|
|
827
|
+
if (selectedSkill?.kind !== "disabled") {
|
|
828
|
+
return {
|
|
829
|
+
...readyState,
|
|
830
|
+
statusMessage: selectedSkill?.kind === "enabled" ? "Disable this skill before removing it" : "Remove is only available for disabled rows"
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
...readyState,
|
|
835
|
+
modal: {
|
|
836
|
+
kind: "confirm-remove",
|
|
837
|
+
skillId: selectedSkill.skillId
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
if (event.type === "request-toggle") {
|
|
842
|
+
if (state.focus !== "skills") {
|
|
843
|
+
return readyState;
|
|
844
|
+
}
|
|
845
|
+
const selectedSkill = getSelectedSkill(state);
|
|
846
|
+
if (selectedSkill?.kind !== "enabled" && selectedSkill?.kind !== "disabled") {
|
|
847
|
+
return {
|
|
848
|
+
...readyState,
|
|
849
|
+
statusMessage: "Toggle is only available for managed rows"
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
...readyState,
|
|
854
|
+
pendingAction: "toggle"
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
if (event.type === "request-scan") {
|
|
858
|
+
return {
|
|
859
|
+
...readyState,
|
|
860
|
+
pendingAction: "scan"
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
if (event.type === "set-busy") {
|
|
864
|
+
return {
|
|
865
|
+
...readyState,
|
|
866
|
+
busy: event.busy
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (event.type === "set-status") {
|
|
870
|
+
return {
|
|
871
|
+
...readyState,
|
|
872
|
+
statusMessage: event.message
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
return readyState;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/tui/components/AgentList.tsx
|
|
879
|
+
import { Box, Text } from "ink";
|
|
880
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
881
|
+
function statusMarker(agent) {
|
|
882
|
+
if (!agent.supported) {
|
|
883
|
+
return "!";
|
|
884
|
+
}
|
|
885
|
+
if (!agent.exists) {
|
|
886
|
+
return "?";
|
|
887
|
+
}
|
|
888
|
+
return "*";
|
|
889
|
+
}
|
|
890
|
+
function statusColor(agent) {
|
|
891
|
+
if (!agent.supported) {
|
|
892
|
+
return "red";
|
|
893
|
+
}
|
|
894
|
+
if (!agent.exists || agent.issueCount > 0) {
|
|
895
|
+
return "yellow";
|
|
896
|
+
}
|
|
897
|
+
return "green";
|
|
898
|
+
}
|
|
899
|
+
function AgentList({
|
|
900
|
+
agents,
|
|
901
|
+
selectedAgentId,
|
|
902
|
+
focused,
|
|
903
|
+
searchQuery,
|
|
904
|
+
width = 24,
|
|
905
|
+
height = 18
|
|
906
|
+
}) {
|
|
907
|
+
const emptyMessage = searchQuery !== void 0 && searchQuery.trim().length > 0 ? "No matching agents" : "No agents found";
|
|
908
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, height, children: [
|
|
909
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: focused ? "cyan" : void 0, children: "Agents" }),
|
|
910
|
+
agents.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: emptyMessage }) : agents.map((agent) => {
|
|
911
|
+
const selected = agent.id === selectedAgentId;
|
|
912
|
+
const selectionPrefix = selected ? ">" : " ";
|
|
913
|
+
return /* @__PURE__ */ jsxs(Text, { inverse: selected, children: [
|
|
914
|
+
/* @__PURE__ */ jsx(Text, { color: statusColor(agent), children: statusMarker(agent) }),
|
|
915
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
916
|
+
selectionPrefix,
|
|
917
|
+
" ",
|
|
918
|
+
agent.name
|
|
919
|
+
] })
|
|
920
|
+
] }, agent.id);
|
|
921
|
+
})
|
|
922
|
+
] });
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/tui/components/ConfirmDialog.tsx
|
|
926
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
927
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
928
|
+
var confirmDialogHeight = 4;
|
|
929
|
+
function confirmationText(modal) {
|
|
930
|
+
if (modal.kind === "confirm-adopt") {
|
|
931
|
+
return `Adopt ${modal.skillId} for ${modal.agentId}?`;
|
|
932
|
+
}
|
|
933
|
+
if (modal.kind === "confirm-adopt-all") {
|
|
934
|
+
return `Adopt all unmanaged skills for ${modal.agentId}?`;
|
|
935
|
+
}
|
|
936
|
+
return `Remove ${modal.skillId} from SkillMux?`;
|
|
937
|
+
}
|
|
938
|
+
function confirmationDetails(modal) {
|
|
939
|
+
if (modal.kind !== "confirm-adopt-all") {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
return `${modal.unmanagedCount} unmanaged skills will be moved under SkillMux management.`;
|
|
943
|
+
}
|
|
944
|
+
function ConfirmDialog({ modal }) {
|
|
945
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: confirmDialogHeight, children: [
|
|
946
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, color: modal.kind === "confirm-remove" ? "yellow" : "cyan", children: "Confirm" }),
|
|
947
|
+
/* @__PURE__ */ jsx2(Text2, { children: confirmationText(modal) }),
|
|
948
|
+
confirmationDetails(modal) === null ? null : /* @__PURE__ */ jsx2(Text2, { children: confirmationDetails(modal) }),
|
|
949
|
+
/* @__PURE__ */ jsx2(Text2, { children: "[y] confirm [Esc] cancel" })
|
|
950
|
+
] });
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/tui/components/DetailPane.tsx
|
|
954
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
955
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
956
|
+
function compactPath(value, maxLength) {
|
|
957
|
+
if (value.length <= maxLength) {
|
|
958
|
+
return value;
|
|
959
|
+
}
|
|
960
|
+
const separator = value.includes("\\") ? "\\" : "/";
|
|
961
|
+
const parts = value.split(/[\\/]+/).filter((part) => part.length > 0);
|
|
962
|
+
let suffix = parts.at(-1) ?? value;
|
|
963
|
+
for (let index = parts.length - 2; index >= 0; index -= 1) {
|
|
964
|
+
const candidate = `${parts[index]}${separator}${suffix}`;
|
|
965
|
+
if (`...${separator}${candidate}`.length > maxLength) {
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
suffix = candidate;
|
|
969
|
+
}
|
|
970
|
+
const shortened = `...${separator}${suffix}`;
|
|
971
|
+
if (shortened.length <= maxLength) {
|
|
972
|
+
return shortened;
|
|
973
|
+
}
|
|
974
|
+
if (maxLength <= 3) {
|
|
975
|
+
return ".".repeat(maxLength);
|
|
976
|
+
}
|
|
977
|
+
return `...${suffix.slice(-(maxLength - 3))}`;
|
|
978
|
+
}
|
|
979
|
+
function detailLines(skill) {
|
|
980
|
+
if (skill.kind === "enabled") {
|
|
981
|
+
return [
|
|
982
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
983
|
+
{ label: "Status", value: "enabled", compact: false },
|
|
984
|
+
{ label: "Store", value: skill.path, compact: true },
|
|
985
|
+
{ label: "Link", value: skill.activationLinkPath, compact: true }
|
|
986
|
+
];
|
|
987
|
+
}
|
|
988
|
+
if (skill.kind === "disabled") {
|
|
989
|
+
return [
|
|
990
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
991
|
+
{ label: "Status", value: "disabled", compact: false },
|
|
992
|
+
{ label: "Store", value: skill.path, compact: true },
|
|
993
|
+
{
|
|
994
|
+
label: "Link",
|
|
995
|
+
value: skill.activationLinkPath ?? "not linked",
|
|
996
|
+
compact: skill.activationLinkPath !== null
|
|
997
|
+
}
|
|
998
|
+
];
|
|
999
|
+
}
|
|
1000
|
+
if (skill.kind === "unmanaged") {
|
|
1001
|
+
return [
|
|
1002
|
+
{ label: "Name", value: skill.name, compact: false },
|
|
1003
|
+
{ label: "Status", value: "unmanaged", compact: false },
|
|
1004
|
+
{ label: "Entry", value: skill.entryKind, compact: false },
|
|
1005
|
+
{ label: "Path", value: skill.path, compact: true }
|
|
1006
|
+
];
|
|
1007
|
+
}
|
|
1008
|
+
return [
|
|
1009
|
+
{ label: "Status", value: "issue", compact: false },
|
|
1010
|
+
{ label: "Code", value: skill.issueCode, compact: false },
|
|
1011
|
+
{ label: "Severity", value: skill.severity, compact: false },
|
|
1012
|
+
{ label: "Message", value: skill.message, compact: false },
|
|
1013
|
+
{ label: "Path", value: skill.path ?? "none", compact: skill.path !== null }
|
|
1014
|
+
];
|
|
1015
|
+
}
|
|
1016
|
+
function DetailPane({
|
|
1017
|
+
selectedAgent,
|
|
1018
|
+
selectedSkill,
|
|
1019
|
+
focused: _focused,
|
|
1020
|
+
loadingAgentName = null,
|
|
1021
|
+
width = 28,
|
|
1022
|
+
height = 18
|
|
1023
|
+
}) {
|
|
1024
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width, height, children: [
|
|
1025
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Detail" }),
|
|
1026
|
+
selectedAgent === null ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Select an agent" }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1027
|
+
"Agent: ",
|
|
1028
|
+
selectedAgent.name
|
|
1029
|
+
] }),
|
|
1030
|
+
selectedSkill === null ? loadingAgentName !== null ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1031
|
+
"Loading details for ",
|
|
1032
|
+
loadingAgentName,
|
|
1033
|
+
"..."
|
|
1034
|
+
] }) : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Select a skill row" }) : detailLines(selectedSkill).map(({ label, value, compact }) => {
|
|
1035
|
+
const valueWidth = Math.max(width - (label.length + 2), 8);
|
|
1036
|
+
const renderedValue = compact ? compactPath(value, valueWidth) : value;
|
|
1037
|
+
return /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1038
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1039
|
+
label,
|
|
1040
|
+
": "
|
|
1041
|
+
] }),
|
|
1042
|
+
/* @__PURE__ */ jsx3(Text3, { children: renderedValue })
|
|
1043
|
+
] }, label);
|
|
1044
|
+
})
|
|
1045
|
+
] });
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/tui/components/Footer.tsx
|
|
1049
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1050
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1051
|
+
var agentLegend = "Agent icons: * ready yellow * issues ? missing ! unsupported";
|
|
1052
|
+
var skillLegend = "Skill markers: \u25CF enabled \u25CB disabled ? unmanaged ! issue";
|
|
1053
|
+
function Footer({ actions, search }) {
|
|
1054
|
+
const shortcuts = [
|
|
1055
|
+
"[Left/Right]focus",
|
|
1056
|
+
actions.toggle ? "[Space]toggle" : null,
|
|
1057
|
+
actions.adopt ? "[a]adopt" : null,
|
|
1058
|
+
actions.adoptAll ? "[Shift+A]adopt all" : null,
|
|
1059
|
+
actions.remove ? "[r]remove" : null,
|
|
1060
|
+
actions.scan ? "[s]scan" : null,
|
|
1061
|
+
actions.help ? "[?]help" : null,
|
|
1062
|
+
"[q]quit"
|
|
1063
|
+
].filter((shortcut) => shortcut !== null);
|
|
1064
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", height: 3, children: search === null ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1065
|
+
/* @__PURE__ */ jsx4(Text4, { children: shortcuts.join(" ") }),
|
|
1066
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: agentLegend }),
|
|
1067
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: skillLegend })
|
|
1068
|
+
] }) : /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1069
|
+
/* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "/" }),
|
|
1070
|
+
/* @__PURE__ */ jsx4(Text4, { children: search.query }),
|
|
1071
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " [Enter]keep [Esc]cancel" })
|
|
1072
|
+
] }) });
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/tui/components/HelpOverlay.tsx
|
|
1076
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1077
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1078
|
+
function HelpOverlay() {
|
|
1079
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: 8, children: [
|
|
1080
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Help" }),
|
|
1081
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1082
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Navigation" }),
|
|
1083
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": Left/Right switch panels, j/k or Up/Down move, g/G jump." })
|
|
1084
|
+
] }),
|
|
1085
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1086
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Actions" }),
|
|
1087
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": Space toggles, a adopts, Shift+A current-agent bulk adopt, r removes, s scans." })
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1090
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Search" }),
|
|
1091
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": / filters the focused list, Enter keeps the result, Esc cancels." })
|
|
1092
|
+
] }),
|
|
1093
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1094
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Agent icons" }),
|
|
1095
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": * ready, yellow * issues, ? missing, ! unsupported." })
|
|
1096
|
+
] }),
|
|
1097
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1098
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Skill markers" }),
|
|
1099
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": \u25CF enabled, \u25CB disabled, ? unmanaged, ! issue." })
|
|
1100
|
+
] }),
|
|
1101
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1102
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Safety" }),
|
|
1103
|
+
/* @__PURE__ */ jsx5(Text5, { children: ": Toggle, adopt, remove, and scan can update SkillMux state and agent links." })
|
|
1104
|
+
] })
|
|
1105
|
+
] });
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// src/tui/components/SkillList.tsx
|
|
1109
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1110
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1111
|
+
function markerColor(skill) {
|
|
1112
|
+
if (skill.kind === "enabled") {
|
|
1113
|
+
return "green";
|
|
1114
|
+
}
|
|
1115
|
+
if (skill.kind === "issue") {
|
|
1116
|
+
return skill.severity === "error" ? "red" : "yellow";
|
|
1117
|
+
}
|
|
1118
|
+
if (skill.kind === "unmanaged") {
|
|
1119
|
+
return "yellow";
|
|
1120
|
+
}
|
|
1121
|
+
return "gray";
|
|
1122
|
+
}
|
|
1123
|
+
function skillLabel(skill) {
|
|
1124
|
+
if (skill.kind === "issue") {
|
|
1125
|
+
return skill.issueCode;
|
|
1126
|
+
}
|
|
1127
|
+
return skill.name;
|
|
1128
|
+
}
|
|
1129
|
+
function SkillList({
|
|
1130
|
+
agentId,
|
|
1131
|
+
skills,
|
|
1132
|
+
selectedSkillId,
|
|
1133
|
+
focused,
|
|
1134
|
+
searchQuery,
|
|
1135
|
+
loadingAgentName = null,
|
|
1136
|
+
width = 28,
|
|
1137
|
+
height = 18
|
|
1138
|
+
}) {
|
|
1139
|
+
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";
|
|
1140
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, height, children: [
|
|
1141
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: focused ? "cyan" : void 0, children: [
|
|
1142
|
+
"Skills for ",
|
|
1143
|
+
agentId ?? "none"
|
|
1144
|
+
] }),
|
|
1145
|
+
skills.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: emptyMessage }) : skills.map((skill) => {
|
|
1146
|
+
const selected = skill.id === selectedSkillId;
|
|
1147
|
+
return /* @__PURE__ */ jsxs6(Text6, { inverse: focused && selected, children: [
|
|
1148
|
+
/* @__PURE__ */ jsx6(Text6, { color: markerColor(skill), children: skill.marker }),
|
|
1149
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1150
|
+
" ",
|
|
1151
|
+
skillLabel(skill)
|
|
1152
|
+
] })
|
|
1153
|
+
] }, skill.id);
|
|
1154
|
+
})
|
|
1155
|
+
] });
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// src/tui/components/StatusLine.tsx
|
|
1159
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1160
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1161
|
+
function StatusLine({
|
|
1162
|
+
busy,
|
|
1163
|
+
statusMessage,
|
|
1164
|
+
lastScanAt,
|
|
1165
|
+
issueCount
|
|
1166
|
+
}) {
|
|
1167
|
+
const message = statusMessage ?? (busy ? "scanning..." : `Last scan: ${lastScanAt ?? "never"} | issues: ${issueCount}`);
|
|
1168
|
+
return /* @__PURE__ */ jsx7(Box7, { height: 1, children: /* @__PURE__ */ jsx7(Text7, { color: busy ? "cyan" : void 0, children: message }) });
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/tui/components/Dashboard.tsx
|
|
1172
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1173
|
+
var minimumWidth = 80;
|
|
1174
|
+
var minimumHeight = 24;
|
|
1175
|
+
var agentRatio = 0.26;
|
|
1176
|
+
var skillRatio = 0.3;
|
|
1177
|
+
var detailRatio = 0.44;
|
|
1178
|
+
var agentMinimumWidth = 20;
|
|
1179
|
+
var skillMinimumWidth = 24;
|
|
1180
|
+
var detailMinimumWidth = 28;
|
|
1181
|
+
function paneWidths(width) {
|
|
1182
|
+
const agentWidth = Math.max(agentMinimumWidth, Math.round(width * agentRatio));
|
|
1183
|
+
const skillWidth = Math.max(skillMinimumWidth, Math.round(width * skillRatio));
|
|
1184
|
+
const detailWidth = Math.max(
|
|
1185
|
+
detailMinimumWidth,
|
|
1186
|
+
Math.round(width * detailRatio)
|
|
1187
|
+
);
|
|
1188
|
+
const widthDelta = width - (agentWidth + skillWidth + detailWidth);
|
|
1189
|
+
if (widthDelta === 0) {
|
|
1190
|
+
return { agentWidth, skillWidth, detailWidth };
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
agentWidth,
|
|
1194
|
+
skillWidth,
|
|
1195
|
+
detailWidth: detailWidth + widthDelta
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function Dashboard({
|
|
1199
|
+
state,
|
|
1200
|
+
width,
|
|
1201
|
+
height
|
|
1202
|
+
}) {
|
|
1203
|
+
if (width < minimumWidth || height < minimumHeight) {
|
|
1204
|
+
return /* @__PURE__ */ jsx8(
|
|
1205
|
+
Box8,
|
|
1206
|
+
{
|
|
1207
|
+
flexDirection: "column",
|
|
1208
|
+
width,
|
|
1209
|
+
height,
|
|
1210
|
+
justifyContent: "center",
|
|
1211
|
+
alignItems: "center",
|
|
1212
|
+
children: /* @__PURE__ */ jsx8(Text8, { children: "Terminal too small. Resize to at least 80x24." })
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const visibleAgents = getVisibleAgents(state);
|
|
1217
|
+
const visibleSkills = getVisibleSkills(state);
|
|
1218
|
+
const selectedAgent = state.model.agents.find((agent) => agent.id === state.model.selectedAgentId) ?? null;
|
|
1219
|
+
const selectedSkill = getSelectedSkill(state);
|
|
1220
|
+
const loadingAgentName = state.pendingAgentId ?? (state.busy && state.statusMessage === "loading agent..." ? state.model.selectedAgentId : null);
|
|
1221
|
+
const loadingAgent = loadingAgentName === null ? null : state.model.agents.find((agent) => agent.id === loadingAgentName) ?? null;
|
|
1222
|
+
const actions = getAvailableActions(state);
|
|
1223
|
+
const footerHeight = 3;
|
|
1224
|
+
const overlayHeight = state.modal?.kind === "help" ? 8 : state.modal?.kind === "confirm-adopt" || state.modal?.kind === "confirm-adopt-all" || state.modal?.kind === "confirm-remove" ? confirmDialogHeight : 0;
|
|
1225
|
+
const bodyHeight = Math.max(height - 1 - footerHeight - overlayHeight, 0);
|
|
1226
|
+
const { agentWidth, skillWidth, detailWidth } = paneWidths(width);
|
|
1227
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", width, height, children: [
|
|
1228
|
+
/* @__PURE__ */ jsx8(
|
|
1229
|
+
StatusLine,
|
|
1230
|
+
{
|
|
1231
|
+
busy: state.busy,
|
|
1232
|
+
statusMessage: state.statusMessage,
|
|
1233
|
+
lastScanAt: state.model.lastScanAt,
|
|
1234
|
+
issueCount: state.model.issueCount
|
|
1235
|
+
}
|
|
1236
|
+
),
|
|
1237
|
+
/* @__PURE__ */ jsxs7(Box8, { flexDirection: "row", width, height: bodyHeight, children: [
|
|
1238
|
+
/* @__PURE__ */ jsx8(
|
|
1239
|
+
AgentList,
|
|
1240
|
+
{
|
|
1241
|
+
agents: visibleAgents,
|
|
1242
|
+
selectedAgentId: state.model.selectedAgentId,
|
|
1243
|
+
focused: state.focus === "agents",
|
|
1244
|
+
searchQuery: state.search?.panel === "agents" ? state.search.query : void 0,
|
|
1245
|
+
width: agentWidth,
|
|
1246
|
+
height: bodyHeight
|
|
1247
|
+
}
|
|
1248
|
+
),
|
|
1249
|
+
/* @__PURE__ */ jsx8(
|
|
1250
|
+
SkillList,
|
|
1251
|
+
{
|
|
1252
|
+
agentId: state.model.selectedAgentId,
|
|
1253
|
+
skills: visibleSkills,
|
|
1254
|
+
selectedSkillId: state.model.selectedSkillId,
|
|
1255
|
+
focused: state.focus === "skills",
|
|
1256
|
+
searchQuery: state.search?.panel === "skills" ? state.search.query : void 0,
|
|
1257
|
+
loadingAgentName: loadingAgent?.name ?? null,
|
|
1258
|
+
width: skillWidth,
|
|
1259
|
+
height: bodyHeight
|
|
1260
|
+
}
|
|
1261
|
+
),
|
|
1262
|
+
/* @__PURE__ */ jsx8(
|
|
1263
|
+
DetailPane,
|
|
1264
|
+
{
|
|
1265
|
+
selectedAgent,
|
|
1266
|
+
selectedSkill,
|
|
1267
|
+
focused: state.focus === "detail",
|
|
1268
|
+
loadingAgentName: loadingAgent?.name ?? null,
|
|
1269
|
+
width: detailWidth,
|
|
1270
|
+
height: bodyHeight
|
|
1271
|
+
}
|
|
1272
|
+
)
|
|
1273
|
+
] }),
|
|
1274
|
+
state.modal?.kind === "help" ? /* @__PURE__ */ jsx8(HelpOverlay, {}) : null,
|
|
1275
|
+
state.modal?.kind === "confirm-adopt" || state.modal?.kind === "confirm-adopt-all" || state.modal?.kind === "confirm-remove" ? /* @__PURE__ */ jsx8(ConfirmDialog, { modal: state.modal }) : null,
|
|
1276
|
+
state.modal === null ? /* @__PURE__ */ jsx8(Footer, { actions, search: state.search }) : /* @__PURE__ */ jsx8(Box8, { height: 3 })
|
|
1277
|
+
] });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/tui/app.tsx
|
|
1281
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1282
|
+
var defaultServices2 = {
|
|
1283
|
+
loadDashboardState,
|
|
1284
|
+
dispatchTuiAction
|
|
1285
|
+
};
|
|
1286
|
+
function errorReason2(error) {
|
|
1287
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1288
|
+
const firstLine = message.split(/\r?\n/u)[0]?.trim();
|
|
1289
|
+
return firstLine === void 0 || firstLine.length === 0 ? "Unknown error" : firstLine;
|
|
1290
|
+
}
|
|
1291
|
+
function loadOptions(props, selectedAgentId, selectedSkillId) {
|
|
1292
|
+
return {
|
|
1293
|
+
homeDir: props.homeDir,
|
|
1294
|
+
skillmuxHome: props.skillmuxHome,
|
|
1295
|
+
platform: props.platform,
|
|
1296
|
+
selectedAgentId,
|
|
1297
|
+
selectedSkillId
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
function replaceStateModel(previous, model, statusMessage) {
|
|
1301
|
+
const next = createInitialTuiState(model);
|
|
1302
|
+
return {
|
|
1303
|
+
...next,
|
|
1304
|
+
focus: previous.focus,
|
|
1305
|
+
search: previous.search,
|
|
1306
|
+
statusMessage,
|
|
1307
|
+
modal: null,
|
|
1308
|
+
busy: false
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function isTextInput(input) {
|
|
1312
|
+
return input.length > 0 && !/[\u0000-\u001F\u007F]/u.test(input);
|
|
1313
|
+
}
|
|
1314
|
+
function parseBridgedSize(value) {
|
|
1315
|
+
try {
|
|
1316
|
+
const parsed = JSON.parse(value);
|
|
1317
|
+
if (typeof parsed.columns === "number" && typeof parsed.rows === "number" && Number.isFinite(parsed.columns) && Number.isFinite(parsed.rows)) {
|
|
1318
|
+
return {
|
|
1319
|
+
columns: parsed.columns,
|
|
1320
|
+
rows: parsed.rows
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
} catch {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
function liveTerminalSize() {
|
|
1329
|
+
return {
|
|
1330
|
+
columns: process.stdout.columns ?? 80,
|
|
1331
|
+
rows: process.stdout.rows ?? 24
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
function readSizeFile(path) {
|
|
1335
|
+
try {
|
|
1336
|
+
return parseBridgedSize(readFileSync(path, "utf8"));
|
|
1337
|
+
} catch {
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function useBridgedTerminalSize(path) {
|
|
1342
|
+
const [size, setSize] = useState(
|
|
1343
|
+
() => path === null ? null : readSizeFile(path)
|
|
1344
|
+
);
|
|
1345
|
+
useEffect(() => {
|
|
1346
|
+
if (path === null) {
|
|
1347
|
+
setSize(null);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
let cancelled = false;
|
|
1351
|
+
const refresh = () => {
|
|
1352
|
+
if (cancelled) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const nextSize = readSizeFile(path);
|
|
1356
|
+
setSize(
|
|
1357
|
+
(current) => nextSize === null ? current : current !== null && current.columns === nextSize.columns && current.rows === nextSize.rows ? current : nextSize
|
|
1358
|
+
);
|
|
1359
|
+
};
|
|
1360
|
+
refresh();
|
|
1361
|
+
const timer = setInterval(refresh, 50);
|
|
1362
|
+
return () => {
|
|
1363
|
+
cancelled = true;
|
|
1364
|
+
clearInterval(timer);
|
|
1365
|
+
};
|
|
1366
|
+
}, [path]);
|
|
1367
|
+
return size;
|
|
1368
|
+
}
|
|
1369
|
+
function LiveDashboardViewport({
|
|
1370
|
+
state,
|
|
1371
|
+
terminalWidth,
|
|
1372
|
+
terminalHeight
|
|
1373
|
+
}) {
|
|
1374
|
+
return /* @__PURE__ */ jsx9(
|
|
1375
|
+
Dashboard,
|
|
1376
|
+
{
|
|
1377
|
+
state,
|
|
1378
|
+
width: terminalWidth ?? liveTerminalSize().columns,
|
|
1379
|
+
height: terminalHeight ?? liveTerminalSize().rows
|
|
1380
|
+
}
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
function BridgedDashboardViewport({
|
|
1384
|
+
state,
|
|
1385
|
+
terminalWidth,
|
|
1386
|
+
terminalHeight,
|
|
1387
|
+
bridgePath
|
|
1388
|
+
}) {
|
|
1389
|
+
const bridgedTerminalSize = useBridgedTerminalSize(bridgePath ?? null);
|
|
1390
|
+
const fallbackSize = liveTerminalSize();
|
|
1391
|
+
return /* @__PURE__ */ jsx9(
|
|
1392
|
+
Dashboard,
|
|
1393
|
+
{
|
|
1394
|
+
state,
|
|
1395
|
+
width: terminalWidth ?? bridgedTerminalSize?.columns ?? fallbackSize.columns,
|
|
1396
|
+
height: terminalHeight ?? bridgedTerminalSize?.rows ?? fallbackSize.rows
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
function App({
|
|
1401
|
+
homeDir,
|
|
1402
|
+
skillmuxHome,
|
|
1403
|
+
platform,
|
|
1404
|
+
terminalWidth,
|
|
1405
|
+
terminalHeight,
|
|
1406
|
+
services: serviceOverrides
|
|
1407
|
+
}) {
|
|
1408
|
+
const { exit } = useApp();
|
|
1409
|
+
const [state, setState] = useState(null);
|
|
1410
|
+
const [loadError, setLoadError] = useState(null);
|
|
1411
|
+
const requestSequence = useRef(0);
|
|
1412
|
+
const latestRequest = useRef(0);
|
|
1413
|
+
const activeActionRequest = useRef(null);
|
|
1414
|
+
const stableModel = useRef(null);
|
|
1415
|
+
const services = useMemo(
|
|
1416
|
+
() => ({ ...defaultServices2, ...serviceOverrides }),
|
|
1417
|
+
[serviceOverrides]
|
|
1418
|
+
);
|
|
1419
|
+
const sizeBridgePath = process.env.SKILLMUX_TUI_PTY_SIZE_FILE?.trim() ?? null;
|
|
1420
|
+
const sizeBridgeEnabled = sizeBridgePath !== null && sizeBridgePath.length > 0;
|
|
1421
|
+
const beginRequest = useCallback(() => {
|
|
1422
|
+
requestSequence.current += 1;
|
|
1423
|
+
latestRequest.current = requestSequence.current;
|
|
1424
|
+
return requestSequence.current;
|
|
1425
|
+
}, []);
|
|
1426
|
+
const isLatestRequest = useCallback((requestId) => {
|
|
1427
|
+
return latestRequest.current === requestId;
|
|
1428
|
+
}, []);
|
|
1429
|
+
const startBusyState = useCallback(
|
|
1430
|
+
(baseState, action) => updateTuiState(
|
|
1431
|
+
updateTuiState(baseState, { type: "set-busy", busy: true }),
|
|
1432
|
+
{
|
|
1433
|
+
type: "set-status",
|
|
1434
|
+
message: action === "scan" ? "scanning..." : "working..."
|
|
1435
|
+
}
|
|
1436
|
+
),
|
|
1437
|
+
[]
|
|
1438
|
+
);
|
|
1439
|
+
useEffect(() => {
|
|
1440
|
+
let cancelled = false;
|
|
1441
|
+
services.loadDashboardState(loadOptions({ homeDir, skillmuxHome, platform })).then((model) => {
|
|
1442
|
+
if (!cancelled) {
|
|
1443
|
+
stableModel.current = model;
|
|
1444
|
+
setState(createInitialTuiState(model));
|
|
1445
|
+
setLoadError(null);
|
|
1446
|
+
}
|
|
1447
|
+
}).catch((error) => {
|
|
1448
|
+
if (!cancelled) {
|
|
1449
|
+
setLoadError(`Failed to load dashboard: ${errorReason2(error)}`);
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
return () => {
|
|
1453
|
+
cancelled = true;
|
|
1454
|
+
};
|
|
1455
|
+
}, [homeDir, platform, services, skillmuxHome]);
|
|
1456
|
+
const runAction = useCallback(
|
|
1457
|
+
(action, model, baseState) => {
|
|
1458
|
+
if (activeActionRequest.current !== null) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const requestId = beginRequest();
|
|
1462
|
+
activeActionRequest.current = requestId;
|
|
1463
|
+
setState(
|
|
1464
|
+
(current) => current === null ? current : startBusyState(baseState ?? current, action)
|
|
1465
|
+
);
|
|
1466
|
+
services.dispatchTuiAction({
|
|
1467
|
+
action,
|
|
1468
|
+
model,
|
|
1469
|
+
homeDir,
|
|
1470
|
+
skillmuxHome,
|
|
1471
|
+
platform
|
|
1472
|
+
}).then((result) => {
|
|
1473
|
+
if (!isLatestRequest(requestId)) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
stableModel.current = result.model;
|
|
1477
|
+
setState(
|
|
1478
|
+
(current) => current === null ? current : replaceStateModel(current, result.model, result.statusMessage)
|
|
1479
|
+
);
|
|
1480
|
+
}).catch((error) => {
|
|
1481
|
+
if (!isLatestRequest(requestId)) {
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
setState(
|
|
1485
|
+
(current) => current === null ? current : updateTuiState(
|
|
1486
|
+
updateTuiState(current, { type: "set-busy", busy: false }),
|
|
1487
|
+
{
|
|
1488
|
+
type: "set-status",
|
|
1489
|
+
message: `Action failed: ${errorReason2(error)}`
|
|
1490
|
+
}
|
|
1491
|
+
)
|
|
1492
|
+
);
|
|
1493
|
+
}).finally(() => {
|
|
1494
|
+
if (activeActionRequest.current === requestId) {
|
|
1495
|
+
activeActionRequest.current = null;
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
},
|
|
1499
|
+
[
|
|
1500
|
+
beginRequest,
|
|
1501
|
+
homeDir,
|
|
1502
|
+
isLatestRequest,
|
|
1503
|
+
platform,
|
|
1504
|
+
services,
|
|
1505
|
+
skillmuxHome,
|
|
1506
|
+
startBusyState
|
|
1507
|
+
]
|
|
1508
|
+
);
|
|
1509
|
+
const reloadAgent = useCallback(
|
|
1510
|
+
(agentId) => {
|
|
1511
|
+
const requestId = beginRequest();
|
|
1512
|
+
setState(
|
|
1513
|
+
(current) => current === null ? current : updateTuiState(
|
|
1514
|
+
updateTuiState(current, { type: "set-busy", busy: true }),
|
|
1515
|
+
{ type: "set-status", message: "loading agent..." }
|
|
1516
|
+
)
|
|
1517
|
+
);
|
|
1518
|
+
services.loadDashboardState(loadOptions({ homeDir, skillmuxHome, platform }, agentId)).then((model) => {
|
|
1519
|
+
if (!isLatestRequest(requestId)) {
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
stableModel.current = model;
|
|
1523
|
+
setState(
|
|
1524
|
+
(current) => current === null ? current : replaceStateModel(current, model, null)
|
|
1525
|
+
);
|
|
1526
|
+
}).catch((error) => {
|
|
1527
|
+
if (!isLatestRequest(requestId)) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
setState(
|
|
1531
|
+
(current) => current === null ? current : stableModel.current === null ? updateTuiState(
|
|
1532
|
+
updateTuiState(current, { type: "set-busy", busy: false }),
|
|
1533
|
+
{
|
|
1534
|
+
type: "set-status",
|
|
1535
|
+
message: `Load failed: ${errorReason2(error)}`
|
|
1536
|
+
}
|
|
1537
|
+
) : replaceStateModel(
|
|
1538
|
+
current,
|
|
1539
|
+
stableModel.current,
|
|
1540
|
+
`Load failed: ${errorReason2(error)}`
|
|
1541
|
+
)
|
|
1542
|
+
);
|
|
1543
|
+
});
|
|
1544
|
+
},
|
|
1545
|
+
[beginRequest, homeDir, isLatestRequest, platform, services, skillmuxHome]
|
|
1546
|
+
);
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
if (state?.pendingAction === null || state?.pendingAction === void 0) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const consumed = consumeActionIntent(state);
|
|
1552
|
+
setState(consumed.state);
|
|
1553
|
+
if (consumed.action !== null) {
|
|
1554
|
+
runAction(consumed.action, consumed.state.model);
|
|
1555
|
+
}
|
|
1556
|
+
}, [runAction, state]);
|
|
1557
|
+
useEffect(() => {
|
|
1558
|
+
if (state?.pendingAgentId === null || state?.pendingAgentId === void 0) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
const consumed = consumeAgentSelectionIntent(state);
|
|
1562
|
+
setState(consumed.state);
|
|
1563
|
+
if (consumed.agentId !== null) {
|
|
1564
|
+
reloadAgent(consumed.agentId);
|
|
1565
|
+
}
|
|
1566
|
+
}, [reloadAgent, state]);
|
|
1567
|
+
useInput((input, key) => {
|
|
1568
|
+
if (key.ctrl && input === "c") {
|
|
1569
|
+
exit();
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (state === null) {
|
|
1573
|
+
if (input === "q") {
|
|
1574
|
+
exit();
|
|
1575
|
+
}
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (state.search !== null) {
|
|
1579
|
+
if (key.escape) {
|
|
1580
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
if (key.return) {
|
|
1584
|
+
setState(updateTuiState(state, { type: "submit-search" }));
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (key.backspace || key.delete) {
|
|
1588
|
+
setState(
|
|
1589
|
+
updateTuiState(state, {
|
|
1590
|
+
type: "search-query-changed",
|
|
1591
|
+
query: state.search.query.slice(0, -1)
|
|
1592
|
+
})
|
|
1593
|
+
);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
if (!key.ctrl && !key.meta && isTextInput(input)) {
|
|
1597
|
+
setState(
|
|
1598
|
+
updateTuiState(state, {
|
|
1599
|
+
type: "search-query-changed",
|
|
1600
|
+
query: `${state.search.query}${input}`
|
|
1601
|
+
})
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (state.modal !== null) {
|
|
1607
|
+
if (key.escape) {
|
|
1608
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (input === "q") {
|
|
1612
|
+
exit();
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
if (activeActionRequest.current !== null) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
if (input.toLocaleLowerCase() === "y" && state.modal.kind === "confirm-adopt") {
|
|
1619
|
+
const closedState = updateTuiState(state, { type: "close" });
|
|
1620
|
+
runAction("adopt", closedState.model, closedState);
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (input.toLocaleLowerCase() === "y" && state.modal.kind === "confirm-adopt-all") {
|
|
1624
|
+
const closedState = updateTuiState(state, { type: "close" });
|
|
1625
|
+
runAction("adopt-all", closedState.model, closedState);
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (input.toLocaleLowerCase() === "y" && state.modal.kind === "confirm-remove") {
|
|
1629
|
+
const closedState = updateTuiState(state, { type: "close" });
|
|
1630
|
+
runAction("remove", closedState.model, closedState);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
if (input === "q") {
|
|
1636
|
+
exit();
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
if (activeActionRequest.current !== null) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (key.rightArrow) {
|
|
1643
|
+
setState(
|
|
1644
|
+
updateTuiState(state, {
|
|
1645
|
+
type: "focus-next"
|
|
1646
|
+
})
|
|
1647
|
+
);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (key.leftArrow) {
|
|
1651
|
+
setState(
|
|
1652
|
+
updateTuiState(state, {
|
|
1653
|
+
type: "focus-previous"
|
|
1654
|
+
})
|
|
1655
|
+
);
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (key.downArrow || input === "j") {
|
|
1659
|
+
setState(updateTuiState(state, { type: "next-row" }));
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (key.upArrow || input === "k") {
|
|
1663
|
+
setState(updateTuiState(state, { type: "previous-row" }));
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (input === "g") {
|
|
1667
|
+
setState(updateTuiState(state, { type: "first-row" }));
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (input === "G") {
|
|
1671
|
+
setState(updateTuiState(state, { type: "last-row" }));
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (input === "/") {
|
|
1675
|
+
setState(updateTuiState(state, { type: "open-search" }));
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (input === "?") {
|
|
1679
|
+
setState(updateTuiState(state, { type: "open-help" }));
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (key.escape) {
|
|
1683
|
+
setState(updateTuiState(state, { type: "close" }));
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (input === " ") {
|
|
1687
|
+
setState(updateTuiState(state, { type: "request-toggle" }));
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (input === "A" || key.shift && input === "a") {
|
|
1691
|
+
setState(updateTuiState(state, { type: "request-adopt-all" }));
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (input === "a") {
|
|
1695
|
+
setState(updateTuiState(state, { type: "request-adopt" }));
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (input === "r") {
|
|
1699
|
+
setState(updateTuiState(state, { type: "request-remove" }));
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (input === "s") {
|
|
1703
|
+
setState(updateTuiState(state, { type: "request-scan" }));
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
if (loadError !== null) {
|
|
1707
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "red", children: loadError });
|
|
1708
|
+
}
|
|
1709
|
+
if (state === null) {
|
|
1710
|
+
return /* @__PURE__ */ jsx9(Text9, { children: "loading dashboard..." });
|
|
1711
|
+
}
|
|
1712
|
+
return sizeBridgeEnabled ? /* @__PURE__ */ jsx9(
|
|
1713
|
+
BridgedDashboardViewport,
|
|
1714
|
+
{
|
|
1715
|
+
state,
|
|
1716
|
+
terminalWidth,
|
|
1717
|
+
terminalHeight,
|
|
1718
|
+
bridgePath: sizeBridgePath
|
|
1719
|
+
}
|
|
1720
|
+
) : /* @__PURE__ */ jsx9(
|
|
1721
|
+
LiveDashboardViewport,
|
|
1722
|
+
{
|
|
1723
|
+
state,
|
|
1724
|
+
terminalWidth,
|
|
1725
|
+
terminalHeight
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// src/tui/launch-tui.tsx
|
|
1731
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1732
|
+
var alternateScreenEnter = "\x1B[?1049h";
|
|
1733
|
+
var alternateScreenExit = "\x1B[?1049l";
|
|
1734
|
+
var cursorHide = "\x1B[?25l";
|
|
1735
|
+
var cursorShow = "\x1B[?25h";
|
|
1736
|
+
var lifecycleTraceEnabled = process.env.SKILLMUX_TUI_PTY_TRACE === "1";
|
|
1737
|
+
async function launchTui(options = {}) {
|
|
1738
|
+
let failure;
|
|
1739
|
+
let instance = null;
|
|
1740
|
+
let sigintRequested = false;
|
|
1741
|
+
const handleSigint = () => {
|
|
1742
|
+
sigintRequested = true;
|
|
1743
|
+
instance?.unmount();
|
|
1744
|
+
};
|
|
1745
|
+
try {
|
|
1746
|
+
process.once("SIGINT", handleSigint);
|
|
1747
|
+
process.stdout.write(alternateScreenEnter);
|
|
1748
|
+
process.stdout.write(cursorHide);
|
|
1749
|
+
await writeLifecycleTrace("alt-screen-enter");
|
|
1750
|
+
instance = render(/* @__PURE__ */ jsx10(App, { ...options }));
|
|
1751
|
+
if (sigintRequested) {
|
|
1752
|
+
instance.unmount();
|
|
1753
|
+
}
|
|
1754
|
+
await instance.waitUntilExit();
|
|
1755
|
+
await writeLifecycleTrace("session-exit-clean");
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
failure = error;
|
|
1758
|
+
} finally {
|
|
1759
|
+
process.removeListener("SIGINT", handleSigint);
|
|
1760
|
+
failure = await runCleanup(failure, () => writeLifecycleTrace("alt-screen-exit"));
|
|
1761
|
+
failure = await runCleanup(failure, () => process.stdout.write(alternateScreenExit));
|
|
1762
|
+
failure = await runCleanup(failure, () => process.stdout.write(cursorShow));
|
|
1763
|
+
}
|
|
1764
|
+
if (failure !== void 0) {
|
|
1765
|
+
throw failure;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function writeLifecycleTrace(stage) {
|
|
1769
|
+
if (!lifecycleTraceEnabled) {
|
|
1770
|
+
return Promise.resolve();
|
|
1771
|
+
}
|
|
1772
|
+
process.stderr.write(`[skillmux:${stage}]
|
|
1773
|
+
`);
|
|
1774
|
+
return stage === "session-exit-clean" ? sleep(0) : Promise.resolve();
|
|
1775
|
+
}
|
|
1776
|
+
async function runCleanup(failure, cleanup) {
|
|
1777
|
+
try {
|
|
1778
|
+
await cleanup();
|
|
1779
|
+
return failure;
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
return failure === void 0 ? error : failure;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function sleep(ms) {
|
|
1785
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1786
|
+
}
|
|
1787
|
+
export {
|
|
1788
|
+
launchTui
|
|
1789
|
+
};
|
|
1790
|
+
//# sourceMappingURL=launch-tui-PHWJPIQZ.js.map
|