@madebywild/agent-harness-framework 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/cli/adapters/autocomplete-select.d.ts +16 -0
- package/dist/cli/adapters/autocomplete-select.d.ts.map +1 -1
- package/dist/cli/adapters/autocomplete-select.js +83 -0
- package/dist/cli/adapters/autocomplete-select.js.map +1 -1
- package/dist/cli/adapters/commander.d.ts.map +1 -1
- package/dist/cli/adapters/commander.js +38 -0
- package/dist/cli/adapters/commander.js.map +1 -1
- package/dist/cli/adapters/interactive.d.ts +23 -1
- package/dist/cli/adapters/interactive.d.ts.map +1 -1
- package/dist/cli/adapters/interactive.js +906 -48
- package/dist/cli/adapters/interactive.js.map +1 -1
- package/dist/cli/command-registry.d.ts.map +1 -1
- package/dist/cli/command-registry.js +59 -0
- package/dist/cli/command-registry.js.map +1 -1
- package/dist/cli/contracts.d.ts +14 -3
- package/dist/cli/contracts.d.ts.map +1 -1
- package/dist/cli/handlers/skills.d.ts +13 -0
- package/dist/cli/handlers/skills.d.ts.map +1 -0
- package/dist/cli/handlers/skills.js +43 -0
- package/dist/cli/handlers/skills.js.map +1 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +2 -6
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/renderers/text.d.ts.map +1 -1
- package/dist/cli/renderers/text.js +41 -0
- package/dist/cli/renderers/text.js.map +1 -1
- package/dist/engine/entities.d.ts +1 -0
- package/dist/engine/entities.d.ts.map +1 -1
- package/dist/engine/entities.js +9 -2
- package/dist/engine/entities.js.map +1 -1
- package/dist/engine/utils.d.ts.map +1 -1
- package/dist/engine/utils.js +3 -0
- package/dist/engine/utils.js.map +1 -1
- package/dist/engine.d.ts +15 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +126 -2
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +6 -0
- package/dist/paths.js.map +1 -1
- package/dist/skills-integration.d.ts +55 -0
- package/dist/skills-integration.d.ts.map +1 -0
- package/dist/skills-integration.js +524 -0
- package/dist/skills-integration.js.map +1 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -3
|
@@ -3,12 +3,42 @@ import { Spinner, TextInput } from "@inkjs/ui";
|
|
|
3
3
|
import { providerIdSchema } from "@madebywild/agent-harness-manifest";
|
|
4
4
|
import { Box, render, Static, Text, useApp, useInput } from "ink";
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { resolveHarnessPaths } from "../../paths.js";
|
|
6
7
|
import { listBuiltinPresets, summarizePreset } from "../../presets.js";
|
|
7
8
|
import { CLI_ENTITY_TYPES } from "../../types.js";
|
|
9
|
+
import { exists } from "../../utils.js";
|
|
10
|
+
import { runDoctor } from "../../versioning/doctor.js";
|
|
8
11
|
import { getCommandDefinition } from "../command-registry.js";
|
|
9
12
|
import { renderTextOutput } from "../renderers/text.js";
|
|
10
|
-
import { AutocompleteSelect } from "./autocomplete-select.js";
|
|
13
|
+
import { AutocompleteMultiSelect, AutocompleteSelect } from "./autocomplete-select.js";
|
|
11
14
|
import { ToggleConfirm } from "./toggle-confirm.js";
|
|
15
|
+
export async function detectWorkspaceStatus(cwd) {
|
|
16
|
+
const paths = resolveHarnessPaths(cwd);
|
|
17
|
+
const [dirExists, manifestExists] = await Promise.all([exists(paths.agentsDir), exists(paths.manifestFile)]);
|
|
18
|
+
if (!dirExists || !manifestExists) {
|
|
19
|
+
return { state: "missing" };
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const doctor = await runDoctor(paths);
|
|
23
|
+
if (!doctor.healthy) {
|
|
24
|
+
return { state: "unhealthy", diagnostics: doctor.diagnostics };
|
|
25
|
+
}
|
|
26
|
+
return { state: "healthy" };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
return {
|
|
31
|
+
state: "unhealthy",
|
|
32
|
+
diagnostics: [
|
|
33
|
+
{
|
|
34
|
+
code: "INTERACTIVE_WORKSPACE_STATUS_CHECK_FAILED",
|
|
35
|
+
severity: "error",
|
|
36
|
+
message: `Failed to determine workspace health: ${message}`,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
// ---------------------------------------------------------------------------
|
|
13
43
|
// Command list shown in the main selector
|
|
14
44
|
// ---------------------------------------------------------------------------
|
|
@@ -25,6 +55,7 @@ const INTERACTIVE_COMMAND_IDS = [
|
|
|
25
55
|
"preset.list",
|
|
26
56
|
"preset.describe",
|
|
27
57
|
"preset.apply",
|
|
58
|
+
"skill.import",
|
|
28
59
|
"add.prompt",
|
|
29
60
|
"add.skill",
|
|
30
61
|
"add.mcp",
|
|
@@ -50,33 +81,66 @@ const COMMAND_OPTIONS = [
|
|
|
50
81
|
// Build the prompt list for each command
|
|
51
82
|
// ---------------------------------------------------------------------------
|
|
52
83
|
function buildPromptsForCommand(commandId, presets) {
|
|
53
|
-
const providers = providerIdSchema.options.map((p) => ({
|
|
84
|
+
const providers = providerIdSchema.options.map((p) => ({
|
|
85
|
+
label: p,
|
|
86
|
+
value: p,
|
|
87
|
+
}));
|
|
54
88
|
const entityTypes = CLI_ENTITY_TYPES.map((t) => ({ label: t, value: t }));
|
|
55
89
|
switch (commandId) {
|
|
56
90
|
case "init":
|
|
57
91
|
return [
|
|
58
|
-
{
|
|
92
|
+
{
|
|
93
|
+
id: "force",
|
|
94
|
+
type: "confirm",
|
|
95
|
+
message: "Overwrite existing .harness workspace if present?",
|
|
96
|
+
initial: false,
|
|
97
|
+
},
|
|
59
98
|
{
|
|
60
99
|
id: "preset",
|
|
61
100
|
type: "select",
|
|
62
101
|
message: "Select a preset to apply during init",
|
|
63
102
|
options: [
|
|
64
103
|
{ value: "", label: "Skip preset" },
|
|
65
|
-
...presets.map((p) => ({
|
|
104
|
+
...presets.map((p) => ({
|
|
105
|
+
value: p.id,
|
|
106
|
+
label: `${p.name} (${p.id})`,
|
|
107
|
+
})),
|
|
66
108
|
],
|
|
67
109
|
},
|
|
68
110
|
// delegate prompt inserted dynamically when preset === "delegate"
|
|
69
111
|
];
|
|
70
112
|
case "provider.enable":
|
|
71
113
|
case "provider.disable":
|
|
72
|
-
return [
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
id: "provider",
|
|
117
|
+
type: "select",
|
|
118
|
+
message: "Select provider",
|
|
119
|
+
options: providers,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
73
122
|
case "registry.add":
|
|
74
123
|
return [
|
|
75
124
|
{ id: "name", type: "text", message: "Registry name", required: true },
|
|
76
125
|
{ id: "gitUrl", type: "text", message: "Git URL", required: true },
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
126
|
+
{
|
|
127
|
+
id: "ref",
|
|
128
|
+
type: "text",
|
|
129
|
+
message: "Git ref (default: main)",
|
|
130
|
+
required: false,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "root",
|
|
134
|
+
type: "text",
|
|
135
|
+
message: "Registry root path",
|
|
136
|
+
required: false,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "tokenEnv",
|
|
140
|
+
type: "text",
|
|
141
|
+
message: "Token env var",
|
|
142
|
+
required: false,
|
|
143
|
+
},
|
|
80
144
|
];
|
|
81
145
|
case "registry.remove":
|
|
82
146
|
case "registry.default.set":
|
|
@@ -89,58 +153,200 @@ function buildPromptsForCommand(commandId, presets) {
|
|
|
89
153
|
message: "Entity type filter",
|
|
90
154
|
options: [{ value: "", label: "All entity types" }, ...entityTypes],
|
|
91
155
|
},
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
156
|
+
{
|
|
157
|
+
id: "id",
|
|
158
|
+
type: "text",
|
|
159
|
+
message: "Entity id filter",
|
|
160
|
+
required: false,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "registry",
|
|
164
|
+
type: "text",
|
|
165
|
+
message: "Registry filter",
|
|
166
|
+
required: false,
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "force",
|
|
170
|
+
type: "confirm",
|
|
171
|
+
message: "Overwrite locally modified imported sources?",
|
|
172
|
+
initial: false,
|
|
173
|
+
},
|
|
95
174
|
];
|
|
96
175
|
case "preset.list":
|
|
97
|
-
return [
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
id: "registry",
|
|
179
|
+
type: "text",
|
|
180
|
+
message: "Registry id",
|
|
181
|
+
required: false,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
98
184
|
case "preset.describe":
|
|
99
185
|
case "preset.apply":
|
|
100
186
|
return [
|
|
101
187
|
{ id: "presetId", type: "text", message: "Preset id", required: true },
|
|
102
|
-
{
|
|
188
|
+
{
|
|
189
|
+
id: "registry",
|
|
190
|
+
type: "text",
|
|
191
|
+
message: "Registry id",
|
|
192
|
+
required: false,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
case "skill.find":
|
|
196
|
+
return [{ id: "query", type: "text", message: "Search query", required: true }];
|
|
197
|
+
case "skill.import":
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
id: "source",
|
|
201
|
+
type: "text",
|
|
202
|
+
message: "Source (owner/repo, URL, or local path)",
|
|
203
|
+
required: true,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "upstreamSkill",
|
|
207
|
+
type: "text",
|
|
208
|
+
message: "Upstream skill id",
|
|
209
|
+
required: true,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
id: "as",
|
|
213
|
+
type: "text",
|
|
214
|
+
message: "Target harness skill id",
|
|
215
|
+
required: false,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: "replace",
|
|
219
|
+
type: "confirm",
|
|
220
|
+
message: "Replace existing skill if it already exists?",
|
|
221
|
+
initial: false,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "allowUnsafe",
|
|
225
|
+
type: "confirm",
|
|
226
|
+
message: "Allow non-pass audited skills?",
|
|
227
|
+
initial: false,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "allowUnaudited",
|
|
231
|
+
type: "confirm",
|
|
232
|
+
message: "Allow unaudited sources?",
|
|
233
|
+
initial: false,
|
|
234
|
+
},
|
|
103
235
|
];
|
|
104
236
|
case "add.prompt":
|
|
105
|
-
return [
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
id: "registry",
|
|
240
|
+
type: "text",
|
|
241
|
+
message: "Registry id",
|
|
242
|
+
required: false,
|
|
243
|
+
},
|
|
244
|
+
];
|
|
106
245
|
case "add.skill":
|
|
107
246
|
return [
|
|
108
247
|
{ id: "skillId", type: "text", message: "Skill id", required: true },
|
|
109
|
-
{
|
|
248
|
+
{
|
|
249
|
+
id: "registry",
|
|
250
|
+
type: "text",
|
|
251
|
+
message: "Registry id",
|
|
252
|
+
required: false,
|
|
253
|
+
},
|
|
110
254
|
];
|
|
111
255
|
case "add.mcp":
|
|
112
256
|
return [
|
|
113
|
-
{
|
|
114
|
-
|
|
257
|
+
{
|
|
258
|
+
id: "configId",
|
|
259
|
+
type: "text",
|
|
260
|
+
message: "MCP config id",
|
|
261
|
+
required: true,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: "registry",
|
|
265
|
+
type: "text",
|
|
266
|
+
message: "Registry id",
|
|
267
|
+
required: false,
|
|
268
|
+
},
|
|
115
269
|
];
|
|
116
270
|
case "add.subagent":
|
|
117
271
|
return [
|
|
118
|
-
{
|
|
119
|
-
|
|
272
|
+
{
|
|
273
|
+
id: "subagentId",
|
|
274
|
+
type: "text",
|
|
275
|
+
message: "Subagent id",
|
|
276
|
+
required: true,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "registry",
|
|
280
|
+
type: "text",
|
|
281
|
+
message: "Registry id",
|
|
282
|
+
required: false,
|
|
283
|
+
},
|
|
120
284
|
];
|
|
121
285
|
case "add.hook":
|
|
122
286
|
return [
|
|
123
287
|
{ id: "hookId", type: "text", message: "Hook id", required: true },
|
|
124
|
-
{
|
|
288
|
+
{
|
|
289
|
+
id: "registry",
|
|
290
|
+
type: "text",
|
|
291
|
+
message: "Registry id",
|
|
292
|
+
required: false,
|
|
293
|
+
},
|
|
125
294
|
];
|
|
126
295
|
case "add.settings":
|
|
127
296
|
return [
|
|
128
|
-
{
|
|
129
|
-
|
|
297
|
+
{
|
|
298
|
+
id: "provider",
|
|
299
|
+
type: "select",
|
|
300
|
+
message: "Provider",
|
|
301
|
+
options: providers,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "registry",
|
|
305
|
+
type: "text",
|
|
306
|
+
message: "Registry id",
|
|
307
|
+
required: false,
|
|
308
|
+
},
|
|
130
309
|
];
|
|
131
310
|
case "add.command":
|
|
132
311
|
return [
|
|
133
|
-
{
|
|
134
|
-
|
|
312
|
+
{
|
|
313
|
+
id: "commandId",
|
|
314
|
+
type: "text",
|
|
315
|
+
message: "Command id",
|
|
316
|
+
required: true,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "registry",
|
|
320
|
+
type: "text",
|
|
321
|
+
message: "Registry id",
|
|
322
|
+
required: false,
|
|
323
|
+
},
|
|
135
324
|
];
|
|
136
325
|
case "remove":
|
|
137
326
|
return [
|
|
138
|
-
{
|
|
327
|
+
{
|
|
328
|
+
id: "entityType",
|
|
329
|
+
type: "select",
|
|
330
|
+
message: "Entity type",
|
|
331
|
+
options: entityTypes,
|
|
332
|
+
},
|
|
139
333
|
{ id: "id", type: "text", message: "Entity id", required: true },
|
|
140
|
-
{
|
|
334
|
+
{
|
|
335
|
+
id: "deleteSource",
|
|
336
|
+
type: "confirm",
|
|
337
|
+
message: "Delete source files too?",
|
|
338
|
+
initial: true,
|
|
339
|
+
},
|
|
141
340
|
];
|
|
142
341
|
case "migrate":
|
|
143
|
-
return [
|
|
342
|
+
return [
|
|
343
|
+
{
|
|
344
|
+
id: "dryRun",
|
|
345
|
+
type: "confirm",
|
|
346
|
+
message: "Run as dry-run only?",
|
|
347
|
+
initial: false,
|
|
348
|
+
},
|
|
349
|
+
];
|
|
144
350
|
default:
|
|
145
351
|
return [];
|
|
146
352
|
}
|
|
@@ -165,7 +371,11 @@ function buildCommandInput(commandId, values) {
|
|
|
165
371
|
case "init":
|
|
166
372
|
return {
|
|
167
373
|
command: commandId,
|
|
168
|
-
options: {
|
|
374
|
+
options: {
|
|
375
|
+
force: bool("force"),
|
|
376
|
+
preset: str("preset"),
|
|
377
|
+
delegate: str("delegate"),
|
|
378
|
+
},
|
|
169
379
|
};
|
|
170
380
|
case "provider.enable":
|
|
171
381
|
case "provider.disable":
|
|
@@ -174,7 +384,12 @@ function buildCommandInput(commandId, values) {
|
|
|
174
384
|
return {
|
|
175
385
|
command: commandId,
|
|
176
386
|
args: { name: str("name") },
|
|
177
|
-
options: {
|
|
387
|
+
options: {
|
|
388
|
+
gitUrl: str("gitUrl"),
|
|
389
|
+
ref: str("ref"),
|
|
390
|
+
root: str("root"),
|
|
391
|
+
tokenEnv: str("tokenEnv"),
|
|
392
|
+
},
|
|
178
393
|
};
|
|
179
394
|
case "registry.remove":
|
|
180
395
|
case "registry.default.set":
|
|
@@ -189,21 +404,66 @@ function buildCommandInput(commandId, values) {
|
|
|
189
404
|
return { command: commandId, options: { registry: str("registry") } };
|
|
190
405
|
case "preset.describe":
|
|
191
406
|
case "preset.apply":
|
|
192
|
-
return {
|
|
407
|
+
return {
|
|
408
|
+
command: commandId,
|
|
409
|
+
args: { presetId: str("presetId") },
|
|
410
|
+
options: { registry: str("registry") },
|
|
411
|
+
};
|
|
412
|
+
case "skill.find":
|
|
413
|
+
return {
|
|
414
|
+
command: commandId,
|
|
415
|
+
args: { query: str("query") },
|
|
416
|
+
};
|
|
417
|
+
case "skill.import":
|
|
418
|
+
return {
|
|
419
|
+
command: commandId,
|
|
420
|
+
args: { source: str("source") },
|
|
421
|
+
options: {
|
|
422
|
+
skill: str("upstreamSkill"),
|
|
423
|
+
as: str("as"),
|
|
424
|
+
replace: bool("replace"),
|
|
425
|
+
allowUnsafe: bool("allowUnsafe"),
|
|
426
|
+
allowUnaudited: bool("allowUnaudited"),
|
|
427
|
+
},
|
|
428
|
+
};
|
|
193
429
|
case "add.prompt":
|
|
194
430
|
return { command: commandId, options: { registry: str("registry") } };
|
|
195
431
|
case "add.skill":
|
|
196
|
-
return {
|
|
432
|
+
return {
|
|
433
|
+
command: commandId,
|
|
434
|
+
args: { skillId: str("skillId") },
|
|
435
|
+
options: { registry: str("registry") },
|
|
436
|
+
};
|
|
197
437
|
case "add.mcp":
|
|
198
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
command: commandId,
|
|
440
|
+
args: { configId: str("configId") },
|
|
441
|
+
options: { registry: str("registry") },
|
|
442
|
+
};
|
|
199
443
|
case "add.subagent":
|
|
200
|
-
return {
|
|
444
|
+
return {
|
|
445
|
+
command: commandId,
|
|
446
|
+
args: { subagentId: str("subagentId") },
|
|
447
|
+
options: { registry: str("registry") },
|
|
448
|
+
};
|
|
201
449
|
case "add.hook":
|
|
202
|
-
return {
|
|
450
|
+
return {
|
|
451
|
+
command: commandId,
|
|
452
|
+
args: { hookId: str("hookId") },
|
|
453
|
+
options: { registry: str("registry") },
|
|
454
|
+
};
|
|
203
455
|
case "add.settings":
|
|
204
|
-
return {
|
|
456
|
+
return {
|
|
457
|
+
command: commandId,
|
|
458
|
+
args: { provider: str("provider") },
|
|
459
|
+
options: { registry: str("registry") },
|
|
460
|
+
};
|
|
205
461
|
case "add.command":
|
|
206
|
-
return {
|
|
462
|
+
return {
|
|
463
|
+
command: commandId,
|
|
464
|
+
args: { commandId: str("commandId") },
|
|
465
|
+
options: { registry: str("registry") },
|
|
466
|
+
};
|
|
207
467
|
case "remove":
|
|
208
468
|
return {
|
|
209
469
|
command: commandId,
|
|
@@ -211,14 +471,24 @@ function buildCommandInput(commandId, values) {
|
|
|
211
471
|
options: { deleteSource: bool("deleteSource", true) },
|
|
212
472
|
};
|
|
213
473
|
case "migrate":
|
|
214
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
command: commandId,
|
|
476
|
+
options: { to: "latest", dryRun: bool("dryRun") },
|
|
477
|
+
};
|
|
215
478
|
default:
|
|
216
479
|
return { command: commandId };
|
|
217
480
|
}
|
|
218
481
|
}
|
|
219
|
-
function
|
|
482
|
+
function initialStepFromStatus(status) {
|
|
483
|
+
if (!status || status.state === "healthy")
|
|
484
|
+
return { type: "select-command" };
|
|
485
|
+
if (status.state === "missing")
|
|
486
|
+
return { type: "onboarding" };
|
|
487
|
+
return { type: "workspace-warning", diagnostics: status.diagnostics };
|
|
488
|
+
}
|
|
489
|
+
export function App({ api, presets, workspaceStatus, onExit }) {
|
|
220
490
|
const { exit } = useApp();
|
|
221
|
-
const [step, setStep] = useState(
|
|
491
|
+
const [step, setStep] = useState(() => initialStepFromStatus(workspaceStatus));
|
|
222
492
|
const [pastLines, setPastLines] = useState([{ id: 0, text: "Harness interactive mode" }]);
|
|
223
493
|
const nextLineId = useRef(1);
|
|
224
494
|
const [exitCode, setExitCode] = useState(0);
|
|
@@ -246,6 +516,15 @@ function App({ api, presets, onExit }) {
|
|
|
246
516
|
}
|
|
247
517
|
}, [step, exitCode, exit, onExit]);
|
|
248
518
|
const renderCurrentStep = () => {
|
|
519
|
+
if (step.type === "onboarding") {
|
|
520
|
+
return (_jsx(OnboardingWizard, { api: api, presets: presets, onComplete: () => {
|
|
521
|
+
addPastLine("Onboarding complete.");
|
|
522
|
+
setStep({ type: "select-command" });
|
|
523
|
+
} }));
|
|
524
|
+
}
|
|
525
|
+
if (step.type === "workspace-warning") {
|
|
526
|
+
return (_jsx(WorkspaceWarningStep, { diagnostics: step.diagnostics, api: api, onDismiss: () => setStep({ type: "select-command" }) }));
|
|
527
|
+
}
|
|
249
528
|
if (step.type === "select-command") {
|
|
250
529
|
return (_jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Command", options: COMMAND_OPTIONS, onChange: (value) => {
|
|
251
530
|
if (value === "exit") {
|
|
@@ -254,9 +533,30 @@ function App({ api, presets, onExit }) {
|
|
|
254
533
|
return;
|
|
255
534
|
}
|
|
256
535
|
const commandId = value;
|
|
536
|
+
if (commandId === "skill.import") {
|
|
537
|
+
setStep({ type: "skills-import-workflow" });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
257
540
|
const prompts = buildPromptsForCommand(commandId, presets);
|
|
258
|
-
setStep({
|
|
259
|
-
|
|
541
|
+
setStep({
|
|
542
|
+
type: "prompt-input",
|
|
543
|
+
commandId,
|
|
544
|
+
collector: { prompts, values: {}, index: 0 },
|
|
545
|
+
});
|
|
546
|
+
} }, "select-command") }));
|
|
547
|
+
}
|
|
548
|
+
if (step.type === "skills-import-workflow") {
|
|
549
|
+
return (_jsx(SkillsImportWorkflow, { api: api, onCancel: () => {
|
|
550
|
+
addPastLine("Cancelled command input.");
|
|
551
|
+
setStep({ type: "select-command" });
|
|
552
|
+
}, onComplete: (lines, isError) => {
|
|
553
|
+
setStep({
|
|
554
|
+
type: "show-output",
|
|
555
|
+
label: "Search and import third-party skills",
|
|
556
|
+
lines,
|
|
557
|
+
isError,
|
|
558
|
+
});
|
|
559
|
+
}, onDismiss: () => setStep({ type: "select-command" }) }));
|
|
260
560
|
}
|
|
261
561
|
if (step.type === "prompt-input") {
|
|
262
562
|
const { commandId, collector } = step;
|
|
@@ -273,7 +573,10 @@ function App({ api, presets, onExit }) {
|
|
|
273
573
|
id: "delegate",
|
|
274
574
|
type: "select",
|
|
275
575
|
message: "Select the provider CLI to delegate prompt authoring to",
|
|
276
|
-
options: providerIdSchema.options.map((p) => ({
|
|
576
|
+
options: providerIdSchema.options.map((p) => ({
|
|
577
|
+
label: p,
|
|
578
|
+
value: p,
|
|
579
|
+
})),
|
|
277
580
|
};
|
|
278
581
|
newPrompts = [
|
|
279
582
|
...collector.prompts.slice(0, collector.index + 1),
|
|
@@ -284,7 +587,11 @@ function App({ api, presets, onExit }) {
|
|
|
284
587
|
setStep({
|
|
285
588
|
type: "prompt-input",
|
|
286
589
|
commandId,
|
|
287
|
-
collector: {
|
|
590
|
+
collector: {
|
|
591
|
+
prompts: newPrompts,
|
|
592
|
+
values: newValues,
|
|
593
|
+
index: collector.index + 1,
|
|
594
|
+
},
|
|
288
595
|
});
|
|
289
596
|
};
|
|
290
597
|
const cancelPrompt = () => {
|
|
@@ -298,7 +605,7 @@ function App({ api, presets, onExit }) {
|
|
|
298
605
|
return (_jsx(ToggleConfirm, { message: prompt.message, defaultValue: prompt.initial, onSubmit: advanceWith, onEscape: cancelPrompt }));
|
|
299
606
|
}
|
|
300
607
|
if (prompt.type === "select") {
|
|
301
|
-
return (_jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: prompt.message, options: prompt.options, onChange: (value) => advanceWith(value), onCancel: cancelPrompt }) }));
|
|
608
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: prompt.message, options: prompt.options, onChange: (value) => advanceWith(value), onCancel: cancelPrompt }, `${commandId}-${collector.index}`) }));
|
|
302
609
|
}
|
|
303
610
|
}
|
|
304
611
|
if (step.type === "confirm-run") {
|
|
@@ -328,7 +635,12 @@ function App({ api, presets, onExit }) {
|
|
|
328
635
|
setStep({ type: "show-output", label, lines, isError: code !== 0 });
|
|
329
636
|
}, onError: (message) => {
|
|
330
637
|
setExitCode(1);
|
|
331
|
-
setStep({
|
|
638
|
+
setStep({
|
|
639
|
+
type: "show-output",
|
|
640
|
+
label,
|
|
641
|
+
lines: [`Error: ${message}`],
|
|
642
|
+
isError: true,
|
|
643
|
+
});
|
|
332
644
|
} }));
|
|
333
645
|
}
|
|
334
646
|
return null;
|
|
@@ -375,13 +687,559 @@ function RunningStep({ label, input, api, onDone, onError }) {
|
|
|
375
687
|
}, [api, input]);
|
|
376
688
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: `Running ${label}...` }) }));
|
|
377
689
|
}
|
|
690
|
+
const DEFAULT_SKILL_IMPORT_OPTIONS = {
|
|
691
|
+
replace: false,
|
|
692
|
+
allowUnsafe: false,
|
|
693
|
+
allowUnaudited: false,
|
|
694
|
+
};
|
|
695
|
+
const SKILL_IMPORT_OPTION_PROMPTS = [
|
|
696
|
+
{ key: "replace", message: "Replace existing local skills if they already exist?" },
|
|
697
|
+
{ key: "allowUnsafe", message: "Allow non-pass audited skills?" },
|
|
698
|
+
{ key: "allowUnaudited", message: "Allow unaudited sources?" },
|
|
699
|
+
];
|
|
700
|
+
function formatDiagnosticLine(diagnostic) {
|
|
701
|
+
const p = diagnostic.path ? ` (${diagnostic.path})` : "";
|
|
702
|
+
return `[${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}${p}`;
|
|
703
|
+
}
|
|
704
|
+
function summarizeAudit(output) {
|
|
705
|
+
if (output.family !== "skills" || output.data.operation !== "import")
|
|
706
|
+
return "";
|
|
707
|
+
const audit = output.data.result.audit;
|
|
708
|
+
if (!audit.audited)
|
|
709
|
+
return "";
|
|
710
|
+
return audit.providers.map((p) => `${p.provider}: ${p.raw}`).join(" · ");
|
|
711
|
+
}
|
|
712
|
+
function extractUserError(output) {
|
|
713
|
+
const errors = output.diagnostics.filter((d) => d.severity === "error");
|
|
714
|
+
if (errors.length === 0)
|
|
715
|
+
return undefined;
|
|
716
|
+
for (const e of errors) {
|
|
717
|
+
if (e.code === "SKILL_IMPORT_SUBPROCESS_FAILED")
|
|
718
|
+
return "Skill source not found or unavailable.";
|
|
719
|
+
if (e.code === "SKILL_IMPORT_AUDIT_BLOCKED")
|
|
720
|
+
return "Blocked by security audit. Use --allow-unsafe to override.";
|
|
721
|
+
if (e.code === "SKILL_IMPORT_AUDIT_UNAUDITED")
|
|
722
|
+
return "No audit report available. Use --allow-unaudited to override.";
|
|
723
|
+
if (e.code === "SKILL_IMPORT_COLLISION")
|
|
724
|
+
return "Skill already exists. Enable replace to overwrite.";
|
|
725
|
+
if (e.code?.startsWith("SKILL_IMPORT_PAYLOAD_"))
|
|
726
|
+
return "Skill payload validation failed.";
|
|
727
|
+
}
|
|
728
|
+
return errors[0]?.message;
|
|
729
|
+
}
|
|
730
|
+
function formatSkillResultLabel(result) {
|
|
731
|
+
return `${result.source}@${result.upstreamSkill}`;
|
|
732
|
+
}
|
|
733
|
+
function SkillItemLabel({ result, isFocused, isSelected, }) {
|
|
734
|
+
const nameColor = isSelected ? "green" : isFocused ? "cyan" : undefined;
|
|
735
|
+
const meta = [result.source, result.installs].filter(Boolean).join(" · ");
|
|
736
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: nameColor, children: result.upstreamSkill }), meta && _jsx(Text, { dimColor: true, children: ` ${meta}` })] }));
|
|
737
|
+
}
|
|
738
|
+
function readSkillFindState(output) {
|
|
739
|
+
if (output.family !== "skills")
|
|
740
|
+
return null;
|
|
741
|
+
if (output.data.operation !== "find")
|
|
742
|
+
return null;
|
|
743
|
+
return {
|
|
744
|
+
query: output.data.query,
|
|
745
|
+
results: output.data.results,
|
|
746
|
+
diagnostics: output.diagnostics,
|
|
747
|
+
rawText: output.data.rawText,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function buildSkillImportInput(skill, options) {
|
|
751
|
+
return {
|
|
752
|
+
command: "skill.import",
|
|
753
|
+
args: { source: skill.source },
|
|
754
|
+
options: {
|
|
755
|
+
skill: skill.upstreamSkill,
|
|
756
|
+
replace: options.replace,
|
|
757
|
+
allowUnsafe: options.allowUnsafe,
|
|
758
|
+
allowUnaudited: options.allowUnaudited,
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function SkillsImportWorkflow({ api, onCancel, onComplete, onDismiss }) {
|
|
763
|
+
const [step, setStep] = useState({ type: "query" });
|
|
764
|
+
const runningRef = useRef(false);
|
|
765
|
+
const onCompleteRef = useRef(onComplete);
|
|
766
|
+
onCompleteRef.current = onComplete;
|
|
767
|
+
useEffect(() => {
|
|
768
|
+
if (runningRef.current)
|
|
769
|
+
return;
|
|
770
|
+
if (step.type === "searching") {
|
|
771
|
+
runningRef.current = true;
|
|
772
|
+
api
|
|
773
|
+
.execute({ command: "skill.find", args: { query: step.query } })
|
|
774
|
+
.then((output) => {
|
|
775
|
+
const search = readSkillFindState(output);
|
|
776
|
+
if (!search) {
|
|
777
|
+
onCompleteRef.current(["Error: Unexpected output while searching third-party skills."], true);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (search.results.length === 0) {
|
|
781
|
+
const lines = [`No skills found for query '${search.query}'.`];
|
|
782
|
+
if (search.rawText.trim().length > 0) {
|
|
783
|
+
lines.push("", search.rawText.trim());
|
|
784
|
+
}
|
|
785
|
+
if (search.diagnostics.length > 0) {
|
|
786
|
+
lines.push("", ...search.diagnostics.map(formatDiagnosticLine));
|
|
787
|
+
}
|
|
788
|
+
onCompleteRef.current(lines, output.exitCode !== 0);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
setStep({ type: "select", search });
|
|
792
|
+
})
|
|
793
|
+
.catch((error) => {
|
|
794
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
795
|
+
onCompleteRef.current([`Error: ${message}`], true);
|
|
796
|
+
})
|
|
797
|
+
.finally(() => {
|
|
798
|
+
runningRef.current = false;
|
|
799
|
+
});
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (step.type === "running") {
|
|
803
|
+
runningRef.current = true;
|
|
804
|
+
(async () => {
|
|
805
|
+
const entries = [];
|
|
806
|
+
for (const skill of step.selected) {
|
|
807
|
+
const output = await api.execute(buildSkillImportInput(skill, step.options));
|
|
808
|
+
const fileCount = output.family === "skills" && output.data.operation === "import" ? output.data.result.fileCount : 0;
|
|
809
|
+
entries.push({
|
|
810
|
+
skill,
|
|
811
|
+
ok: output.ok,
|
|
812
|
+
fileCount,
|
|
813
|
+
auditSummary: summarizeAudit(output),
|
|
814
|
+
errorMessage: output.ok ? undefined : extractUserError(output),
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
setStep({ type: "results", entries });
|
|
818
|
+
})()
|
|
819
|
+
.catch((error) => {
|
|
820
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
821
|
+
onCompleteRef.current([`Error: ${message}`], true);
|
|
822
|
+
})
|
|
823
|
+
.finally(() => {
|
|
824
|
+
runningRef.current = false;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}, [api, step]);
|
|
828
|
+
if (step.type === "query") {
|
|
829
|
+
return (_jsx(TextPrompt, { message: "Search third-party skills", required: true, onSubmit: (value) => {
|
|
830
|
+
const query = value.trim();
|
|
831
|
+
if (query.length === 0)
|
|
832
|
+
return;
|
|
833
|
+
setStep({ type: "searching", query });
|
|
834
|
+
}, onCancel: onCancel }, "skill-import-query"));
|
|
835
|
+
}
|
|
836
|
+
if (step.type === "searching") {
|
|
837
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: `Searching skills for '${step.query}'...` }) }));
|
|
838
|
+
}
|
|
839
|
+
if (step.type === "select") {
|
|
840
|
+
const results = step.search.results;
|
|
841
|
+
const options = results.map((result, index) => ({
|
|
842
|
+
value: String(index),
|
|
843
|
+
label: formatSkillResultLabel(result),
|
|
844
|
+
}));
|
|
845
|
+
const renderSkillLabel = ({ option, isFocused, isSelected }) => {
|
|
846
|
+
const result = results[Number(option.value)];
|
|
847
|
+
if (!result)
|
|
848
|
+
return null;
|
|
849
|
+
return _jsx(SkillItemLabel, { result: result, isFocused: isFocused, isSelected: isSelected });
|
|
850
|
+
};
|
|
851
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: `Found ${results.length} skill(s) for '${step.search.query}'.` }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteMultiSelect, { label: "Filter skills", options: options, renderLabel: renderSkillLabel, onCancel: onCancel, onSubmit: (selectedValues) => {
|
|
852
|
+
const selected = selectedValues
|
|
853
|
+
.map((value) => Number.parseInt(value, 10))
|
|
854
|
+
.filter((index) => Number.isInteger(index))
|
|
855
|
+
.map((index) => step.search.results[index])
|
|
856
|
+
.filter((result) => result !== undefined);
|
|
857
|
+
if (selected.length === 0) {
|
|
858
|
+
onComplete(["No skills selected. Nothing imported."], false);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
setStep({
|
|
862
|
+
type: "options",
|
|
863
|
+
search: step.search,
|
|
864
|
+
selected,
|
|
865
|
+
options: { ...DEFAULT_SKILL_IMPORT_OPTIONS },
|
|
866
|
+
optionIndex: 0,
|
|
867
|
+
});
|
|
868
|
+
} }, `skill-import-select-${step.search.query}`) })] }));
|
|
869
|
+
}
|
|
870
|
+
if (step.type === "options") {
|
|
871
|
+
const prompt = SKILL_IMPORT_OPTION_PROMPTS[step.optionIndex];
|
|
872
|
+
if (!prompt) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
return (_jsx(ToggleConfirm, { message: prompt.message, defaultValue: step.options[prompt.key], onEscape: onCancel, onSubmit: (value) => {
|
|
876
|
+
const nextOptions = {
|
|
877
|
+
...step.options,
|
|
878
|
+
[prompt.key]: value,
|
|
879
|
+
};
|
|
880
|
+
const nextIndex = step.optionIndex + 1;
|
|
881
|
+
if (nextIndex < SKILL_IMPORT_OPTION_PROMPTS.length) {
|
|
882
|
+
setStep({
|
|
883
|
+
...step,
|
|
884
|
+
options: nextOptions,
|
|
885
|
+
optionIndex: nextIndex,
|
|
886
|
+
});
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
setStep({
|
|
890
|
+
type: "confirm",
|
|
891
|
+
search: step.search,
|
|
892
|
+
selected: step.selected,
|
|
893
|
+
options: nextOptions,
|
|
894
|
+
});
|
|
895
|
+
} }, `skill-import-option-${step.optionIndex}`));
|
|
896
|
+
}
|
|
897
|
+
if (step.type === "confirm") {
|
|
898
|
+
return (_jsx(ToggleConfirm, { message: `Import ${step.selected.length} selected skill(s) now?`, defaultValue: true, onEscape: onCancel, onSubmit: (confirmed) => {
|
|
899
|
+
if (!confirmed) {
|
|
900
|
+
onCancel();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
setStep({
|
|
904
|
+
type: "running",
|
|
905
|
+
search: step.search,
|
|
906
|
+
selected: step.selected,
|
|
907
|
+
options: step.options,
|
|
908
|
+
});
|
|
909
|
+
} }));
|
|
910
|
+
}
|
|
911
|
+
if (step.type === "running") {
|
|
912
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: `Importing ${step.selected.length} skill(s)...` }) }));
|
|
913
|
+
}
|
|
914
|
+
if (step.type === "results") {
|
|
915
|
+
return _jsx(SkillImportResults, { entries: step.entries, onDismiss: onDismiss });
|
|
916
|
+
}
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
function SkillImportResults({ entries, onDismiss }) {
|
|
920
|
+
useInput((_input, key) => {
|
|
921
|
+
if (key.return)
|
|
922
|
+
onDismiss();
|
|
923
|
+
});
|
|
924
|
+
const imported = entries.filter((e) => e.ok);
|
|
925
|
+
const failed = entries.filter((e) => !e.ok);
|
|
926
|
+
const allOk = failed.length === 0;
|
|
927
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: allOk ? "green" : "yellow", children: allOk ? `✓ Imported ${imported.length} skill(s)` : `${imported.length} imported, ${failed.length} failed` }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: entries.map((entry) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: entry.ok ? "green" : "red", children: entry.ok ? "✓" : "✗" }), _jsx(Text, { bold: true, children: entry.skill.upstreamSkill }), _jsx(Text, { dimColor: true, children: entry.skill.source })] }), entry.ok && entry.fileCount > 0 && _jsx(Text, { dimColor: true, children: ` ${entry.fileCount} file(s) added` }), entry.ok && entry.auditSummary && _jsx(Text, { dimColor: true, children: ` Audit: ${entry.auditSummary}` }), !entry.ok && entry.errorMessage && _jsx(Text, { color: "red", children: ` ${entry.errorMessage}` })] }, `${entry.skill.source}/${entry.skill.upstreamSkill}`))) }), _jsx(Text, { dimColor: true, children: "Press Enter to continue..." })] }));
|
|
928
|
+
}
|
|
929
|
+
// ---------------------------------------------------------------------------
|
|
930
|
+
// OnboardingWizard — full guided setup for new workspaces
|
|
931
|
+
// ---------------------------------------------------------------------------
|
|
932
|
+
const HARNESS_LOGO = ` __ __
|
|
933
|
+
/ // /__ ________ ___ ___ ___
|
|
934
|
+
/ _ / _ \`/ __/ _ \\/ -_|_-<(_-<
|
|
935
|
+
/_//_/\\_,_/_/ /_//_/\\__/___/___/`;
|
|
936
|
+
const HARNESS_TAGLINE = "Configure your AI coding agents from a single source of truth.";
|
|
937
|
+
function OnboardingWizard({ api, presets, onComplete }) {
|
|
938
|
+
const [subStep, setSubStep] = useState({
|
|
939
|
+
type: "welcome",
|
|
940
|
+
});
|
|
941
|
+
const [revealIndex, setRevealIndex] = useState(0);
|
|
942
|
+
const [animationDone, setAnimationDone] = useState(false);
|
|
943
|
+
const summaryRef = useRef([]);
|
|
944
|
+
const runningRef = useRef(false);
|
|
945
|
+
const fullText = `${HARNESS_LOGO}\n\n ${HARNESS_TAGLINE}`;
|
|
946
|
+
// Animated typing effect for welcome screen
|
|
947
|
+
useEffect(() => {
|
|
948
|
+
if (subStep.type !== "welcome")
|
|
949
|
+
return;
|
|
950
|
+
if (revealIndex >= fullText.length) {
|
|
951
|
+
setAnimationDone(true);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const timer = setTimeout(() => setRevealIndex((i) => i + 1), revealIndex === 0 ? 100 : 8);
|
|
955
|
+
return () => clearTimeout(timer);
|
|
956
|
+
}, [subStep.type, revealIndex, fullText.length]);
|
|
957
|
+
useInput((_input, key) => {
|
|
958
|
+
if (subStep.type === "welcome" && key.return) {
|
|
959
|
+
if (!animationDone) {
|
|
960
|
+
setRevealIndex(fullText.length);
|
|
961
|
+
setAnimationDone(true);
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
setSubStep({ type: "preset" });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
// Single effect for all async onboarding actions, guarded by ref.
|
|
969
|
+
// Uses else-if so only one branch can fire per render cycle.
|
|
970
|
+
useEffect(() => {
|
|
971
|
+
if (runningRef.current)
|
|
972
|
+
return;
|
|
973
|
+
if (subStep.type === "running-init") {
|
|
974
|
+
runningRef.current = true;
|
|
975
|
+
api
|
|
976
|
+
.execute({
|
|
977
|
+
command: "init",
|
|
978
|
+
options: {
|
|
979
|
+
force: false,
|
|
980
|
+
preset: subStep.preset,
|
|
981
|
+
delegate: subStep.delegate,
|
|
982
|
+
},
|
|
983
|
+
})
|
|
984
|
+
.then((output) => {
|
|
985
|
+
if (output.exitCode !== 0) {
|
|
986
|
+
const lines = [];
|
|
987
|
+
renderTextOutput(output, (line) => lines.push(line));
|
|
988
|
+
setSubStep({
|
|
989
|
+
type: "init-error",
|
|
990
|
+
message: lines.join("\n"),
|
|
991
|
+
preset: subStep.preset,
|
|
992
|
+
delegate: subStep.delegate,
|
|
993
|
+
});
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
summaryRef.current.push("Initialized .harness/ workspace");
|
|
997
|
+
if (subStep.preset)
|
|
998
|
+
summaryRef.current.push(`Applied preset: ${subStep.preset}`);
|
|
999
|
+
setSubStep({ type: "providers", selected: [] });
|
|
1000
|
+
})
|
|
1001
|
+
.catch((err) => {
|
|
1002
|
+
setSubStep({
|
|
1003
|
+
type: "init-error",
|
|
1004
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1005
|
+
preset: subStep.preset,
|
|
1006
|
+
delegate: subStep.delegate,
|
|
1007
|
+
});
|
|
1008
|
+
})
|
|
1009
|
+
.finally(() => {
|
|
1010
|
+
runningRef.current = false;
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
else if (subStep.type === "running-providers") {
|
|
1014
|
+
runningRef.current = true;
|
|
1015
|
+
const { selected } = subStep;
|
|
1016
|
+
(async () => {
|
|
1017
|
+
for (const provider of selected) {
|
|
1018
|
+
await api.execute({ command: "provider.enable", args: { provider } });
|
|
1019
|
+
}
|
|
1020
|
+
summaryRef.current.push(`Enabled provider(s): ${selected.join(", ")}`);
|
|
1021
|
+
setSubStep({ type: "add-prompt" });
|
|
1022
|
+
})()
|
|
1023
|
+
.catch((err) => {
|
|
1024
|
+
summaryRef.current.push(`Warning: provider enablement failed (${err instanceof Error ? err.message : String(err)})`);
|
|
1025
|
+
setSubStep({ type: "add-prompt" });
|
|
1026
|
+
})
|
|
1027
|
+
.finally(() => {
|
|
1028
|
+
runningRef.current = false;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
else if (subStep.type === "running-add-prompt") {
|
|
1032
|
+
runningRef.current = true;
|
|
1033
|
+
api
|
|
1034
|
+
.execute({ command: "add.prompt" })
|
|
1035
|
+
.then(() => {
|
|
1036
|
+
summaryRef.current.push("Added system prompt entity");
|
|
1037
|
+
setSubStep({ type: "running-apply" });
|
|
1038
|
+
})
|
|
1039
|
+
.catch((err) => {
|
|
1040
|
+
summaryRef.current.push(`Warning: failed to add prompt (${err instanceof Error ? err.message : String(err)})`);
|
|
1041
|
+
setSubStep({ type: "running-apply" });
|
|
1042
|
+
})
|
|
1043
|
+
.finally(() => {
|
|
1044
|
+
runningRef.current = false;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
else if (subStep.type === "running-apply") {
|
|
1048
|
+
runningRef.current = true;
|
|
1049
|
+
api
|
|
1050
|
+
.execute({ command: "apply" })
|
|
1051
|
+
.then(() => {
|
|
1052
|
+
summaryRef.current.push("Applied workspace (generated provider artifacts)");
|
|
1053
|
+
setSubStep({ type: "complete", summary: summaryRef.current });
|
|
1054
|
+
})
|
|
1055
|
+
.catch((err) => {
|
|
1056
|
+
summaryRef.current.push(`Warning: apply failed (${err instanceof Error ? err.message : String(err)})`);
|
|
1057
|
+
setSubStep({ type: "complete", summary: summaryRef.current });
|
|
1058
|
+
})
|
|
1059
|
+
.finally(() => {
|
|
1060
|
+
runningRef.current = false;
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}, [subStep, api]);
|
|
1064
|
+
if (subStep.type === "welcome") {
|
|
1065
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "cyan", children: fullText.slice(0, revealIndex) }), animationDone && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to get started..." }) }))] }));
|
|
1066
|
+
}
|
|
1067
|
+
if (subStep.type === "preset") {
|
|
1068
|
+
const presetOptions = [
|
|
1069
|
+
{ value: "", label: "Skip preset" },
|
|
1070
|
+
...presets.map((p) => ({ value: p.id, label: `${p.name} (${p.id})` })),
|
|
1071
|
+
];
|
|
1072
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 1/4 \u2014 Choose a preset" }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Preset", options: presetOptions, onChange: (value) => {
|
|
1073
|
+
if (value === "delegate") {
|
|
1074
|
+
setSubStep({ type: "delegate-provider" });
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
setSubStep({
|
|
1078
|
+
type: "running-init",
|
|
1079
|
+
preset: value || undefined,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
} }, "onboarding-preset") })] }));
|
|
1083
|
+
}
|
|
1084
|
+
if (subStep.type === "delegate-provider") {
|
|
1085
|
+
const providers = providerIdSchema.options.map((p) => ({
|
|
1086
|
+
label: p,
|
|
1087
|
+
value: p,
|
|
1088
|
+
}));
|
|
1089
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 1/4 \u2014 Select delegate provider" }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Provider", options: providers, onChange: (value) => {
|
|
1090
|
+
setSubStep({
|
|
1091
|
+
type: "running-init",
|
|
1092
|
+
preset: "delegate",
|
|
1093
|
+
delegate: value,
|
|
1094
|
+
});
|
|
1095
|
+
} }, "onboarding-delegate") })] }));
|
|
1096
|
+
}
|
|
1097
|
+
if (subStep.type === "running-init") {
|
|
1098
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Initializing workspace..." }) }));
|
|
1099
|
+
}
|
|
1100
|
+
if (subStep.type === "init-error") {
|
|
1101
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Initialization failed" }), _jsx(Box, { marginLeft: 2, marginTop: 1, children: _jsx(Text, { children: subStep.message }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Please resolve the issue and try again." }) }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Action", options: [
|
|
1102
|
+
{ label: "Retry initialization", value: "retry" },
|
|
1103
|
+
{ label: "Back", value: "back" },
|
|
1104
|
+
{ label: "Continue to main menu", value: "continue" },
|
|
1105
|
+
], onChange: (value) => {
|
|
1106
|
+
if (value === "retry") {
|
|
1107
|
+
setSubStep({
|
|
1108
|
+
type: "running-init",
|
|
1109
|
+
preset: subStep.preset,
|
|
1110
|
+
delegate: subStep.delegate,
|
|
1111
|
+
});
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (value === "back") {
|
|
1115
|
+
if (subStep.preset === "delegate") {
|
|
1116
|
+
setSubStep({ type: "delegate-provider" });
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
setSubStep({ type: "preset" });
|
|
1120
|
+
}
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
onComplete();
|
|
1124
|
+
} }, "onboarding-init-error-action") })] }));
|
|
1125
|
+
}
|
|
1126
|
+
if (subStep.type === "providers") {
|
|
1127
|
+
const remaining = providerIdSchema.options
|
|
1128
|
+
.filter((p) => !subStep.selected.includes(p))
|
|
1129
|
+
.map((p) => ({ label: p, value: p }));
|
|
1130
|
+
const doneLabel = subStep.selected.length === 0 ? "Skip (enable later)" : `Done (${subStep.selected.join(", ")})`;
|
|
1131
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 2/4 \u2014 Enable providers" }), subStep.selected.length > 0 && _jsxs(Text, { dimColor: true, children: ["Selected: ", subStep.selected.join(", ")] }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Provider", options: [{ label: doneLabel, value: "" }, ...remaining], onChange: (value) => {
|
|
1132
|
+
if (!value) {
|
|
1133
|
+
if (subStep.selected.length === 0) {
|
|
1134
|
+
setSubStep({ type: "add-prompt" });
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
setSubStep({
|
|
1138
|
+
type: "running-providers",
|
|
1139
|
+
selected: subStep.selected,
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
setSubStep({
|
|
1145
|
+
type: "providers",
|
|
1146
|
+
selected: [...subStep.selected, value],
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
} }, `onboarding-providers-${subStep.selected.length}`) })] }));
|
|
1150
|
+
}
|
|
1151
|
+
if (subStep.type === "running-providers") {
|
|
1152
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: `Enabling provider(s): ${subStep.selected.join(", ")}...` }) }));
|
|
1153
|
+
}
|
|
1154
|
+
if (subStep.type === "add-prompt") {
|
|
1155
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "Step 3/4 \u2014 System prompt" }), _jsx(ToggleConfirm, { message: "Add a system prompt entity?", defaultValue: true, onSubmit: (yes) => {
|
|
1156
|
+
if (yes) {
|
|
1157
|
+
setSubStep({ type: "running-add-prompt" });
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
setSubStep({ type: "running-apply" });
|
|
1161
|
+
}
|
|
1162
|
+
} })] }));
|
|
1163
|
+
}
|
|
1164
|
+
if (subStep.type === "running-add-prompt") {
|
|
1165
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Adding system prompt..." }) }));
|
|
1166
|
+
}
|
|
1167
|
+
if (subStep.type === "running-apply") {
|
|
1168
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Step 4/4 \u2014 Applying workspace..." }) }));
|
|
1169
|
+
}
|
|
1170
|
+
if (subStep.type === "complete") {
|
|
1171
|
+
return _jsx(OnboardingComplete, { summary: subStep.summary, onDismiss: onComplete });
|
|
1172
|
+
}
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
function OnboardingComplete({ summary, onDismiss }) {
|
|
1176
|
+
useInput((_input, key) => {
|
|
1177
|
+
if (key.return)
|
|
1178
|
+
onDismiss();
|
|
1179
|
+
});
|
|
1180
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: "green", children: "Setup complete!" }), summary.length > 0 && (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: summary.map((line, i) => (
|
|
1181
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static list, never reordered
|
|
1182
|
+
_jsxs(Text, { dimColor: true, children: ["- ", line] }, i))) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to continue to the main menu..." }) })] }));
|
|
1183
|
+
}
|
|
1184
|
+
function WorkspaceWarningStep({ diagnostics, api, onDismiss }) {
|
|
1185
|
+
const [running, setRunning] = useState(false);
|
|
1186
|
+
const [output, setOutput] = useState(null);
|
|
1187
|
+
const runningRef = useRef(false);
|
|
1188
|
+
useEffect(() => {
|
|
1189
|
+
if (!running || runningRef.current)
|
|
1190
|
+
return;
|
|
1191
|
+
runningRef.current = true;
|
|
1192
|
+
api
|
|
1193
|
+
.execute({ command: "doctor" })
|
|
1194
|
+
.then((result) => {
|
|
1195
|
+
const lines = [];
|
|
1196
|
+
renderTextOutput(result, (line) => lines.push(line));
|
|
1197
|
+
setOutput({ lines, isError: result.exitCode !== 0 });
|
|
1198
|
+
})
|
|
1199
|
+
.catch((err) => {
|
|
1200
|
+
setOutput({
|
|
1201
|
+
lines: [err instanceof Error ? err.message : String(err)],
|
|
1202
|
+
isError: true,
|
|
1203
|
+
});
|
|
1204
|
+
})
|
|
1205
|
+
.finally(() => {
|
|
1206
|
+
runningRef.current = false;
|
|
1207
|
+
setRunning(false);
|
|
1208
|
+
});
|
|
1209
|
+
}, [running, api]);
|
|
1210
|
+
useInput((_input, key) => {
|
|
1211
|
+
if (output && key.return)
|
|
1212
|
+
onDismiss();
|
|
1213
|
+
});
|
|
1214
|
+
if (running) {
|
|
1215
|
+
return (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Running doctor..." }) }));
|
|
1216
|
+
}
|
|
1217
|
+
if (output) {
|
|
1218
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: output.isError ? "red" : "green", children: output.isError ? "✗ Doctor" : "✓ Doctor" }), _jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: _jsx(Text, { children: output.lines.join("\n") }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to continue..." }) })] }));
|
|
1219
|
+
}
|
|
1220
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Workspace issues detected" }), _jsx(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: diagnostics.map((d) => (_jsxs(Text, { children: [_jsxs(Text, { color: "yellow", children: ["[", d.severity, "]"] }), " ", d.code, ": ", d.message] }, d.code))) }), _jsx(Box, { marginTop: 1, children: _jsx(AutocompleteSelect, { label: "Action", options: [
|
|
1221
|
+
{ label: "Run doctor", value: "doctor" },
|
|
1222
|
+
{ label: "Continue to menu", value: "continue" },
|
|
1223
|
+
], onChange: (value) => {
|
|
1224
|
+
if (value === "doctor") {
|
|
1225
|
+
setRunning(true);
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
onDismiss();
|
|
1229
|
+
}
|
|
1230
|
+
} }, "workspace-warning") })] }));
|
|
1231
|
+
}
|
|
378
1232
|
// ---------------------------------------------------------------------------
|
|
379
1233
|
// Exported entry point
|
|
380
1234
|
// ---------------------------------------------------------------------------
|
|
381
|
-
export async function runInteractiveAdapter(api) {
|
|
382
|
-
const
|
|
1235
|
+
export async function runInteractiveAdapter(api, options) {
|
|
1236
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
1237
|
+
const [presets, workspaceStatus] = await Promise.all([
|
|
1238
|
+
listBuiltinPresets().then((ps) => ps.map(summarizePreset)),
|
|
1239
|
+
detectWorkspaceStatus(cwd),
|
|
1240
|
+
]);
|
|
383
1241
|
let resolvedExitCode = 0;
|
|
384
|
-
const { waitUntilExit } = render(_jsx(App, { api: api, presets: presets, onExit: (code) => {
|
|
1242
|
+
const { waitUntilExit } = render(_jsx(App, { api: api, presets: presets, workspaceStatus: workspaceStatus, onExit: (code) => {
|
|
385
1243
|
resolvedExitCode = code;
|
|
386
1244
|
} }), { exitOnCtrlC: true });
|
|
387
1245
|
await waitUntilExit();
|