skill-flow 1.0.8 → 1.3.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.
Files changed (143) hide show
  1. package/README.md +209 -131
  2. package/README.zh.md +170 -131
  3. package/dist/bridge-command.d.ts +9 -0
  4. package/dist/bridge-command.js +422 -0
  5. package/dist/bridge-command.js.map +1 -0
  6. package/dist/cli.js +68 -8
  7. package/dist/cli.js.map +1 -1
  8. package/package.json +11 -2
  9. package/dist/adapters/channel-adapters.d.ts +0 -8
  10. package/dist/adapters/channel-adapters.js +0 -64
  11. package/dist/adapters/channel-adapters.js.map +0 -1
  12. package/dist/domain/types.d.ts +0 -234
  13. package/dist/domain/types.js +0 -2
  14. package/dist/domain/types.js.map +0 -1
  15. package/dist/services/config-coordinator.d.ts +0 -38
  16. package/dist/services/config-coordinator.js +0 -83
  17. package/dist/services/config-coordinator.js.map +0 -1
  18. package/dist/services/deployment-applier.d.ts +0 -10
  19. package/dist/services/deployment-applier.js +0 -84
  20. package/dist/services/deployment-applier.js.map +0 -1
  21. package/dist/services/deployment-planner.d.ts +0 -16
  22. package/dist/services/deployment-planner.js +0 -366
  23. package/dist/services/deployment-planner.js.map +0 -1
  24. package/dist/services/doctor-service.d.ts +0 -7
  25. package/dist/services/doctor-service.js +0 -204
  26. package/dist/services/doctor-service.js.map +0 -1
  27. package/dist/services/inventory-service.d.ts +0 -17
  28. package/dist/services/inventory-service.js +0 -216
  29. package/dist/services/inventory-service.js.map +0 -1
  30. package/dist/services/skill-flow.d.ts +0 -136
  31. package/dist/services/skill-flow.js +0 -1210
  32. package/dist/services/skill-flow.js.map +0 -1
  33. package/dist/services/source-service.d.ts +0 -57
  34. package/dist/services/source-service.js +0 -809
  35. package/dist/services/source-service.js.map +0 -1
  36. package/dist/services/workflow-service.d.ts +0 -5
  37. package/dist/services/workflow-service.js +0 -45
  38. package/dist/services/workflow-service.js.map +0 -1
  39. package/dist/services/workspace-bootstrap-service.d.ts +0 -25
  40. package/dist/services/workspace-bootstrap-service.js +0 -140
  41. package/dist/services/workspace-bootstrap-service.js.map +0 -1
  42. package/dist/state/store.d.ts +0 -35
  43. package/dist/state/store.js +0 -151
  44. package/dist/state/store.js.map +0 -1
  45. package/dist/tests/add-flow-model.test.d.ts +0 -1
  46. package/dist/tests/add-flow-model.test.js +0 -108
  47. package/dist/tests/add-flow-model.test.js.map +0 -1
  48. package/dist/tests/add-flow-ui.test.d.ts +0 -1
  49. package/dist/tests/add-flow-ui.test.js +0 -16
  50. package/dist/tests/add-flow-ui.test.js.map +0 -1
  51. package/dist/tests/add-prepare-flow.test.d.ts +0 -1
  52. package/dist/tests/add-prepare-flow.test.js +0 -166
  53. package/dist/tests/add-prepare-flow.test.js.map +0 -1
  54. package/dist/tests/add-selection-and-find-command.test.d.ts +0 -1
  55. package/dist/tests/add-selection-and-find-command.test.js +0 -89
  56. package/dist/tests/add-selection-and-find-command.test.js.map +0 -1
  57. package/dist/tests/clawhub.test.d.ts +0 -1
  58. package/dist/tests/clawhub.test.js +0 -63
  59. package/dist/tests/clawhub.test.js.map +0 -1
  60. package/dist/tests/cli-utils.test.d.ts +0 -1
  61. package/dist/tests/cli-utils.test.js +0 -24
  62. package/dist/tests/cli-utils.test.js.map +0 -1
  63. package/dist/tests/config-coordinator.test.d.ts +0 -1
  64. package/dist/tests/config-coordinator.test.js +0 -219
  65. package/dist/tests/config-coordinator.test.js.map +0 -1
  66. package/dist/tests/config-integration.test.d.ts +0 -1
  67. package/dist/tests/config-integration.test.js +0 -276
  68. package/dist/tests/config-integration.test.js.map +0 -1
  69. package/dist/tests/config-ui-utils.test.d.ts +0 -1
  70. package/dist/tests/config-ui-utils.test.js +0 -523
  71. package/dist/tests/config-ui-utils.test.js.map +0 -1
  72. package/dist/tests/find-and-naming-utils.test.d.ts +0 -1
  73. package/dist/tests/find-and-naming-utils.test.js +0 -127
  74. package/dist/tests/find-and-naming-utils.test.js.map +0 -1
  75. package/dist/tests/inventory-service-precedence.test.d.ts +0 -1
  76. package/dist/tests/inventory-service-precedence.test.js +0 -42
  77. package/dist/tests/inventory-service-precedence.test.js.map +0 -1
  78. package/dist/tests/skill-flow.test.d.ts +0 -1
  79. package/dist/tests/skill-flow.test.js +0 -991
  80. package/dist/tests/skill-flow.test.js.map +0 -1
  81. package/dist/tests/source-lifecycle.test.d.ts +0 -1
  82. package/dist/tests/source-lifecycle.test.js +0 -644
  83. package/dist/tests/source-lifecycle.test.js.map +0 -1
  84. package/dist/tests/source-parsing-compatibility.test.d.ts +0 -1
  85. package/dist/tests/source-parsing-compatibility.test.js +0 -72
  86. package/dist/tests/source-parsing-compatibility.test.js.map +0 -1
  87. package/dist/tests/target-definitions.test.d.ts +0 -1
  88. package/dist/tests/target-definitions.test.js +0 -51
  89. package/dist/tests/target-definitions.test.js.map +0 -1
  90. package/dist/tests/test-helpers.d.ts +0 -18
  91. package/dist/tests/test-helpers.js +0 -123
  92. package/dist/tests/test-helpers.js.map +0 -1
  93. package/dist/tui/add-flow-model.d.ts +0 -62
  94. package/dist/tui/add-flow-model.js +0 -206
  95. package/dist/tui/add-flow-model.js.map +0 -1
  96. package/dist/tui/add-flow.d.ts +0 -25
  97. package/dist/tui/add-flow.js +0 -534
  98. package/dist/tui/add-flow.js.map +0 -1
  99. package/dist/tui/config-app.d.ts +0 -178
  100. package/dist/tui/config-app.js +0 -1551
  101. package/dist/tui/config-app.js.map +0 -1
  102. package/dist/tui/find-app.d.ts +0 -9
  103. package/dist/tui/find-app.js +0 -150
  104. package/dist/tui/find-app.js.map +0 -1
  105. package/dist/tui/selection-state.d.ts +0 -8
  106. package/dist/tui/selection-state.js +0 -32
  107. package/dist/tui/selection-state.js.map +0 -1
  108. package/dist/utils/builtin-git-sources.d.ts +0 -5
  109. package/dist/utils/builtin-git-sources.js +0 -23
  110. package/dist/utils/builtin-git-sources.js.map +0 -1
  111. package/dist/utils/clawhub.d.ts +0 -41
  112. package/dist/utils/clawhub.js +0 -94
  113. package/dist/utils/clawhub.js.map +0 -1
  114. package/dist/utils/cli.d.ts +0 -2
  115. package/dist/utils/cli.js +0 -19
  116. package/dist/utils/cli.js.map +0 -1
  117. package/dist/utils/constants.d.ts +0 -23
  118. package/dist/utils/constants.js +0 -195
  119. package/dist/utils/constants.js.map +0 -1
  120. package/dist/utils/find-command.d.ts +0 -2
  121. package/dist/utils/find-command.js +0 -29
  122. package/dist/utils/find-command.js.map +0 -1
  123. package/dist/utils/format.d.ts +0 -7
  124. package/dist/utils/format.js +0 -68
  125. package/dist/utils/format.js.map +0 -1
  126. package/dist/utils/fs.d.ts +0 -16
  127. package/dist/utils/fs.js +0 -144
  128. package/dist/utils/fs.js.map +0 -1
  129. package/dist/utils/git.d.ts +0 -3
  130. package/dist/utils/git.js +0 -12
  131. package/dist/utils/git.js.map +0 -1
  132. package/dist/utils/github-catalog.d.ts +0 -1
  133. package/dist/utils/github-catalog.js +0 -25
  134. package/dist/utils/github-catalog.js.map +0 -1
  135. package/dist/utils/naming.d.ts +0 -29
  136. package/dist/utils/naming.js +0 -115
  137. package/dist/utils/naming.js.map +0 -1
  138. package/dist/utils/result.d.ts +0 -4
  139. package/dist/utils/result.js +0 -15
  140. package/dist/utils/result.js.map +0 -1
  141. package/dist/utils/source-id.d.ts +0 -2
  142. package/dist/utils/source-id.js +0 -49
  143. package/dist/utils/source-id.js.map +0 -1
@@ -1,1551 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { Box, Text, useApp, useInput, useStdout } from "ink";
4
- import { TARGET_LABELS, TARGET_ORDER } from "../utils/constants.js";
5
- import { buildProjectedSkillName, formatGroupLabel, parseGitHubRepo, resolveProjectedSkillNames, } from "../utils/naming.js";
6
- import { getParentSelectionState, toggleChild, toggleParent, } from "./selection-state.js";
7
- import { ADD_BADGE_TEXT } from "./add-flow.js";
8
- const EMPTY_DRAFT = {
9
- enabledTargets: [],
10
- selectedLeafIds: [],
11
- };
12
- const EMPTY_CONFIG_GROUP = {
13
- id: "__empty__",
14
- title: "",
15
- kind: "source",
16
- summaries: [],
17
- };
18
- const EMPTY_PREVIEW = {
19
- actions: [],
20
- blockedCount: 0,
21
- errorMessage: undefined,
22
- loading: false,
23
- requestId: 0,
24
- };
25
- const PANE_CHROME_ROWS = 5;
26
- const UPDATED_FEEDBACK_MS = 1_200;
27
- const CLAWHUB_GROUP_ID = "__clawhub_skills__";
28
- export function normalizeDraft(draft) {
29
- return {
30
- enabledTargets: [...draft.enabledTargets].sort(),
31
- selectedLeafIds: [...draft.selectedLeafIds].sort(),
32
- };
33
- }
34
- export function draftsEqual(left, right) {
35
- const nextLeft = normalizeDraft(left);
36
- const nextRight = normalizeDraft(right);
37
- return JSON.stringify(nextLeft) === JSON.stringify(nextRight);
38
- }
39
- export function buildDraftsFromSummaries(summaries) {
40
- return Object.fromEntries(summaries.map((summary) => {
41
- const enabledTargets = Object.entries(summary.bindings.targets)
42
- .filter(([, value]) => value?.enabled)
43
- .map(([target]) => target);
44
- const selectedLeafIds = [
45
- ...new Set((summary.bindings.selectedLeafIds && summary.bindings.selectedLeafIds.length > 0
46
- ? summary.bindings.selectedLeafIds
47
- : enabledTargets.flatMap((target) => summary.bindings.targets[target]?.leafIds ?? []))),
48
- ];
49
- return [summary.source.id, normalizeDraft({ enabledTargets, selectedLeafIds })];
50
- }));
51
- }
52
- export function buildConfigGroups(summaries) {
53
- const clawhubSummaries = summaries.filter((summary) => summary.source.kind === "clawhub");
54
- const groups = [];
55
- let clawhubGroupInserted = false;
56
- for (const summary of summaries) {
57
- if (summary.source.kind === "clawhub") {
58
- if (!clawhubGroupInserted && clawhubSummaries.length > 0) {
59
- groups.push({
60
- id: CLAWHUB_GROUP_ID,
61
- title: "ClawHub Skills",
62
- kind: "clawhub",
63
- summaries: clawhubSummaries,
64
- });
65
- clawhubGroupInserted = true;
66
- }
67
- continue;
68
- }
69
- groups.push({
70
- id: summary.source.id,
71
- title: formatGroupLabel(summary.source),
72
- kind: "source",
73
- summaries: [summary],
74
- });
75
- }
76
- if (!clawhubGroupInserted && clawhubSummaries.length > 0) {
77
- groups.push({
78
- id: CLAWHUB_GROUP_ID,
79
- title: "ClawHub Skills",
80
- kind: "clawhub",
81
- summaries: clawhubSummaries,
82
- });
83
- }
84
- return groups;
85
- }
86
- export function buildConfigGroupSkillRows(group) {
87
- return group.summaries.flatMap((summary) => summary.leafs.map((leaf) => ({
88
- summary,
89
- leaf,
90
- })));
91
- }
92
- export function getPaneViewportCount(paneHeight, reservedRows = 0) {
93
- return Math.max(1, paneHeight - PANE_CHROME_ROWS - reservedRows);
94
- }
95
- export function getPaneWidths(terminalColumns) {
96
- const available = Math.max(56, terminalColumns - 1);
97
- const left = Math.max(20, Math.min(30, Math.floor(available * 0.28)));
98
- return [left, Math.max(32, available - left)];
99
- }
100
- export function getActionChangeCount(actions) {
101
- return actions.filter((action) => action.kind !== "noop").length;
102
- }
103
- export function getGroupSelectedLeafCount({ drafts, group, }) {
104
- return group.summaries.reduce((count, summary) => count + (drafts[summary.source.id]?.selectedLeafIds.length ?? 0), 0);
105
- }
106
- export function getStatusDisplay({ deleteState, isSelectedDelete, saveState, updateState, }) {
107
- if (isSelectedDelete && deleteState.phase === "deleting") {
108
- return { kind: "deleting", label: "Deleting", color: "yellow" };
109
- }
110
- if (updateState.phase === "updating") {
111
- return { kind: "updating", label: "Updating", color: "cyan" };
112
- }
113
- if (updateState.phase === "failed") {
114
- return { kind: "update-failed", label: "Update Failed", color: "red" };
115
- }
116
- if (saveState.phase === "saving") {
117
- return { kind: "saving", label: "Saving", color: "cyan" };
118
- }
119
- if (saveState.phase === "failed") {
120
- return { kind: "failed", label: "Failed", color: "red" };
121
- }
122
- if (updateState.phase === "updated") {
123
- return { kind: "updated", label: "Updated", color: "green" };
124
- }
125
- if (saveState.phase === "saved") {
126
- return { kind: "saved", label: "Saved", color: "green" };
127
- }
128
- return { kind: "clean", label: "Clean", color: "gray" };
129
- }
130
- export function buildTopBar({ width, isDirty, changeCount, showDelete, statusLabel, }) {
131
- const topBar = {
132
- title: "Skill Flow",
133
- titleColor: "blue",
134
- };
135
- if (changeCount > 0) {
136
- topBar.detail = `Changes: ${changeCount}`;
137
- return topBar;
138
- }
139
- if (statusLabel === "Saving" ||
140
- statusLabel === "Updating" ||
141
- statusLabel === "Deleting" ||
142
- statusLabel === "Failed" ||
143
- statusLabel === "Update Failed") {
144
- topBar.detail = `Status: ${statusLabel}`;
145
- topBar.detailColor =
146
- statusLabel === "Deleting"
147
- ? "yellow"
148
- : statusLabel === "Saving" || statusLabel === "Updating"
149
- ? "cyan"
150
- : "red";
151
- }
152
- return topBar;
153
- }
154
- export function prioritizeAlerts(alerts) {
155
- const seen = new Set();
156
- const priority = {
157
- error: 0,
158
- blocked: 1,
159
- warning: 2,
160
- };
161
- return alerts
162
- .filter((alert) => {
163
- const key = `${alert.level}:${alert.message}`;
164
- if (seen.has(key)) {
165
- return false;
166
- }
167
- seen.add(key);
168
- return true;
169
- })
170
- .sort((left, right) => priority[left.level] - priority[right.level])
171
- .slice(0, 2);
172
- }
173
- export function getInitialDetailFocus({ hasAgents, hasSkills, }) {
174
- if (hasAgents) {
175
- return "detail.agents";
176
- }
177
- if (hasSkills) {
178
- return "detail.skills";
179
- }
180
- return "detail.actions";
181
- }
182
- export function moveDetailFocus({ actionCursor, actionCount, agentCount, agentCursor, direction, focus, skillCount, skillCursor, }) {
183
- if (focus === "detail.agents") {
184
- if (direction === -1) {
185
- if (agentCount > 0 && agentCursor > 0) {
186
- return { focus, agentCursor: agentCursor - 1, skillCursor, actionCursor };
187
- }
188
- return { focus, agentCursor, skillCursor, actionCursor };
189
- }
190
- if (agentCount > 0 && agentCursor < agentCount - 1) {
191
- return { focus, agentCursor: agentCursor + 1, skillCursor, actionCursor };
192
- }
193
- if (skillCount > 0) {
194
- return { focus: "detail.skills", agentCursor, skillCursor: 0, actionCursor };
195
- }
196
- return {
197
- focus: "detail.actions",
198
- agentCursor,
199
- skillCursor,
200
- actionCursor: Math.min(actionCursor, Math.max(0, actionCount - 1)),
201
- };
202
- }
203
- if (focus === "detail.skills") {
204
- if (direction === -1) {
205
- if (skillCount > 0 && skillCursor > 0) {
206
- return { focus, agentCursor, skillCursor: skillCursor - 1, actionCursor };
207
- }
208
- if (agentCount > 0) {
209
- return {
210
- focus: "detail.agents",
211
- agentCursor: Math.max(0, agentCount - 1),
212
- skillCursor,
213
- actionCursor,
214
- };
215
- }
216
- return { focus, agentCursor, skillCursor, actionCursor };
217
- }
218
- if (skillCount > 0 && skillCursor < skillCount - 1) {
219
- return { focus, agentCursor, skillCursor: skillCursor + 1, actionCursor };
220
- }
221
- return {
222
- focus: "detail.actions",
223
- agentCursor,
224
- skillCursor,
225
- actionCursor: Math.min(actionCursor, Math.max(0, actionCount - 1)),
226
- };
227
- }
228
- if (direction === 1) {
229
- if (actionCursor < actionCount - 1) {
230
- return { focus, agentCursor, skillCursor, actionCursor: actionCursor + 1 };
231
- }
232
- return { focus, agentCursor, skillCursor, actionCursor };
233
- }
234
- if (actionCursor > 0) {
235
- return { focus, agentCursor, skillCursor, actionCursor: actionCursor - 1 };
236
- }
237
- if (skillCount > 0) {
238
- return {
239
- focus: "detail.skills",
240
- agentCursor,
241
- skillCursor: Math.max(0, skillCount - 1),
242
- actionCursor,
243
- };
244
- }
245
- if (agentCount > 0) {
246
- return {
247
- focus: "detail.agents",
248
- agentCursor: Math.max(0, agentCount - 1),
249
- skillCursor,
250
- actionCursor,
251
- };
252
- }
253
- return { focus, agentCursor, skillCursor, actionCursor };
254
- }
255
- export function getNextSelectionIndexAfterDelete(currentIndex, nextCount) {
256
- if (nextCount <= 0) {
257
- return -1;
258
- }
259
- return Math.min(currentIndex, nextCount - 1);
260
- }
261
- export function captureFocusSnapshot({ actionCursor, agentCursor, availableTargets, focus, groupId, selectedGroupIndex, selectedSummary, skillCursor, }) {
262
- return {
263
- focus,
264
- groupIndex: selectedGroupIndex,
265
- groupId,
266
- sourceId: selectedSummary?.source.id,
267
- agentTarget: agentCursor > 0 ? availableTargets[agentCursor - 1] : undefined,
268
- skillId: selectedSummary && skillCursor > 0
269
- ? selectedSummary.leafs[skillCursor - 1]?.id
270
- : undefined,
271
- action: actionCursor === 1 ? "delete" : "update",
272
- };
273
- }
274
- export function reconcileFocusAfterReload({ availableTargets, nextGroups, snapshot, }) {
275
- const selectedGroupIndex = nextGroups.findIndex((group) => group.id === snapshot.groupId);
276
- const fallbackGroupIndex = Math.min(snapshot.groupIndex, Math.max(0, nextGroups.length - 1));
277
- const resolvedGroupIndex = nextGroups.length === 0
278
- ? -1
279
- : selectedGroupIndex >= 0 && nextGroups[selectedGroupIndex]
280
- ? selectedGroupIndex
281
- : fallbackGroupIndex;
282
- const group = resolvedGroupIndex >= 0 ? nextGroups[resolvedGroupIndex] : undefined;
283
- const skillRows = group ? buildConfigGroupSkillRows(group) : [];
284
- const hasAgents = availableTargets.length > 0;
285
- const hasSkills = skillRows.length > 0;
286
- let focus = snapshot.focus;
287
- let agentCursor = 0;
288
- let skillCursor = 0;
289
- let actionCursor = snapshot.action === "delete" && group?.kind !== "clawhub" ? 1 : 0;
290
- if (focus === "detail.agents") {
291
- if (hasAgents) {
292
- const nextAgentIndex = snapshot.agentTarget
293
- ? availableTargets.indexOf(snapshot.agentTarget)
294
- : -1;
295
- agentCursor = nextAgentIndex >= 0 ? nextAgentIndex + 1 : 0;
296
- }
297
- else {
298
- focus = getInitialDetailFocus({ hasAgents, hasSkills });
299
- }
300
- }
301
- if (focus === "detail.skills") {
302
- if (hasSkills) {
303
- const nextSkillIndex = snapshot.sourceId && snapshot.skillId
304
- ? skillRows.findIndex((row) => row.summary.source.id === snapshot.sourceId && row.leaf.id === snapshot.skillId)
305
- : -1;
306
- skillCursor = nextSkillIndex >= 0 ? nextSkillIndex + 1 : 0;
307
- }
308
- else {
309
- focus = getInitialDetailFocus({ hasAgents, hasSkills });
310
- }
311
- }
312
- if (focus === "detail.actions") {
313
- focus = "detail.actions";
314
- }
315
- return {
316
- actionCursor,
317
- agentCursor,
318
- focus: resolvedGroupIndex >= 0 ? focus : "groups",
319
- groupCursor: resolvedGroupIndex,
320
- selectedGroupIndex: resolvedGroupIndex,
321
- skillCursor,
322
- };
323
- }
324
- export function getRequestedAction({ actionCursor, canDelete, focus, input, keyReturn, }) {
325
- if (input === "u") {
326
- return "update";
327
- }
328
- if (input === "d" && canDelete) {
329
- return "delete";
330
- }
331
- if (focus === "detail.actions" && keyReturn) {
332
- return actionCursor === 1 && canDelete ? "delete" : "update";
333
- }
334
- return undefined;
335
- }
336
- export function buildProjectionWarningMap({ drafts, summaries, sourceId, }) {
337
- const currentDraft = drafts[sourceId] ?? EMPTY_DRAFT;
338
- const currentSummary = summaries.find((summary) => summary.source.id === sourceId);
339
- if (!currentSummary || currentDraft.enabledTargets.length === 0) {
340
- return {};
341
- }
342
- const currentSelectedLeafIds = new Set(currentDraft.selectedLeafIds);
343
- const currentEnabledTargets = new Set(currentDraft.enabledTargets);
344
- const otherSelectedLeafs = summaries.flatMap((summary) => {
345
- if (summary.source.id === sourceId) {
346
- return [];
347
- }
348
- const otherDraft = drafts[summary.source.id] ?? EMPTY_DRAFT;
349
- const hasTargetOverlap = otherDraft.enabledTargets.some((target) => currentEnabledTargets.has(target));
350
- if (!hasTargetOverlap) {
351
- return [];
352
- }
353
- return otherDraft.selectedLeafIds
354
- .map((leafId) => summary.leafs.find((leaf) => leaf.id === leafId))
355
- .filter((leaf) => Boolean(leaf))
356
- .map((leaf) => ({
357
- source: summary.source,
358
- leaf,
359
- exactKey: getExactDuplicateKey(leaf.linkName, leaf.name, leaf.description),
360
- }));
361
- });
362
- const projectedNames = resolveProjectedSkillNames([
363
- ...otherSelectedLeafs.map((candidate) => ({
364
- leafId: candidate.leaf.id,
365
- groupId: candidate.source.id,
366
- groupName: candidate.source.displayName,
367
- groupAuthor: parseGitHubRepo(candidate.source.locator)?.owner,
368
- skillName: candidate.leaf.linkName,
369
- })),
370
- ...currentSummary.leafs
371
- .filter((leaf) => currentSelectedLeafIds.has(leaf.id))
372
- .map((leaf) => ({
373
- leafId: leaf.id,
374
- groupId: currentSummary.source.id,
375
- groupName: currentSummary.source.displayName,
376
- groupAuthor: parseGitHubRepo(currentSummary.source.locator)?.owner,
377
- skillName: leaf.linkName,
378
- })),
379
- ]);
380
- const warningsByLeafId = {};
381
- for (const leaf of currentSummary.leafs) {
382
- if (!currentSelectedLeafIds.has(leaf.id)) {
383
- continue;
384
- }
385
- const exactKey = getExactDuplicateKey(leaf.linkName, leaf.name, leaf.description);
386
- const exactDuplicate = otherSelectedLeafs.find((candidate) => candidate.exactKey === exactKey);
387
- if (exactDuplicate) {
388
- warningsByLeafId[leaf.id] = [
389
- `identical skill already selected in ${formatGroupLabel(exactDuplicate.source)}, this one will be skipped`,
390
- ];
391
- continue;
392
- }
393
- const renameConflict = otherSelectedLeafs.find((candidate) => candidate.leaf.linkName === leaf.linkName);
394
- if (renameConflict) {
395
- const projectedName = projectedNames.get(leaf.id) ??
396
- buildProjectedSkillName(currentSummary.source.displayName, leaf.linkName, parseGitHubRepo(currentSummary.source.locator)?.owner);
397
- warningsByLeafId[leaf.id] = [
398
- `conflicts with ${formatGroupLabel(renameConflict.source)}, will deploy as ${projectedName}`,
399
- ];
400
- }
401
- }
402
- return warningsByLeafId;
403
- }
404
- function buildProjectionNameMap({ drafts, summaries, sourceId, }) {
405
- const currentDraft = drafts[sourceId] ?? EMPTY_DRAFT;
406
- const currentSummary = summaries.find((summary) => summary.source.id === sourceId);
407
- if (!currentSummary || currentDraft.enabledTargets.length === 0) {
408
- return new Map();
409
- }
410
- const currentEnabledTargets = new Set(currentDraft.enabledTargets);
411
- const selectedLeafIds = new Set(currentDraft.selectedLeafIds);
412
- return resolveProjectedSkillNames([
413
- ...summaries.flatMap((summary) => {
414
- if (summary.source.id === sourceId) {
415
- return [];
416
- }
417
- const draft = drafts[summary.source.id] ?? EMPTY_DRAFT;
418
- const hasTargetOverlap = draft.enabledTargets.some((target) => currentEnabledTargets.has(target));
419
- if (!hasTargetOverlap) {
420
- return [];
421
- }
422
- return draft.selectedLeafIds
423
- .map((leafId) => summary.leafs.find((leaf) => leaf.id === leafId))
424
- .filter((leaf) => Boolean(leaf))
425
- .map((leaf) => ({
426
- leafId: leaf.id,
427
- groupId: summary.source.id,
428
- groupName: summary.source.displayName,
429
- groupAuthor: parseGitHubRepo(summary.source.locator)?.owner,
430
- skillName: leaf.linkName,
431
- }));
432
- }),
433
- ...currentSummary.leafs
434
- .filter((leaf) => selectedLeafIds.has(leaf.id))
435
- .map((leaf) => ({
436
- leafId: leaf.id,
437
- groupId: currentSummary.source.id,
438
- groupName: currentSummary.source.displayName,
439
- groupAuthor: parseGitHubRepo(currentSummary.source.locator)?.owner,
440
- skillName: leaf.linkName,
441
- })),
442
- ]);
443
- }
444
- export function buildScrollableRows(items, cursorIndex, visibleCount, keyPrefix = "scroll", reserveHintSlots = false) {
445
- const safeVisibleCount = Math.max(1, visibleCount);
446
- const needsHints = items.length > safeVisibleCount;
447
- const hasHintSlots = needsHints || (reserveHintSlots && safeVisibleCount >= 3);
448
- const adjustedVisibleCount = hasHintSlots ? Math.max(1, safeVisibleCount - 2) : safeVisibleCount;
449
- const windowed = getWindowedRows(items, cursorIndex, adjustedVisibleCount);
450
- const hasUp = windowed.start > 0;
451
- const hasDown = windowed.end < items.length;
452
- const rows = [];
453
- if (hasHintSlots) {
454
- rows.push({
455
- key: `__scroll_up__:${keyPrefix}`,
456
- text: hasUp ? "↑ more" : "",
457
- active: false,
458
- color: "gray",
459
- });
460
- }
461
- rows.push(...windowed.rows);
462
- if (hasHintSlots) {
463
- rows.push({
464
- key: `__scroll_down__:${keyPrefix}`,
465
- text: hasDown ? "↓ more" : "",
466
- active: false,
467
- color: "gray",
468
- });
469
- }
470
- return {
471
- rows,
472
- start: windowed.start,
473
- end: windowed.end,
474
- };
475
- }
476
- export function ConfigApp({ app, availableTargets, summaries, initialDrafts, bootStatus, }) {
477
- const { exit } = useApp();
478
- const { stdout } = useStdout();
479
- const terminalSize = useTerminalSize(stdout);
480
- const previewRequestIds = useRef({});
481
- const saveRequestIds = useRef({});
482
- const updateRequestIds = useRef({});
483
- const updatedTimers = useRef({});
484
- const [summaryList, setSummaryList] = useState(summaries);
485
- const groupViews = buildConfigGroups(summaryList);
486
- const [selectedGroupIndex, setSelectedGroupIndex] = useState(groupViews.length > 0 ? 0 : -1);
487
- const [groupCursor, setGroupCursor] = useState(groupViews.length > 0 ? 0 : -1);
488
- const [focus, setFocus] = useState("groups");
489
- const [skillCursor, setSkillCursor] = useState(0);
490
- const [targetCursor, setTargetCursor] = useState(0);
491
- const [actionCursor, setActionCursor] = useState(0);
492
- const [drafts, setDrafts] = useState(initialDrafts);
493
- const [savedDrafts, setSavedDrafts] = useState(initialDrafts);
494
- const [previewBySourceId, setPreviewBySourceId] = useState({});
495
- const [saveStateBySourceId, setSaveStateBySourceId] = useState({});
496
- const [updateStateBySourceId, setUpdateStateBySourceId] = useState({});
497
- const [deleteState, setDeleteState] = useState({
498
- phase: "idle",
499
- sourceId: undefined,
500
- message: undefined,
501
- });
502
- const selectedGroup = groupViews[selectedGroupIndex] ?? EMPTY_CONFIG_GROUP;
503
- const selectedSkillRows = buildConfigGroupSkillRows(selectedGroup);
504
- const selectedSkillRow = skillCursor > 0 ? selectedSkillRows[skillCursor - 1] : undefined;
505
- const activeSummary = selectedSkillRow?.summary ?? selectedGroup.summaries[0];
506
- const selectedSourceId = activeSummary?.source.id ?? "";
507
- const selectedDraft = drafts[selectedSourceId] ?? EMPTY_DRAFT;
508
- const savedDraft = savedDrafts[selectedSourceId] ?? EMPTY_DRAFT;
509
- const isDirty = !draftsEqual(selectedDraft, savedDraft);
510
- const leafIds = activeSummary?.leafs.map((leaf) => leaf.id) ?? [];
511
- const groupSelectedLeafCount = getGroupSelectedLeafCount({
512
- drafts,
513
- group: selectedGroup,
514
- });
515
- const visibleTargets = availableTargets;
516
- const agentInteractiveCount = visibleTargets.length > 0 ? visibleTargets.length + 1 : 0;
517
- const skillInteractiveCount = selectedSkillRows.length > 0 ? selectedSkillRows.length + 1 : 0;
518
- const treeState = selectedGroup.kind === "clawhub"
519
- ? {
520
- allLeafIds: selectedSkillRows.map((row) => row.leaf.id),
521
- selectedLeafIds: selectedGroup.summaries.flatMap((summary) => drafts[summary.source.id]?.selectedLeafIds ?? []),
522
- }
523
- : {
524
- allLeafIds: leafIds,
525
- selectedLeafIds: selectedDraft.selectedLeafIds,
526
- };
527
- const parentSelectionState = getParentSelectionState(treeState);
528
- const visibleEnabledTargets = visibleTargets.filter((target) => selectedDraft.enabledTargets.includes(target));
529
- const allTargetsSelected = visibleTargets.length > 0 && visibleEnabledTargets.length === visibleTargets.length;
530
- const projectionWarningsByLeafId = buildProjectionWarningMap({
531
- drafts,
532
- summaries: summaryList,
533
- sourceId: selectedSourceId,
534
- });
535
- const projectedNamesByLeafId = buildProjectionNameMap({
536
- drafts,
537
- summaries: summaryList,
538
- sourceId: selectedSourceId,
539
- });
540
- const failedBootBySourceId = new Map(bootStatus.failedSources.map((item) => [item.sourceId, item.message]));
541
- const previewState = previewBySourceId[selectedSourceId] ?? EMPTY_PREVIEW;
542
- const changeCount = getActionChangeCount(previewState.actions);
543
- const saveState = saveStateBySourceId[selectedSourceId] ?? {
544
- phase: "idle",
545
- message: undefined,
546
- };
547
- const updateState = updateStateBySourceId[selectedSourceId] ?? {
548
- phase: "idle",
549
- message: undefined,
550
- };
551
- const isSelectedDelete = deleteState.sourceId === selectedSourceId;
552
- const canDelete = selectedGroup.kind === "clawhub" ? focus === "detail.skills" && skillCursor > 0 : true;
553
- const showDeleteAction = selectedGroup.kind !== "clawhub";
554
- const actionCount = showDeleteAction ? 2 : 1;
555
- const statusDisplay = getStatusDisplay({
556
- deleteState,
557
- isSelectedDelete,
558
- saveState,
559
- updateState,
560
- });
561
- const canEditSelected = activeSummary !== undefined &&
562
- updateState.phase !== "updating" &&
563
- deleteState.phase !== "deleting";
564
- const canRunActions = activeSummary !== undefined &&
565
- saveState.phase !== "saving" &&
566
- updateState.phase !== "updating" &&
567
- deleteState.phase !== "deleting";
568
- useEffect(() => {
569
- return () => {
570
- for (const timer of Object.values(updatedTimers.current)) {
571
- if (timer) {
572
- clearTimeout(timer);
573
- }
574
- }
575
- };
576
- }, []);
577
- useEffect(() => {
578
- if (!activeSummary) {
579
- return;
580
- }
581
- const sourceId = activeSummary.source.id;
582
- const requestId = (previewRequestIds.current[sourceId] ?? 0) + 1;
583
- previewRequestIds.current[sourceId] = requestId;
584
- setPreviewBySourceId((current) => ({
585
- ...current,
586
- [sourceId]: {
587
- ...(current[sourceId] ?? EMPTY_PREVIEW),
588
- errorMessage: undefined,
589
- loading: true,
590
- requestId,
591
- },
592
- }));
593
- let disposed = false;
594
- void app.previewDraft(sourceId, selectedDraft).then((result) => {
595
- if (disposed) {
596
- return;
597
- }
598
- setPreviewBySourceId((current) => {
599
- const currentState = current[sourceId] ?? EMPTY_PREVIEW;
600
- if (currentState.requestId !== requestId) {
601
- return current;
602
- }
603
- if (!result.ok) {
604
- return {
605
- ...current,
606
- [sourceId]: {
607
- actions: [],
608
- blockedCount: 0,
609
- errorMessage: firstErrorMessage(result),
610
- loading: false,
611
- requestId,
612
- },
613
- };
614
- }
615
- return {
616
- ...current,
617
- [sourceId]: {
618
- actions: result.data.plan.actions,
619
- blockedCount: result.data.plan.blocked.length,
620
- errorMessage: undefined,
621
- loading: false,
622
- requestId,
623
- },
624
- };
625
- });
626
- });
627
- return () => {
628
- disposed = true;
629
- };
630
- }, [app, activeSummary, selectedDraft]);
631
- useEffect(() => {
632
- if (!activeSummary || draftsEqual(selectedDraft, savedDraft)) {
633
- return;
634
- }
635
- if (saveState.phase === "saving" || saveState.phase === "failed") {
636
- return;
637
- }
638
- const sourceId = activeSummary.source.id;
639
- const requestId = (saveRequestIds.current[sourceId] ?? 0) + 1;
640
- saveRequestIds.current[sourceId] = requestId;
641
- const draftToSave = normalizeDraft(selectedDraft);
642
- setSaveStateBySourceId((current) => ({
643
- ...current,
644
- [sourceId]: {
645
- phase: "saving",
646
- message: "saving changes...",
647
- },
648
- }));
649
- void app.applyDraft(sourceId, draftToSave).then((result) => {
650
- setSaveStateBySourceId((current) => {
651
- if ((saveRequestIds.current[sourceId] ?? 0) !== requestId) {
652
- return current;
653
- }
654
- if (!result.ok) {
655
- return {
656
- ...current,
657
- [sourceId]: {
658
- phase: "failed",
659
- message: firstErrorMessage(result),
660
- },
661
- };
662
- }
663
- const appliedDraft = normalizeDraft(result.data.draft);
664
- setDrafts((draftsCurrent) => ({
665
- ...draftsCurrent,
666
- [sourceId]: appliedDraft,
667
- }));
668
- setSavedDrafts((savedCurrent) => ({
669
- ...savedCurrent,
670
- [sourceId]: appliedDraft,
671
- }));
672
- setPreviewBySourceId((previewCurrent) => ({
673
- ...previewCurrent,
674
- [sourceId]: {
675
- actions: result.data.actions,
676
- blockedCount: result.data.actions.filter((action) => action.kind === "blocked")
677
- .length,
678
- errorMessage: undefined,
679
- loading: false,
680
- requestId: previewRequestIds.current[sourceId] ?? 0,
681
- },
682
- }));
683
- return {
684
- ...current,
685
- [sourceId]: {
686
- phase: "saved",
687
- message: "saved",
688
- },
689
- };
690
- });
691
- });
692
- }, [app, saveState.phase, savedDraft, selectedDraft, activeSummary]);
693
- const updateSelectedDraft = (updater) => {
694
- if (!activeSummary || !canEditSelected) {
695
- return;
696
- }
697
- const sourceId = activeSummary.source.id;
698
- setDrafts((current) => {
699
- const currentDraft = current[sourceId] ?? EMPTY_DRAFT;
700
- const nextDraft = normalizeDraft(updater(currentDraft));
701
- if (draftsEqual(currentDraft, nextDraft)) {
702
- return current;
703
- }
704
- return {
705
- ...current,
706
- [sourceId]: nextDraft,
707
- };
708
- });
709
- setSaveStateBySourceId((current) => {
710
- const state = current[sourceId];
711
- if (!state || state.phase === "idle") {
712
- return current;
713
- }
714
- return {
715
- ...current,
716
- [sourceId]: {
717
- phase: "idle",
718
- message: undefined,
719
- },
720
- };
721
- });
722
- };
723
- const handleUpdate = () => {
724
- if (!activeSummary || !selectedGroup || !canRunActions) {
725
- return;
726
- }
727
- const sourceId = activeSummary.source.id;
728
- const sourceIds = selectedGroup.kind === "clawhub"
729
- ? selectedGroup.summaries.map((summary) => summary.source.id)
730
- : [sourceId];
731
- const requestId = (updateRequestIds.current[sourceId] ?? 0) + 1;
732
- updateRequestIds.current[sourceId] = requestId;
733
- for (const id of sourceIds) {
734
- previewRequestIds.current[id] = (previewRequestIds.current[id] ?? 0) + 1;
735
- saveRequestIds.current[id] = (saveRequestIds.current[id] ?? 0) + 1;
736
- }
737
- if (updatedTimers.current[sourceId]) {
738
- clearTimeout(updatedTimers.current[sourceId]);
739
- updatedTimers.current[sourceId] = undefined;
740
- }
741
- const snapshot = captureFocusSnapshot({
742
- actionCursor,
743
- agentCursor: targetCursor,
744
- availableTargets,
745
- focus,
746
- groupId: selectedGroup.id,
747
- selectedGroupIndex,
748
- selectedSummary: activeSummary,
749
- skillCursor,
750
- });
751
- setUpdateStateBySourceId((current) => ({
752
- ...current,
753
- [sourceId]: {
754
- phase: "updating",
755
- message: `updating ${selectedGroup.title}...`,
756
- },
757
- }));
758
- void app.updateSources(sourceIds).then(async (result) => {
759
- if ((updateRequestIds.current[sourceId] ?? 0) !== requestId) {
760
- return;
761
- }
762
- if (!result.ok) {
763
- setUpdateStateBySourceId((current) => ({
764
- ...current,
765
- [sourceId]: {
766
- phase: "failed",
767
- message: firstErrorMessage(result),
768
- },
769
- }));
770
- return;
771
- }
772
- const configResult = await app.getConfigData();
773
- if ((updateRequestIds.current[sourceId] ?? 0) !== requestId) {
774
- return;
775
- }
776
- if (!configResult.ok) {
777
- setUpdateStateBySourceId((current) => ({
778
- ...current,
779
- [sourceId]: {
780
- phase: "failed",
781
- message: firstErrorMessage(configResult),
782
- },
783
- }));
784
- return;
785
- }
786
- const nextSummaries = configResult.data.summaries;
787
- const nextDrafts = buildDraftsFromSummaries(nextSummaries);
788
- const nextIds = new Set(nextSummaries.map((summary) => summary.source.id));
789
- const nextGroups = buildConfigGroups(nextSummaries);
790
- const nextFocusState = reconcileFocusAfterReload({
791
- availableTargets,
792
- nextGroups,
793
- snapshot,
794
- });
795
- setSummaryList(nextSummaries);
796
- setDrafts(nextDrafts);
797
- setSavedDrafts(nextDrafts);
798
- setPreviewBySourceId((current) => pruneSourceMap(current, nextIds));
799
- setSaveStateBySourceId((current) => ({
800
- ...pruneSourceMap(current, nextIds),
801
- [sourceId]: {
802
- phase: "saved",
803
- message: "saved",
804
- },
805
- }));
806
- setSelectedGroupIndex(nextFocusState.selectedGroupIndex);
807
- setGroupCursor(nextFocusState.groupCursor);
808
- setFocus(nextFocusState.focus);
809
- setTargetCursor(nextFocusState.agentCursor);
810
- setSkillCursor(nextFocusState.skillCursor);
811
- setActionCursor(nextFocusState.actionCursor);
812
- setUpdateStateBySourceId((current) => ({
813
- ...pruneSourceMap(current, nextIds),
814
- [sourceId]: {
815
- phase: "updated",
816
- message: "updated",
817
- },
818
- }));
819
- updatedTimers.current[sourceId] = setTimeout(() => {
820
- if ((updateRequestIds.current[sourceId] ?? 0) !== requestId) {
821
- return;
822
- }
823
- setUpdateStateBySourceId((current) => {
824
- const state = current[sourceId];
825
- if (!state || state.phase !== "updated") {
826
- return current;
827
- }
828
- return {
829
- ...current,
830
- [sourceId]: {
831
- phase: "idle",
832
- message: undefined,
833
- },
834
- };
835
- });
836
- updatedTimers.current[sourceId] = undefined;
837
- }, UPDATED_FEEDBACK_MS);
838
- });
839
- };
840
- const handleDelete = () => {
841
- if (!activeSummary || !canRunActions) {
842
- return;
843
- }
844
- const sourceId = activeSummary.source.id;
845
- previewRequestIds.current[sourceId] = (previewRequestIds.current[sourceId] ?? 0) + 1;
846
- saveRequestIds.current[sourceId] = (saveRequestIds.current[sourceId] ?? 0) + 1;
847
- updateRequestIds.current[sourceId] = (updateRequestIds.current[sourceId] ?? 0) + 1;
848
- if (updatedTimers.current[sourceId]) {
849
- clearTimeout(updatedTimers.current[sourceId]);
850
- updatedTimers.current[sourceId] = undefined;
851
- }
852
- setDeleteState({
853
- phase: "deleting",
854
- sourceId,
855
- message: `deleting ${formatGroupLabel(activeSummary.source)}...`,
856
- });
857
- void app.uninstall([sourceId]).then((result) => {
858
- if (!result.ok) {
859
- setDeleteState({
860
- phase: "failed",
861
- sourceId,
862
- message: firstErrorMessage(result),
863
- });
864
- return;
865
- }
866
- const nextSummaries = summaryList.filter((summary) => summary.source.id !== sourceId);
867
- const nextGroups = buildConfigGroups(nextSummaries);
868
- const nextSelectedGroupIndex = getNextSelectionIndexAfterDelete(selectedGroupIndex, nextGroups.length);
869
- setSummaryList(nextSummaries);
870
- setDrafts((current) => removeSourceFromMap(current, sourceId));
871
- setSavedDrafts((current) => removeSourceFromMap(current, sourceId));
872
- setPreviewBySourceId((current) => removeSourceFromMap(current, sourceId));
873
- setSaveStateBySourceId((current) => removeSourceFromMap(current, sourceId));
874
- setUpdateStateBySourceId((current) => removeSourceFromMap(current, sourceId));
875
- setDeleteState({
876
- phase: "idle",
877
- sourceId: undefined,
878
- message: undefined,
879
- });
880
- setSelectedGroupIndex(nextSelectedGroupIndex);
881
- setGroupCursor(nextSelectedGroupIndex);
882
- setFocus("groups");
883
- setTargetCursor(0);
884
- setSkillCursor(0);
885
- setActionCursor(0);
886
- });
887
- };
888
- useInput((input, key) => {
889
- if (!activeSummary) {
890
- if (input === "q" || key.escape || (input === "c" && key.ctrl)) {
891
- exit();
892
- }
893
- return;
894
- }
895
- if (input === "c" && key.ctrl) {
896
- exit();
897
- return;
898
- }
899
- const requestedAction = getRequestedAction({
900
- actionCursor,
901
- canDelete,
902
- focus,
903
- input,
904
- keyReturn: Boolean(key.return),
905
- });
906
- if (requestedAction === "update") {
907
- handleUpdate();
908
- return;
909
- }
910
- if (requestedAction === "delete") {
911
- handleDelete();
912
- return;
913
- }
914
- if (input === "q" || key.escape) {
915
- if (focus !== "groups") {
916
- setFocus("groups");
917
- return;
918
- }
919
- exit();
920
- return;
921
- }
922
- if (key.tab) {
923
- if (focus === "groups") {
924
- setFocus(getInitialDetailFocus({
925
- hasAgents: agentInteractiveCount > 0,
926
- hasSkills: skillInteractiveCount > 0,
927
- }));
928
- return;
929
- }
930
- setFocus("groups");
931
- return;
932
- }
933
- if (key.rightArrow && focus === "groups") {
934
- setFocus(getInitialDetailFocus({
935
- hasAgents: agentInteractiveCount > 0,
936
- hasSkills: skillInteractiveCount > 0,
937
- }));
938
- return;
939
- }
940
- if (key.leftArrow && focus !== "groups") {
941
- setFocus("groups");
942
- return;
943
- }
944
- if (focus === "groups") {
945
- if (key.downArrow) {
946
- const next = Math.min(groupCursor + 1, Math.max(0, groupViews.length - 1));
947
- setGroupCursor(next);
948
- setSelectedGroupIndex(next);
949
- setTargetCursor(0);
950
- setSkillCursor(0);
951
- setActionCursor(0);
952
- }
953
- if (key.upArrow) {
954
- const next = Math.max(groupCursor - 1, 0);
955
- setGroupCursor(next);
956
- setSelectedGroupIndex(next);
957
- setTargetCursor(0);
958
- setSkillCursor(0);
959
- setActionCursor(0);
960
- }
961
- return;
962
- }
963
- if (key.downArrow || key.upArrow) {
964
- const next = moveDetailFocus({
965
- actionCursor,
966
- actionCount,
967
- agentCount: agentInteractiveCount,
968
- agentCursor: targetCursor,
969
- direction: key.downArrow ? 1 : -1,
970
- focus: focus,
971
- skillCount: skillInteractiveCount,
972
- skillCursor,
973
- });
974
- setFocus(next.focus);
975
- setTargetCursor(next.agentCursor);
976
- setSkillCursor(next.skillCursor);
977
- setActionCursor(next.actionCursor);
978
- return;
979
- }
980
- if (focus === "detail.agents" && input === " " && agentInteractiveCount > 0) {
981
- updateSelectedDraft((currentDraft) => {
982
- if (targetCursor === 0) {
983
- const enabledTargets = new Set(currentDraft.enabledTargets);
984
- const nextSelectAll = !visibleTargets.every((target) => enabledTargets.has(target));
985
- for (const target of visibleTargets) {
986
- if (nextSelectAll) {
987
- enabledTargets.add(target);
988
- }
989
- else {
990
- enabledTargets.delete(target);
991
- }
992
- }
993
- return {
994
- ...currentDraft,
995
- enabledTargets: TARGET_ORDER.filter((target) => enabledTargets.has(target)),
996
- };
997
- }
998
- const target = visibleTargets[targetCursor - 1];
999
- if (!target) {
1000
- return currentDraft;
1001
- }
1002
- const enabledTargets = new Set(currentDraft.enabledTargets);
1003
- if (enabledTargets.has(target)) {
1004
- enabledTargets.delete(target);
1005
- }
1006
- else {
1007
- enabledTargets.add(target);
1008
- }
1009
- return {
1010
- ...currentDraft,
1011
- enabledTargets: TARGET_ORDER.filter((item) => enabledTargets.has(item)),
1012
- };
1013
- });
1014
- return;
1015
- }
1016
- if (focus === "detail.skills" && input === " " && skillInteractiveCount > 0) {
1017
- updateSelectedDraft((currentDraft) => {
1018
- if (selectedGroup.kind === "clawhub") {
1019
- if (skillCursor === 0) {
1020
- return currentDraft;
1021
- }
1022
- const row = selectedSkillRows[skillCursor - 1];
1023
- if (!row) {
1024
- return currentDraft;
1025
- }
1026
- if (row.summary.source.id !== selectedSourceId) {
1027
- return currentDraft;
1028
- }
1029
- const baseState = {
1030
- allLeafIds: row.summary.leafs.map((leaf) => leaf.id),
1031
- selectedLeafIds: currentDraft.selectedLeafIds,
1032
- };
1033
- const nextState = toggleChild(baseState, row.leaf.id);
1034
- return {
1035
- ...currentDraft,
1036
- selectedLeafIds: nextState.selectedLeafIds,
1037
- };
1038
- }
1039
- const baseState = {
1040
- allLeafIds: leafIds,
1041
- selectedLeafIds: currentDraft.selectedLeafIds,
1042
- };
1043
- const nextState = skillCursor === 0
1044
- ? toggleParent(baseState)
1045
- : toggleChild(baseState, leafIds[skillCursor - 1]);
1046
- return {
1047
- ...currentDraft,
1048
- selectedLeafIds: nextState.selectedLeafIds,
1049
- };
1050
- });
1051
- }
1052
- });
1053
- if (groupViews.length === 0) {
1054
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "No skills groups yet" }), _jsx(Text, { children: "Run skill-flow add <source> to install your first group." }), _jsx(Text, { dimColor: true, children: deleteState.message ?? "Press q or esc to exit." })] }));
1055
- }
1056
- const renderSummary = activeSummary;
1057
- const terminalRows = terminalSize.rows;
1058
- const terminalColumns = terminalSize.columns;
1059
- const paneHeight = Math.max(12, terminalRows - 3);
1060
- const [groupsWidth, detailWidth] = getPaneWidths(terminalColumns);
1061
- const bodyRowCount = getPaneViewportCount(paneHeight);
1062
- const groupRows = buildScrollableRows(groupViews.map((group, index) => {
1063
- const isCursor = groupCursor === index;
1064
- const isSelected = selectedGroupIndex === index;
1065
- return {
1066
- key: group.id,
1067
- text: `${group.title}${group.kind === "clawhub"
1068
- ? group.summaries.some((summary) => failedBootBySourceId.has(summary.source.id))
1069
- ? " !"
1070
- : ""
1071
- : failedBootBySourceId.has(group.summaries[0]?.source.id ?? "")
1072
- ? " !"
1073
- : ""}`,
1074
- active: focus === "groups" && isCursor,
1075
- activeColor: "cyan",
1076
- bold: isSelected,
1077
- color: isSelected ? "white" : "gray",
1078
- };
1079
- }), Math.max(0, groupCursor), bodyRowCount, "groups");
1080
- const agentRows = visibleTargets.length > 0
1081
- ? [
1082
- {
1083
- key: "__all_targets__",
1084
- text: `${selectionMarker(allTargetsSelected ? "full" : visibleEnabledTargets.length > 0 ? "partial" : "empty")} All Agents`,
1085
- active: focus === "detail.agents" && targetCursor === 0,
1086
- bold: true,
1087
- color: undefined,
1088
- },
1089
- ...visibleTargets.map((target, index) => ({
1090
- key: target,
1091
- text: `${selectionMarker(selectedDraft.enabledTargets.includes(target) ? "full" : "empty")} ${TARGET_LABELS[target]}`,
1092
- active: focus === "detail.agents" && targetCursor === index + 1,
1093
- color: "gray",
1094
- })),
1095
- ]
1096
- : [
1097
- {
1098
- key: "__no_targets__",
1099
- text: "No detected agent targets",
1100
- active: false,
1101
- color: "gray",
1102
- },
1103
- ];
1104
- const skillRows = selectedSkillRows.length > 0
1105
- ? [
1106
- {
1107
- key: "__all__",
1108
- text: `${selectionMarker(parentSelectionState)} All Skills`,
1109
- active: focus === "detail.skills" && skillCursor === 0,
1110
- bold: true,
1111
- color: undefined,
1112
- },
1113
- ...selectedSkillRows.map((row, index) => {
1114
- const rowDraft = drafts[row.summary.source.id] ?? EMPTY_DRAFT;
1115
- const rowSelected = rowDraft.selectedLeafIds.includes(row.leaf.id);
1116
- const warnings = [
1117
- ...row.leaf.metadataWarnings,
1118
- ...(row.summary.source.id === selectedSourceId
1119
- ? (projectionWarningsByLeafId[row.leaf.id] ?? [])
1120
- : []),
1121
- ];
1122
- const inlineWarning = warnings[0] ? ` (${warnings[0]})` : "";
1123
- const projectedLabel = row.summary.source.id === selectedSourceId && rowSelected
1124
- ? (projectedNamesByLeafId.get(row.leaf.id) ?? row.leaf.linkName)
1125
- : row.leaf.linkName;
1126
- const label = selectedGroup.kind === "clawhub"
1127
- ? `${projectedLabel} · ${formatGroupLabel(row.summary.source)}`
1128
- : projectedLabel;
1129
- return {
1130
- key: row.leaf.id,
1131
- text: `${selectionMarker(rowSelected ? "full" : "empty")} ${label}${inlineWarning}`,
1132
- active: focus === "detail.skills" && skillCursor === index + 1,
1133
- color: warnings.length > 0 ? "yellow" : "gray",
1134
- };
1135
- }),
1136
- ]
1137
- : [
1138
- {
1139
- key: "__no_skills__",
1140
- text: "No skills in this group",
1141
- active: false,
1142
- color: "gray",
1143
- },
1144
- ];
1145
- const previewLabel = buildPreviewLabel({
1146
- blockedCount: previewState.blockedCount,
1147
- changeCount,
1148
- errorMessage: previewState.errorMessage,
1149
- loading: previewState.loading,
1150
- });
1151
- const metadataRows = buildDetailMetadataRows({
1152
- detailWidth,
1153
- group: selectedGroup,
1154
- summary: renderSummary,
1155
- });
1156
- const actionRows = buildActionRows({
1157
- actionCursor,
1158
- canRunActions,
1159
- deleteState,
1160
- focus,
1161
- isSelectedDelete,
1162
- showDeleteAction,
1163
- updateState,
1164
- });
1165
- const fixedRows = metadataRows.length +
1166
- 4 +
1167
- actionRows.length;
1168
- const sectionBudget = Math.max(2, bodyRowCount - fixedRows);
1169
- const hasAgentSection = visibleTargets.length > 0;
1170
- const hasSkillSection = selectedSkillRows.length > 0;
1171
- const agentBudget = hasAgentSection && hasSkillSection
1172
- ? Math.min(Math.max(1, sectionBudget - 1), Math.max(4, Math.floor(sectionBudget * 0.4)))
1173
- : sectionBudget;
1174
- const skillBudget = hasAgentSection && hasSkillSection ? Math.max(1, sectionBudget - agentBudget) : sectionBudget;
1175
- const visibleAgentRows = buildScrollableRows(agentRows, Math.min(targetCursor, Math.max(0, agentRows.length - 1)), Math.max(1, agentBudget), "agents", true);
1176
- const visibleSkillRows = buildScrollableRows(skillRows, Math.min(skillCursor, Math.max(0, skillRows.length - 1)), Math.max(1, skillBudget), "skills", true);
1177
- const filledSkillRows = [...visibleSkillRows.rows];
1178
- const skillPaddingCount = Math.max(0, skillBudget - filledSkillRows.length);
1179
- for (let index = 0; index < skillPaddingCount; index += 1) {
1180
- filledSkillRows.push({
1181
- key: `__skills_fill__:${index}`,
1182
- text: "",
1183
- active: false,
1184
- color: undefined,
1185
- });
1186
- }
1187
- const detailRows = [
1188
- ...metadataRows,
1189
- {
1190
- key: "__agents_gap__",
1191
- text: "",
1192
- active: false,
1193
- color: undefined,
1194
- },
1195
- {
1196
- key: "__agents_header__",
1197
- text: `Select Agents (${visibleEnabledTargets.length}/${availableTargets.length})`,
1198
- active: false,
1199
- bold: true,
1200
- color: undefined,
1201
- },
1202
- ...visibleAgentRows.rows,
1203
- {
1204
- key: "__skills_gap__",
1205
- text: "",
1206
- active: false,
1207
- color: undefined,
1208
- },
1209
- {
1210
- key: "__skills_header__",
1211
- text: `Select Skills (${selectedGroup.kind === "clawhub" ? groupSelectedLeafCount : selectedDraft.selectedLeafIds.length}/${selectedSkillRows.length})`,
1212
- active: false,
1213
- bold: true,
1214
- color: undefined,
1215
- },
1216
- ...filledSkillRows,
1217
- ];
1218
- const topBar = buildTopBar({
1219
- width: terminalColumns,
1220
- isDirty,
1221
- changeCount,
1222
- showDelete: canDelete,
1223
- statusLabel: statusDisplay.label,
1224
- });
1225
- return (_jsxs(Box, { flexDirection: "column", height: terminalRows, children: [_jsx(ConfigHeader, {}), _jsxs(Box, { children: [_jsx(Pane, { active: focus === "groups", footer: `${groupViews.length} groups`, gapAfter: true, height: paneHeight, title: "Skills Groups", width: groupsWidth, children: renderPaneRows(groupRows.rows, bodyRowCount, groupsWidth) }), _jsx(Pane, { active: focus !== "groups", footer: buildCommandBar(focus), height: paneHeight, title: "Group Detail", width: detailWidth, children: renderPaneRows(detailRows, bodyRowCount, detailWidth, actionRows) })] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: buildFooterHints(focus, canDelete) })] }));
1226
- }
1227
- export function ConfigBootstrapApp({ app }) {
1228
- const { exit } = useApp();
1229
- const { stdout } = useStdout();
1230
- const terminalSize = useTerminalSize(stdout);
1231
- const [state, setState] = useState({
1232
- phase: "loading",
1233
- logs: ["Booting config..."],
1234
- });
1235
- useEffect(() => {
1236
- let cancelled = false;
1237
- void app.configCoordinator.bootstrapWorkspaceState((event) => {
1238
- if (cancelled) {
1239
- return;
1240
- }
1241
- setState((current) => {
1242
- const nextLogs = [...current.logs, event.message].slice(-6);
1243
- return {
1244
- ...current,
1245
- logs: nextLogs,
1246
- };
1247
- });
1248
- }).then((result) => {
1249
- if (cancelled) {
1250
- return;
1251
- }
1252
- if (!result.ok) {
1253
- setState((current) => ({
1254
- phase: "error",
1255
- logs: current.logs,
1256
- message: firstErrorMessage(result),
1257
- }));
1258
- return;
1259
- }
1260
- setState((current) => ({
1261
- phase: "ready",
1262
- logs: current.logs,
1263
- availableTargets: result.data.availableTargets,
1264
- summaries: result.data.summaries,
1265
- initialDrafts: result.data.initialDrafts,
1266
- audit: result.data.audit,
1267
- bootStatus: result.data.bootStatus,
1268
- }));
1269
- });
1270
- return () => {
1271
- cancelled = true;
1272
- };
1273
- }, [app]);
1274
- useInput((input, key) => {
1275
- if (state.phase === "ready") {
1276
- return;
1277
- }
1278
- if (input === "q" || key.escape || (input === "c" && key.ctrl)) {
1279
- exit();
1280
- }
1281
- });
1282
- if (state.phase === "ready") {
1283
- return (_jsx(ConfigApp, { app: app, availableTargets: state.availableTargets, summaries: state.summaries, initialDrafts: state.initialDrafts, bootStatus: state.bootStatus }));
1284
- }
1285
- const rows = terminalSize.rows;
1286
- const bootLogs = state.logs.slice(-4);
1287
- return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsx(ConfigHeader, { title: "Skill Flow Config" }), _jsx(Text, { color: "gray", children: state.phase === "loading"
1288
- ? "Checking groups, skills, targets, and current paths..."
1289
- : "Bootstrap failed" }), state.phase === "error" ? _jsx(Text, { color: "red", children: state.message }) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "BOOT LOG" }), bootLogs.map((log) => (_jsx(Text, { color: "gray", children: log }, log))), _jsx(Text, { color: "gray", children: "Press q or Esc to exit." })] })] }));
1290
- }
1291
- function ConfigHeader({ title }) {
1292
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { backgroundColor: "cyan", color: "black", children: ADD_BADGE_TEXT }), title ? _jsx(Text, { bold: true, children: title }) : null] }));
1293
- }
1294
- function useTerminalSize(stdout) {
1295
- const [size, setSize] = useState(() => ({
1296
- rows: stdout.rows ?? 24,
1297
- columns: stdout.columns ?? 120,
1298
- }));
1299
- useEffect(() => {
1300
- const handleResize = () => {
1301
- setSize({
1302
- rows: stdout.rows ?? 24,
1303
- columns: stdout.columns ?? 120,
1304
- });
1305
- };
1306
- handleResize();
1307
- stdout.on("resize", handleResize);
1308
- return () => {
1309
- stdout.off("resize", handleResize);
1310
- };
1311
- }, [stdout]);
1312
- return size;
1313
- }
1314
- function buildSaveStatusLabel(saveState) {
1315
- if (saveState.phase === "saving") {
1316
- return "Saving";
1317
- }
1318
- if (saveState.phase === "saved") {
1319
- return "Saved";
1320
- }
1321
- if (saveState.phase === "failed") {
1322
- return "Failed";
1323
- }
1324
- return "Clean";
1325
- }
1326
- function buildPreviewLabel({ blockedCount, changeCount, errorMessage, loading, }) {
1327
- if (loading) {
1328
- return "planning...";
1329
- }
1330
- if (errorMessage) {
1331
- return "failed";
1332
- }
1333
- if (blockedCount > 0 && changeCount > 0) {
1334
- return `${changeCount} changes, ${blockedCount} blocked`;
1335
- }
1336
- if (blockedCount > 0) {
1337
- return `${blockedCount} blocked`;
1338
- }
1339
- return `${changeCount} changes`;
1340
- }
1341
- function buildAlerts({ deleteState, failedBootMessages, isSelectedDelete, previewState, projectionWarningsByLeafId, saveState, selectedDraft, selectedSummary, updateState, }) {
1342
- const alerts = [];
1343
- if (updateState.phase === "failed" && updateState.message) {
1344
- alerts.push({ level: "error", message: `Update failed: ${updateState.message}` });
1345
- }
1346
- if (isSelectedDelete && deleteState.phase === "failed" && deleteState.message) {
1347
- alerts.push({ level: "error", message: `Delete failed: ${deleteState.message}` });
1348
- }
1349
- if (saveState.phase === "failed" && saveState.message) {
1350
- alerts.push({ level: "error", message: `Save failed: ${saveState.message}` });
1351
- }
1352
- if (previewState.errorMessage) {
1353
- alerts.push({ level: "error", message: `Preview failed: ${previewState.errorMessage}` });
1354
- }
1355
- for (const failedBootMessage of failedBootMessages) {
1356
- alerts.push({ level: "error", message: `Boot issue: ${failedBootMessage}` });
1357
- }
1358
- for (const action of previewState.actions) {
1359
- if (action.kind === "blocked" && action.reason) {
1360
- alerts.push({ level: "blocked", message: action.reason });
1361
- }
1362
- }
1363
- for (const leaf of selectedSummary.leafs) {
1364
- if (!selectedDraft.selectedLeafIds.includes(leaf.id)) {
1365
- continue;
1366
- }
1367
- for (const warning of leaf.metadataWarnings) {
1368
- alerts.push({ level: "warning", message: warning });
1369
- }
1370
- for (const warning of projectionWarningsByLeafId[leaf.id] ?? []) {
1371
- alerts.push({ level: "warning", message: warning });
1372
- }
1373
- }
1374
- if ((selectedSummary.lock?.invalidLeafs.length ?? 0) > 0) {
1375
- alerts.push({
1376
- level: "warning",
1377
- message: `${selectedSummary.lock?.invalidLeafs.length ?? 0} invalid skill entries skipped`,
1378
- });
1379
- }
1380
- return alerts;
1381
- }
1382
- export function buildDetailMetadataRows({ detailWidth, group, summary, }) {
1383
- const sourceSummary = group.kind === "clawhub"
1384
- ? `Sources: ${group.summaries.length} clawhub source${group.summaries.length === 1 ? "" : "s"}`
1385
- : `Source: ${summary.source.locator}`;
1386
- const rows = [
1387
- {
1388
- key: "__title__",
1389
- text: group.title,
1390
- active: false,
1391
- bold: true,
1392
- color: undefined,
1393
- },
1394
- {
1395
- key: "__source__",
1396
- text: fitPaneLine(sourceSummary, getPaneInnerWidth(detailWidth) - 2),
1397
- active: false,
1398
- color: "gray",
1399
- },
1400
- ];
1401
- if (summary.lock?.checkoutPath) {
1402
- rows.push({
1403
- key: "__local_path__",
1404
- text: fitPaneLine(`Local Path: ${summary.lock.checkoutPath}`, getPaneInnerWidth(detailWidth) - 2),
1405
- active: false,
1406
- color: "gray",
1407
- });
1408
- }
1409
- if (group.kind === "clawhub") {
1410
- rows.push({
1411
- key: "__focused_source__",
1412
- text: fitPaneLine(`Focused Source: ${formatGroupLabel(summary.source)}`, getPaneInnerWidth(detailWidth) - 2),
1413
- active: false,
1414
- color: "gray",
1415
- });
1416
- }
1417
- return rows;
1418
- }
1419
- export function buildActionRows({ actionCursor, canRunActions, deleteState, focus, isSelectedDelete, showDeleteAction, updateState, }) {
1420
- const updateText = updateState.phase === "updating"
1421
- ? "Update · UPDATING..."
1422
- : updateState.phase === "failed"
1423
- ? "Update · FAILED"
1424
- : "Update";
1425
- const rows = [
1426
- {
1427
- key: "__actions_separator__",
1428
- text: "────────────────────────",
1429
- active: false,
1430
- color: "gray",
1431
- },
1432
- {
1433
- key: "__action_update__",
1434
- text: `[${updateText}]`,
1435
- active: focus === "detail.actions" && actionCursor === 0,
1436
- color: canRunActions || updateState.phase !== "idle" ? undefined : "gray",
1437
- bold: true,
1438
- },
1439
- ];
1440
- if (showDeleteAction) {
1441
- const deleteText = isSelectedDelete && deleteState.phase === "deleting"
1442
- ? "Delete · DELETING..."
1443
- : isSelectedDelete && deleteState.phase === "failed"
1444
- ? "Delete · FAILED"
1445
- : "Delete";
1446
- rows.push({
1447
- key: "__action_delete__",
1448
- text: `[${deleteText}]`,
1449
- active: focus === "detail.actions" && actionCursor === 1,
1450
- color: isSelectedDelete && deleteState.phase === "failed"
1451
- ? "red"
1452
- : canRunActions || (isSelectedDelete && deleteState.phase !== "idle")
1453
- ? "red"
1454
- : "gray",
1455
- bold: true,
1456
- });
1457
- }
1458
- return rows;
1459
- }
1460
- export function buildCommandBar(focus) {
1461
- if (focus === "groups") {
1462
- return "[Tab/→] Edit";
1463
- }
1464
- if (focus === "detail.actions") {
1465
- return "[Enter] Action";
1466
- }
1467
- return "[Space] Toggle";
1468
- }
1469
- export function buildFooterHints(focus, canDelete) {
1470
- if (focus === "groups") {
1471
- return canDelete
1472
- ? "[↑↓] Move [Tab/→] Switch pane [u] Update [d] Delete [q] Exit"
1473
- : "[↑↓] Move [Tab/→] Switch pane [u] Update [q] Exit";
1474
- }
1475
- if (focus === "detail.actions") {
1476
- return canDelete
1477
- ? "[↑↓] Move [Enter] Action [Tab/←/Esc] Back [u] Update [d] Delete"
1478
- : "[↑↓] Move [Enter] Action [Tab/←/Esc] Back [u] Update";
1479
- }
1480
- return canDelete
1481
- ? "[↑↓] Move [Space] Toggle [Tab/←/Esc] Back [u] Update [d] Delete"
1482
- : "[↑↓] Move [Space] Toggle [Tab/←/Esc] Back [u] Update";
1483
- }
1484
- function RowText({ row, width }) {
1485
- const color = row.active ? row.activeColor ?? "cyan" : row.color;
1486
- const prefix = row.active ? "❯ " : " ";
1487
- const contentWidth = Math.max(1, getPaneInnerWidth(width) - prefix.length);
1488
- const content = fitPaneLine(row.text, contentWidth);
1489
- return (_jsxs(Text, { wrap: "truncate-end", ...(color ? { color } : {}), ...(row.bold ? { bold: true } : {}), children: [prefix, content] }));
1490
- }
1491
- function Pane({ title, active, width, children, height, footer, gapAfter = false, }) {
1492
- return (_jsxs(Box, { flexDirection: "column", width: width, height: height, marginRight: gapAfter ? 1 : 0, paddingX: 1, borderStyle: "round", borderColor: active ? "cyan" : "gray", children: [_jsx(Text, { bold: true, wrap: "truncate-end", children: fitPaneLine(title, getPaneInnerWidth(width)) }), _jsx(Text, { children: " " }), children, _jsx(Text, { dimColor: true, wrap: "truncate-middle", children: fitPaneLine(footer, getPaneInnerWidth(width)) })] }));
1493
- }
1494
- function renderPaneRows(rows, bodyRowCount, paneWidth, tailRows = []) {
1495
- const items = rows.map((row) => (_jsx(RowText, { row: row, width: paneWidth }, row.key)));
1496
- const blankCount = Math.max(0, bodyRowCount - rows.length - tailRows.length);
1497
- for (let index = 0; index < blankCount; index += 1) {
1498
- items.push(_jsx(Text, { children: " " }, `__blank__:${index}`));
1499
- }
1500
- for (const row of tailRows) {
1501
- items.push(_jsx(RowText, { row: row, width: paneWidth }, row.key));
1502
- }
1503
- return items;
1504
- }
1505
- function fitPaneLine(text, width) {
1506
- if (text.length <= width) {
1507
- return text.padEnd(width, " ");
1508
- }
1509
- if (width <= 1) {
1510
- return "…";
1511
- }
1512
- return `${text.slice(0, width - 1)}…`;
1513
- }
1514
- function getPaneInnerWidth(width) {
1515
- return Math.max(1, width - 4);
1516
- }
1517
- export function selectionMarker(state) {
1518
- if (state === "full") {
1519
- return "●";
1520
- }
1521
- if (state === "partial") {
1522
- return "◐";
1523
- }
1524
- return "○";
1525
- }
1526
- function getExactDuplicateKey(linkName, name, description) {
1527
- return `${linkName}\n${name}\n${description}`;
1528
- }
1529
- function getWindowedRows(items, cursorIndex, visibleCount) {
1530
- const safeVisibleCount = Math.max(1, visibleCount);
1531
- const maxStart = Math.max(0, items.length - safeVisibleCount);
1532
- const start = Math.min(Math.max(0, cursorIndex - Math.floor(safeVisibleCount / 2)), maxStart);
1533
- const end = Math.min(items.length, start + safeVisibleCount);
1534
- return {
1535
- rows: items.slice(start, end),
1536
- start,
1537
- end,
1538
- };
1539
- }
1540
- function pruneSourceMap(sourceMap, allowedIds) {
1541
- return Object.fromEntries(Object.entries(sourceMap).filter(([sourceId]) => allowedIds.has(sourceId)));
1542
- }
1543
- function removeSourceFromMap(sourceMap, sourceId) {
1544
- const next = { ...sourceMap };
1545
- delete next[sourceId];
1546
- return next;
1547
- }
1548
- function firstErrorMessage(result) {
1549
- return result.errors[0]?.message ?? "operation failed";
1550
- }
1551
- //# sourceMappingURL=config-app.js.map