@united-workforce/cli 0.4.0 → 0.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 +30 -3
- package/dist/.build-fingerprint +1 -0
- package/dist/__tests__/adapter-json-roundtrip.test.js +16 -6
- package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
- package/dist/__tests__/concurrency.test.d.ts +2 -0
- package/dist/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/concurrency.test.js +196 -0
- package/dist/__tests__/concurrency.test.js.map +1 -0
- package/dist/__tests__/config-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/config-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/config-text-renderer.test.js +137 -0
- package/dist/__tests__/config-text-renderer.test.js.map +1 -0
- package/dist/__tests__/e2e-mock-agent.test.js +23 -7
- package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
- package/dist/__tests__/format-text-default.test.d.ts +2 -0
- package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-default.test.js +43 -0
- package/dist/__tests__/format-text-default.test.js.map +1 -0
- package/dist/__tests__/format-text-registry.test.d.ts +2 -0
- package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
- package/dist/__tests__/format-text-registry.test.js +158 -0
- package/dist/__tests__/format-text-registry.test.js.map +1 -0
- package/dist/__tests__/issue-180-workflow-ref-removed.test.js +1 -1
- package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/log-text-renderer.test.js +265 -0
- package/dist/__tests__/log-text-renderer.test.js.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
- package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
- package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
- package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
- package/dist/__tests__/pid-recycling.test.js +9 -7
- package/dist/__tests__/pid-recycling.test.js.map +1 -1
- package/dist/__tests__/prompt.test.js +46 -4
- package/dist/__tests__/prompt.test.js.map +1 -1
- package/dist/__tests__/resolve-head-hash.test.js +8 -0
- package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js +3 -1
- package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
- package/dist/__tests__/step-ask.test.js +9 -1
- package/dist/__tests__/step-ask.test.js.map +1 -1
- package/dist/__tests__/store-unified-threads.test.js +19 -17
- package/dist/__tests__/store-unified-threads.test.js.map +1 -1
- package/dist/__tests__/thread-agent-failure-suspended.test.d.ts +2 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.d.ts.map +1 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js +332 -0
- package/dist/__tests__/thread-agent-failure-suspended.test.js.map +1 -0
- package/dist/__tests__/thread-cancel-status.test.js +19 -13
- package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
- package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-join.test.d.ts +2 -0
- package/dist/__tests__/thread-join.test.d.ts.map +1 -0
- package/dist/__tests__/thread-join.test.js +77 -0
- package/dist/__tests__/thread-join.test.js.map +1 -0
- package/dist/__tests__/thread-list-filters.test.js +10 -8
- package/dist/__tests__/thread-list-filters.test.js.map +1 -1
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
- package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
- package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
- package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
- package/dist/__tests__/thread-poke.test.js +15 -2
- package/dist/__tests__/thread-poke.test.js.map +1 -1
- package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
- package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
- package/dist/__tests__/thread-resume.test.js +11 -1
- package/dist/__tests__/thread-resume.test.js.map +1 -1
- package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
- package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
- package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
- package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
- package/dist/__tests__/thread-suspend-step.test.js +5 -2
- package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
- package/dist/__tests__/thread-test-helpers.d.ts +7 -0
- package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
- package/dist/__tests__/thread-test-helpers.js +13 -0
- package/dist/__tests__/thread-test-helpers.js.map +1 -1
- package/dist/__tests__/thread.test.js +11 -9
- package/dist/__tests__/thread.test.js.map +1 -1
- package/dist/__tests__/validate-semantic.test.js +56 -2
- package/dist/__tests__/validate-semantic.test.js.map +1 -1
- package/dist/__tests__/workflow-list-recursive.test.js +10 -7
- package/dist/__tests__/workflow-list-recursive.test.js.map +1 -1
- package/dist/__tests__/workflow-paths.test.d.ts +2 -0
- package/dist/__tests__/workflow-paths.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-paths.test.js +261 -0
- package/dist/__tests__/workflow-paths.test.js.map +1 -0
- package/dist/__tests__/workflow-resolution.test.js +10 -7
- package/dist/__tests__/workflow-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-show-resolution.test.js +10 -7
- package/dist/__tests__/workflow-show-resolution.test.js.map +1 -1
- package/dist/__tests__/workflow-validate.test.js +75 -55
- package/dist/__tests__/workflow-validate.test.js.map +1 -1
- package/dist/__tests__/write-envelope.test.d.ts +2 -0
- package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
- package/dist/__tests__/write-envelope.test.js +201 -0
- package/dist/__tests__/write-envelope.test.js.map +1 -0
- package/dist/cli.js +76 -36
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +81 -3
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +42 -29
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/setup.d.ts +9 -4
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +51 -7
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/thread.d.ts +12 -0
- package/dist/commands/thread.d.ts.map +1 -1
- package/dist/commands/thread.js +226 -9
- package/dist/commands/thread.js.map +1 -1
- package/dist/commands/workflow.d.ts +2 -2
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +26 -10
- package/dist/commands/workflow.js.map +1 -1
- package/dist/concurrency/concurrency.d.ts +34 -0
- package/dist/concurrency/concurrency.d.ts.map +1 -0
- package/dist/concurrency/concurrency.js +216 -0
- package/dist/concurrency/concurrency.js.map +1 -0
- package/dist/concurrency/index.d.ts +3 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +2 -0
- package/dist/concurrency/index.js.map +1 -0
- package/dist/concurrency/types.d.ts +19 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +2 -0
- package/dist/concurrency/types.js.map +1 -0
- package/dist/format.d.ts +69 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +198 -1
- package/dist/format.js.map +1 -1
- package/dist/output-mappers.d.ts +122 -0
- package/dist/output-mappers.d.ts.map +1 -0
- package/dist/output-mappers.js +134 -0
- package/dist/output-mappers.js.map +1 -0
- package/dist/schemas.d.ts +4 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +31 -4
- package/dist/schemas.js.map +1 -1
- package/dist/store.d.ts +11 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +20 -1
- package/dist/store.js.map +1 -1
- package/dist/text-renderers.d.ts +30 -0
- package/dist/text-renderers.d.ts.map +1 -0
- package/dist/text-renderers.js +251 -0
- package/dist/text-renderers.js.map +1 -0
- package/dist/validate-semantic.d.ts.map +1 -1
- package/dist/validate-semantic.js +28 -11
- package/dist/validate-semantic.js.map +1 -1
- package/examples/brainstorm.yaml +130 -0
- package/examples/debate.yaml +169 -0
- package/examples/socratic-questioning.yaml +112 -0
- package/package.json +12 -11
- package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
- package/src/__tests__/concurrency.test.ts +266 -0
- package/src/__tests__/config-text-renderer.test.ts +156 -0
- package/src/__tests__/e2e-mock-agent.test.ts +45 -7
- package/src/__tests__/format-text-default.test.ts +49 -0
- package/src/__tests__/format-text-registry.test.ts +173 -0
- package/src/__tests__/issue-180-workflow-ref-removed.test.ts +1 -1
- package/src/__tests__/log-text-renderer.test.ts +294 -0
- package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
- package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
- package/src/__tests__/pid-recycling.test.ts +9 -8
- package/src/__tests__/prompt.test.ts +48 -4
- package/src/__tests__/resolve-head-hash.test.ts +7 -0
- package/src/__tests__/solve-issue-tea-worktree.test.ts +3 -1
- package/src/__tests__/step-ask.test.ts +8 -1
- package/src/__tests__/store-unified-threads.test.ts +21 -18
- package/src/__tests__/thread-agent-failure-suspended.test.ts +406 -0
- package/src/__tests__/thread-cancel-status.test.ts +21 -14
- package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
- package/src/__tests__/thread-join.test.ts +103 -0
- package/src/__tests__/thread-list-filters.test.ts +9 -9
- package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
- package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
- package/src/__tests__/thread-poke.test.ts +14 -2
- package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
- package/src/__tests__/thread-resume.test.ts +10 -1
- package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
- package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
- package/src/__tests__/thread-suspend-step.test.ts +5 -2
- package/src/__tests__/thread-test-helpers.ts +15 -1
- package/src/__tests__/thread.test.ts +10 -10
- package/src/__tests__/validate-semantic.test.ts +59 -2
- package/src/__tests__/workflow-list-recursive.test.ts +9 -9
- package/src/__tests__/workflow-paths.test.ts +337 -0
- package/src/__tests__/workflow-resolution.test.ts +9 -8
- package/src/__tests__/workflow-show-resolution.test.ts +9 -8
- package/src/__tests__/workflow-validate.test.ts +78 -56
- package/src/__tests__/write-envelope.test.ts +257 -0
- package/src/cli.ts +111 -35
- package/src/commands/config.ts +85 -3
- package/src/commands/prompt.ts +42 -29
- package/src/commands/setup.ts +57 -7
- package/src/commands/thread.ts +280 -9
- package/src/commands/workflow.ts +32 -11
- package/src/concurrency/concurrency.ts +245 -0
- package/src/concurrency/index.ts +10 -0
- package/src/concurrency/types.ts +19 -0
- package/src/format.ts +282 -2
- package/src/output-mappers.ts +255 -0
- package/src/schemas.ts +39 -3
- package/src/store.ts +25 -1
- package/src/text-renderers.ts +355 -0
- package/src/validate-semantic.ts +33 -12
- package/LICENSE +0 -21
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
|
|
3
|
-
import type { CasRef, ThreadId, ThreadStatus } from "@united-workforce/protocol";
|
|
3
|
+
import type { CasRef, OutputSchemaName, ThreadId, ThreadStatus } from "@united-workforce/protocol";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
|
|
6
6
|
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
|
@@ -16,6 +16,7 @@ import { cmdStepAsk, cmdStepFork, cmdStepList, cmdStepRead, cmdStepShow } from "
|
|
|
16
16
|
import {
|
|
17
17
|
cmdThreadCancel,
|
|
18
18
|
cmdThreadExec,
|
|
19
|
+
cmdThreadJoin,
|
|
19
20
|
cmdThreadList,
|
|
20
21
|
cmdThreadPoke,
|
|
21
22
|
cmdThreadRead,
|
|
@@ -32,12 +33,61 @@ import {
|
|
|
32
33
|
cmdWorkflowShow,
|
|
33
34
|
cmdWorkflowValidate,
|
|
34
35
|
} from "./commands/workflow.js";
|
|
35
|
-
import {
|
|
36
|
-
|
|
36
|
+
import {
|
|
37
|
+
formatOutput,
|
|
38
|
+
isOutputFormat,
|
|
39
|
+
type OutputFormat,
|
|
40
|
+
SUPPORTED_FORMATS,
|
|
41
|
+
writeEnvelope,
|
|
42
|
+
} from "./format.js";
|
|
43
|
+
import {
|
|
44
|
+
toStepDetailPayload,
|
|
45
|
+
toStepListPayload,
|
|
46
|
+
toThreadExecPayload,
|
|
47
|
+
toThreadListPayload,
|
|
48
|
+
toThreadStartPayload,
|
|
49
|
+
toThreadStatusPayload,
|
|
50
|
+
toValidateResultPayload,
|
|
51
|
+
toWorkflowAddPayload,
|
|
52
|
+
toWorkflowDetailPayload,
|
|
53
|
+
toWorkflowListPayload,
|
|
54
|
+
} from "./output-mappers.js";
|
|
55
|
+
import { createUwfStore, resolveStorageRoot } from "./store.js";
|
|
56
|
+
|
|
57
|
+
function getFormat(): OutputFormat {
|
|
58
|
+
const raw = program.opts().format as string;
|
|
59
|
+
if (!isOutputFormat(raw)) {
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`Invalid --format: ${raw}. Must be one of: ${SUPPORTED_FORMATS.join(", ")}\n`,
|
|
62
|
+
);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return raw;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function writeOutput(
|
|
69
|
+
payload: unknown,
|
|
70
|
+
schemaName: OutputSchemaName,
|
|
71
|
+
storageRoot: string,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const fmt = getFormat();
|
|
74
|
+
const uwf = await createUwfStore(storageRoot);
|
|
75
|
+
await writeEnvelope(payload, schemaName, {
|
|
76
|
+
format: fmt,
|
|
77
|
+
store: uwf.store,
|
|
78
|
+
schemas: uwf.schemas,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
37
81
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Legacy raw output for commands without an output schema (log/config/setup).
|
|
84
|
+
* Always emits text/JSON/YAML based on the active --format. For `text`
|
|
85
|
+
* (the default) it renders via the per-command registry when available
|
|
86
|
+
* and falls back to JSON.
|
|
87
|
+
*/
|
|
88
|
+
function writeRawOutput(data: unknown, commandPath?: string): void {
|
|
89
|
+
const fmt = getFormat();
|
|
90
|
+
process.stdout.write(`${formatOutput(data, fmt, commandPath)}\n`);
|
|
41
91
|
}
|
|
42
92
|
|
|
43
93
|
function runAction(action: () => Promise<void>): void {
|
|
@@ -60,7 +110,11 @@ program
|
|
|
60
110
|
" workflow → thread → step → turn",
|
|
61
111
|
)
|
|
62
112
|
.version(pkg.default.version, "-V, --version");
|
|
63
|
-
program.option(
|
|
113
|
+
program.option(
|
|
114
|
+
"--format <fmt>",
|
|
115
|
+
"Output format: text (default), json, yaml, raw-json, raw-yaml",
|
|
116
|
+
"text",
|
|
117
|
+
);
|
|
64
118
|
|
|
65
119
|
const workflow = program
|
|
66
120
|
.command("workflow")
|
|
@@ -74,7 +128,7 @@ workflow
|
|
|
74
128
|
const storageRoot = resolveStorageRoot();
|
|
75
129
|
runAction(async () => {
|
|
76
130
|
const result = await cmdWorkflowAdd(storageRoot, file);
|
|
77
|
-
writeOutput(result);
|
|
131
|
+
await writeOutput(toWorkflowAddPayload(result), "workflow-add", storageRoot);
|
|
78
132
|
});
|
|
79
133
|
});
|
|
80
134
|
|
|
@@ -83,9 +137,13 @@ workflow
|
|
|
83
137
|
.description("Validate a workflow YAML without registering it (CI-friendly)")
|
|
84
138
|
.argument("<file>", "Workflow YAML file")
|
|
85
139
|
.action((file: string) => {
|
|
140
|
+
const storageRoot = resolveStorageRoot();
|
|
86
141
|
runAction(async () => {
|
|
87
|
-
await cmdWorkflowValidate(file);
|
|
88
|
-
|
|
142
|
+
const errors = await cmdWorkflowValidate(file);
|
|
143
|
+
await writeOutput(toValidateResultPayload(errors), "validate-result", storageRoot);
|
|
144
|
+
if (errors.length > 0) {
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
89
147
|
});
|
|
90
148
|
});
|
|
91
149
|
|
|
@@ -97,7 +155,7 @@ workflow
|
|
|
97
155
|
const storageRoot = resolveStorageRoot();
|
|
98
156
|
runAction(async () => {
|
|
99
157
|
const result = await cmdWorkflowShow(storageRoot, id, process.cwd());
|
|
100
|
-
writeOutput(result);
|
|
158
|
+
await writeOutput(toWorkflowDetailPayload(result), "workflow-detail", storageRoot);
|
|
101
159
|
});
|
|
102
160
|
});
|
|
103
161
|
|
|
@@ -108,7 +166,7 @@ workflow
|
|
|
108
166
|
const storageRoot = resolveStorageRoot();
|
|
109
167
|
runAction(async () => {
|
|
110
168
|
const result = await cmdWorkflowList(storageRoot, process.cwd());
|
|
111
|
-
writeOutput(result);
|
|
169
|
+
await writeOutput(toWorkflowListPayload(result), "workflow-list", storageRoot);
|
|
112
170
|
});
|
|
113
171
|
});
|
|
114
172
|
|
|
@@ -130,7 +188,7 @@ thread
|
|
|
130
188
|
process.cwd(),
|
|
131
189
|
opts.cwd ?? process.cwd(),
|
|
132
190
|
);
|
|
133
|
-
writeOutput(result);
|
|
191
|
+
await writeOutput(toThreadStartPayload(result), "thread-start", storageRoot);
|
|
134
192
|
});
|
|
135
193
|
});
|
|
136
194
|
|
|
@@ -167,11 +225,7 @@ thread
|
|
|
167
225
|
background,
|
|
168
226
|
backgroundWorker,
|
|
169
227
|
);
|
|
170
|
-
|
|
171
|
-
writeOutput(results[0]);
|
|
172
|
-
} else {
|
|
173
|
-
writeOutput(results);
|
|
174
|
-
}
|
|
228
|
+
await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
|
|
175
229
|
});
|
|
176
230
|
},
|
|
177
231
|
);
|
|
@@ -184,7 +238,7 @@ thread
|
|
|
184
238
|
const storageRoot = resolveStorageRoot();
|
|
185
239
|
runAction(async () => {
|
|
186
240
|
const result = await cmdThreadShow(storageRoot, threadId);
|
|
187
|
-
writeOutput(result);
|
|
241
|
+
await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
|
|
188
242
|
});
|
|
189
243
|
});
|
|
190
244
|
|
|
@@ -292,7 +346,7 @@ thread
|
|
|
292
346
|
take,
|
|
293
347
|
showAll,
|
|
294
348
|
);
|
|
295
|
-
writeOutput(result);
|
|
349
|
+
await writeOutput(toThreadListPayload(result), "thread-list", storageRoot);
|
|
296
350
|
});
|
|
297
351
|
},
|
|
298
352
|
);
|
|
@@ -314,7 +368,7 @@ thread
|
|
|
314
368
|
supplement,
|
|
315
369
|
agentOverride,
|
|
316
370
|
);
|
|
317
|
-
writeOutput(result);
|
|
371
|
+
await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
|
|
318
372
|
});
|
|
319
373
|
});
|
|
320
374
|
|
|
@@ -334,7 +388,7 @@ thread
|
|
|
334
388
|
opts.prompt,
|
|
335
389
|
agentOverride,
|
|
336
390
|
);
|
|
337
|
-
writeOutput(result);
|
|
391
|
+
await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
|
|
338
392
|
});
|
|
339
393
|
});
|
|
340
394
|
|
|
@@ -346,7 +400,7 @@ thread
|
|
|
346
400
|
const storageRoot = resolveStorageRoot();
|
|
347
401
|
runAction(async () => {
|
|
348
402
|
const result = await cmdThreadStop(storageRoot, threadId);
|
|
349
|
-
|
|
403
|
+
writeRawOutput(result, "thread stop");
|
|
350
404
|
});
|
|
351
405
|
});
|
|
352
406
|
|
|
@@ -358,7 +412,25 @@ thread
|
|
|
358
412
|
const storageRoot = resolveStorageRoot();
|
|
359
413
|
runAction(async () => {
|
|
360
414
|
const result = await cmdThreadCancel(storageRoot, threadId);
|
|
361
|
-
|
|
415
|
+
writeRawOutput(result, "thread cancel");
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
thread
|
|
420
|
+
.command("join")
|
|
421
|
+
.description("Block until a running thread finishes, then return the final result")
|
|
422
|
+
.argument("<thread-id>", "Thread ULID")
|
|
423
|
+
.option("--timeout <seconds>", "Max seconds to wait before giving up")
|
|
424
|
+
.action((threadId: string, opts: { timeout: string | undefined }) => {
|
|
425
|
+
const storageRoot = resolveStorageRoot();
|
|
426
|
+
runAction(async () => {
|
|
427
|
+
const timeoutMs = opts.timeout !== undefined ? Number(opts.timeout) * 1000 : null;
|
|
428
|
+
if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
429
|
+
process.stderr.write("invalid --timeout: must be a positive number\n");
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
const results = await cmdThreadJoin(storageRoot, threadId, timeoutMs);
|
|
433
|
+
await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
|
|
362
434
|
});
|
|
363
435
|
});
|
|
364
436
|
|
|
@@ -401,7 +473,7 @@ step
|
|
|
401
473
|
const storageRoot = resolveStorageRoot();
|
|
402
474
|
runAction(async () => {
|
|
403
475
|
const result = await cmdStepList(storageRoot, threadId);
|
|
404
|
-
writeOutput(result);
|
|
476
|
+
await writeOutput(toStepListPayload(result), "step-list", storageRoot);
|
|
405
477
|
});
|
|
406
478
|
});
|
|
407
479
|
|
|
@@ -413,7 +485,11 @@ step
|
|
|
413
485
|
const storageRoot = resolveStorageRoot();
|
|
414
486
|
runAction(async () => {
|
|
415
487
|
const detail = await cmdStepShow(storageRoot, stepHash as CasRef);
|
|
416
|
-
writeOutput(
|
|
488
|
+
await writeOutput(
|
|
489
|
+
toStepDetailPayload(stepHash as CasRef, detail),
|
|
490
|
+
"step-detail",
|
|
491
|
+
storageRoot,
|
|
492
|
+
);
|
|
417
493
|
});
|
|
418
494
|
});
|
|
419
495
|
|
|
@@ -475,7 +551,7 @@ step
|
|
|
475
551
|
const storageRoot = resolveStorageRoot();
|
|
476
552
|
runAction(async () => {
|
|
477
553
|
const result = await cmdStepFork(storageRoot, stepHash as CasRef);
|
|
478
|
-
|
|
554
|
+
writeRawOutput(result);
|
|
479
555
|
});
|
|
480
556
|
});
|
|
481
557
|
|
|
@@ -618,7 +694,7 @@ program
|
|
|
618
694
|
.command("setup")
|
|
619
695
|
.description(
|
|
620
696
|
"Configure the default agent. Run without --agent for interactive wizard.\n" +
|
|
621
|
-
"LLM
|
|
697
|
+
"Each adapter owns its own LLM configuration — the engine config is LLM-free.",
|
|
622
698
|
)
|
|
623
699
|
.option("--agent <name>", "Default agent adapter (e.g. hermes → uwf-hermes)")
|
|
624
700
|
.action((opts: { agent?: string }) => {
|
|
@@ -626,7 +702,7 @@ program
|
|
|
626
702
|
runAction(async () => {
|
|
627
703
|
if (opts.agent !== undefined && opts.agent !== "") {
|
|
628
704
|
const result = await cmdSetup({ agent: opts.agent, storageRoot });
|
|
629
|
-
|
|
705
|
+
writeRawOutput(result);
|
|
630
706
|
} else {
|
|
631
707
|
await cmdSetupInteractive(storageRoot);
|
|
632
708
|
}
|
|
@@ -642,7 +718,7 @@ log
|
|
|
642
718
|
const storageRoot = resolveStorageRoot();
|
|
643
719
|
runAction(async () => {
|
|
644
720
|
const result = await cmdLogList(storageRoot);
|
|
645
|
-
|
|
721
|
+
writeRawOutput(result, "log list");
|
|
646
722
|
});
|
|
647
723
|
});
|
|
648
724
|
|
|
@@ -665,7 +741,7 @@ log
|
|
|
665
741
|
process: opts.process ?? null,
|
|
666
742
|
date: opts.date ?? null,
|
|
667
743
|
});
|
|
668
|
-
|
|
744
|
+
writeRawOutput(result, "log show");
|
|
669
745
|
});
|
|
670
746
|
},
|
|
671
747
|
);
|
|
@@ -678,7 +754,7 @@ log
|
|
|
678
754
|
const storageRoot = resolveStorageRoot();
|
|
679
755
|
runAction(async () => {
|
|
680
756
|
const result = await cmdLogClean(storageRoot, opts.before);
|
|
681
|
-
|
|
757
|
+
writeRawOutput(result);
|
|
682
758
|
});
|
|
683
759
|
});
|
|
684
760
|
|
|
@@ -691,7 +767,7 @@ config
|
|
|
691
767
|
const storageRoot = resolveStorageRoot();
|
|
692
768
|
runAction(async () => {
|
|
693
769
|
const result = await cmdConfigList(storageRoot);
|
|
694
|
-
|
|
770
|
+
writeRawOutput(result, "config list");
|
|
695
771
|
});
|
|
696
772
|
});
|
|
697
773
|
|
|
@@ -706,7 +782,7 @@ config
|
|
|
706
782
|
const storageRoot = resolveStorageRoot();
|
|
707
783
|
runAction(async () => {
|
|
708
784
|
const result = await cmdConfigGet(storageRoot, key);
|
|
709
|
-
|
|
785
|
+
writeRawOutput({ value: result }, "config get");
|
|
710
786
|
});
|
|
711
787
|
});
|
|
712
788
|
|
|
@@ -719,7 +795,7 @@ config
|
|
|
719
795
|
const storageRoot = resolveStorageRoot();
|
|
720
796
|
runAction(async () => {
|
|
721
797
|
const result = await cmdConfigSet(storageRoot, key, value);
|
|
722
|
-
|
|
798
|
+
writeRawOutput(result, "config set");
|
|
723
799
|
});
|
|
724
800
|
});
|
|
725
801
|
|
package/src/commands/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
3
4
|
import { parse, stringify } from "yaml";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -21,6 +22,12 @@ const VALID_CONFIG_KEYS: Record<
|
|
|
21
22
|
// No knownFields — workflow/role names are user-defined
|
|
22
23
|
},
|
|
23
24
|
defaultAgent: { nested: false },
|
|
25
|
+
concurrency: {
|
|
26
|
+
nested: true,
|
|
27
|
+
knownFields: ["maxRunning"],
|
|
28
|
+
minDepth: 2,
|
|
29
|
+
},
|
|
30
|
+
workflowPaths: { nested: false },
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
/**
|
|
@@ -216,6 +223,31 @@ function parseArgsValue(value: string): unknown {
|
|
|
216
223
|
throw new Error("Value for 'args' key must be a JSON array starting with '['");
|
|
217
224
|
}
|
|
218
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Parse value for a top-level string array key (must be JSON array of strings).
|
|
228
|
+
*/
|
|
229
|
+
function parseStringArrayValue(value: string, keyName: string): unknown {
|
|
230
|
+
if (value.startsWith("[")) {
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(value);
|
|
233
|
+
if (!Array.isArray(parsed)) {
|
|
234
|
+
throw new Error("Value must be an array");
|
|
235
|
+
}
|
|
236
|
+
for (const item of parsed) {
|
|
237
|
+
if (typeof item !== "string") {
|
|
238
|
+
throw new Error(`All items must be strings, got ${typeof item}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return parsed;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Invalid JSON array for ${keyName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
throw new Error(`Value for '${keyName}' must be a JSON array starting with '['`);
|
|
249
|
+
}
|
|
250
|
+
|
|
219
251
|
/**
|
|
220
252
|
* Validate that we're not setting a property on a non-object
|
|
221
253
|
*/
|
|
@@ -260,10 +292,18 @@ export async function cmdConfigSet(
|
|
|
260
292
|
|
|
261
293
|
const lastSegment = path[path.length - 1];
|
|
262
294
|
|
|
263
|
-
// Parse value if it's for an array key (args)
|
|
295
|
+
// Parse value if it's for an array key (args, workflowPaths)
|
|
264
296
|
let parsedValue: unknown = value;
|
|
265
|
-
if (
|
|
297
|
+
if (path[0] === "workflowPaths") {
|
|
298
|
+
parsedValue = parseStringArrayValue(value, "workflowPaths");
|
|
299
|
+
} else if (lastSegment === "args") {
|
|
266
300
|
parsedValue = parseArgsValue(value);
|
|
301
|
+
} else if (lastSegment === "maxRunning") {
|
|
302
|
+
const num = Number(value);
|
|
303
|
+
if (!Number.isInteger(num) || num < 1) {
|
|
304
|
+
throw new Error("Value for 'maxRunning' must be a positive integer");
|
|
305
|
+
}
|
|
306
|
+
parsedValue = num;
|
|
267
307
|
}
|
|
268
308
|
|
|
269
309
|
// Validate we're not setting a property on a non-object
|
|
@@ -274,3 +314,45 @@ export async function cmdConfigSet(
|
|
|
274
314
|
|
|
275
315
|
return { key, value: parsedValue };
|
|
276
316
|
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Expand leading `~/` in a path to the user's home directory.
|
|
320
|
+
*/
|
|
321
|
+
function expandTilde(p: string): string {
|
|
322
|
+
if (p.startsWith("~/") || p === "~") {
|
|
323
|
+
return join(homedir(), p.slice(1));
|
|
324
|
+
}
|
|
325
|
+
return p;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Load workflowPaths from config and resolve to absolute paths.
|
|
330
|
+
* Returns empty array if config doesn't exist or key is missing.
|
|
331
|
+
*/
|
|
332
|
+
export function loadWorkflowPaths(storageRoot: string): string[] {
|
|
333
|
+
const configPath = getConfigPath(storageRoot);
|
|
334
|
+
if (!existsSync(configPath)) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let config: Record<string, unknown>;
|
|
339
|
+
try {
|
|
340
|
+
config = loadConfig(configPath);
|
|
341
|
+
} catch {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const raw = config.workflowPaths;
|
|
346
|
+
if (!Array.isArray(raw)) {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const result: string[] = [];
|
|
351
|
+
for (const item of raw) {
|
|
352
|
+
if (typeof item === "string" && item.trim() !== "") {
|
|
353
|
+
const expanded = expandTilde(item.trim());
|
|
354
|
+
result.push(resolvePath(expanded));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
}
|
package/src/commands/prompt.ts
CHANGED
|
@@ -148,44 +148,36 @@ pipx install 'hermes-agent[acp]'
|
|
|
148
148
|
pip install -e '.[acp]'
|
|
149
149
|
\`\`\`
|
|
150
150
|
|
|
151
|
-
### Step 2 — Configure
|
|
151
|
+
### Step 2 — Configure default agent
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Run the interactive wizard:
|
|
154
154
|
|
|
155
155
|
\`\`\`bash
|
|
156
|
-
uwf setup
|
|
156
|
+
uwf setup
|
|
157
157
|
\`\`\`
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
**Preset providers** — when using a preset name, \`--base-url\` is auto-filled and can be omitted:
|
|
162
|
-
|
|
163
|
-
| Provider | Name | Default base URL |
|
|
164
|
-
|----------|------|-----------------|
|
|
165
|
-
| OpenAI | \`openai\` | https://api.openai.com/v1 |
|
|
166
|
-
| xAI | \`xai\` | https://api.x.ai/v1 |
|
|
167
|
-
| OpenRouter | \`openrouter\` | https://openrouter.ai/api/v1 |
|
|
168
|
-
| Venice | \`venice\` | https://api.venice.ai/api/v1 |
|
|
169
|
-
| Dashscope | \`dashscope\` | https://dashscope.aliyuncs.com/compatible-mode/v1 |
|
|
170
|
-
| DeepSeek | \`deepseek\` | https://api.deepseek.com/v1 |
|
|
171
|
-
| SiliconFlow | \`siliconflow\` | https://api.siliconflow.cn/v1 |
|
|
172
|
-
| VolcEngine | \`volcengine\` | https://ark.cn-beijing.volces.com/api/v3 |
|
|
173
|
-
| Kimi (Moonshot) | \`kimi\` | https://api.moonshot.cn/v1 |
|
|
174
|
-
| GLM (Zhipu AI) | \`glm\` | https://open.bigmodel.cn/api/paas/v4 |
|
|
175
|
-
| StepFun | \`stepfun\` | https://api.stepfun.com/v1 |
|
|
176
|
-
| MiniMax | \`minimax\` | https://api.minimax.io/v1 |
|
|
177
|
-
| Ollama (local) | \`ollama\` | http://localhost:11434/v1 |
|
|
178
|
-
|
|
179
|
-
For **non-preset providers**, you must specify \`--base-url\` manually.
|
|
180
|
-
|
|
181
|
-
Example:
|
|
159
|
+
Or configure non-interactively:
|
|
160
|
+
|
|
182
161
|
\`\`\`bash
|
|
183
|
-
uwf setup --
|
|
162
|
+
uwf setup --agent <adapter-command>
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
**Note:** \`--agent\` takes the adapter **command name** (e.g. \`uwf-hermes\`, \`uwf-claude-code\`), not the npm package name.
|
|
166
|
+
|
|
167
|
+
Config is saved to \`~/.uwf/config.yaml\`:
|
|
168
|
+
|
|
169
|
+
\`\`\`yaml
|
|
170
|
+
agents:
|
|
171
|
+
hermes:
|
|
172
|
+
command: uwf-hermes
|
|
173
|
+
args: []
|
|
174
|
+
defaultAgent: hermes
|
|
175
|
+
agentOverrides: {}
|
|
184
176
|
\`\`\`
|
|
185
177
|
|
|
186
|
-
|
|
178
|
+
**LLM configuration** is per-adapter — each adapter manages its own provider, model, and API key settings independently (typically via environment variables like \`CLAUDE_MODEL\`, \`ANTHROPIC_API_KEY\`, etc.). The engine config (\`~/.uwf/config.yaml\`) is LLM-free.
|
|
187
179
|
|
|
188
|
-
|
|
180
|
+
Verify with \`cat ~/.uwf/config.yaml\`.
|
|
189
181
|
|
|
190
182
|
### Step 3 — Install skills
|
|
191
183
|
|
|
@@ -339,5 +331,26 @@ uwf prompt workflow-authoring # workflow YAML design guide
|
|
|
339
331
|
uwf prompt adapter-developing # building agent adapters
|
|
340
332
|
uwf prompt bootstrap # this guide
|
|
341
333
|
\`\`\`
|
|
334
|
+
|
|
335
|
+
## What's next — introduce uwf to the user
|
|
336
|
+
|
|
337
|
+
After setup is complete, give the user a brief introduction to what uwf can do
|
|
338
|
+
and invite them to try it. Cover these three things:
|
|
339
|
+
|
|
340
|
+
1. **Run workflows** — execute pre-built workflows to automate multi-step tasks.
|
|
341
|
+
Setup auto-registers example workflows, ready to use immediately.
|
|
342
|
+
2. **Create workflows** — design custom YAML workflows for their own recurring tasks
|
|
343
|
+
(code review, issue triage, release pipelines, etc.). Use \`uwf prompt workflow-authoring\`
|
|
344
|
+
for the authoring guide.
|
|
345
|
+
3. **Debug & improve workflows** — iterate on workflow definitions, inspect thread state
|
|
346
|
+
with \`uwf thread show\`, replay failed steps with \`uwf thread poke\`, and refine
|
|
347
|
+
role procedures based on real execution results.
|
|
348
|
+
|
|
349
|
+
**Discover & try built-in examples:**
|
|
350
|
+
|
|
351
|
+
Run \`uwf workflow list\` to see which workflows are registered (setup auto-registers
|
|
352
|
+
several built-in examples). Show the user what's available, then invite them to try
|
|
353
|
+
one — for instance, suggest a fun debate topic like "AI 是否会抢了人类的工作?" and
|
|
354
|
+
offer to kick it off for them.
|
|
342
355
|
`;
|
|
343
356
|
}
|
package/src/commands/setup.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { stdin as input, stdout as output } from "node:process";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { parse, stringify } from "yaml";
|
|
8
|
+
import { cmdWorkflowAdd } from "./workflow.js";
|
|
7
9
|
|
|
8
10
|
export type SetupArgs = {
|
|
9
11
|
agent: string;
|
|
@@ -261,10 +263,56 @@ export function _checkAdapterAvailability(agentName: string): string[] {
|
|
|
261
263
|
return warnings;
|
|
262
264
|
}
|
|
263
265
|
|
|
266
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
// Bundled example workflows
|
|
268
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
/** Resolve the examples/ directory bundled with the CLI package. */
|
|
271
|
+
function _findExamplesDir(): string | null {
|
|
272
|
+
// Walk up from this file (src/commands/ or dist/commands/) to the package root
|
|
273
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
274
|
+
for (let i = 0; i < 5; i++) {
|
|
275
|
+
const candidate = join(dir, "examples");
|
|
276
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
277
|
+
return candidate;
|
|
278
|
+
}
|
|
279
|
+
dir = dirname(dir);
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Register bundled example workflows. Non-destructive — silently skips
|
|
286
|
+
* any that fail (e.g. already registered with same hash).
|
|
287
|
+
* Returns list of successfully registered workflow names.
|
|
288
|
+
*/
|
|
289
|
+
export async function _registerBundledExamples(storageRoot: string): Promise<string[]> {
|
|
290
|
+
const examplesDir = _findExamplesDir();
|
|
291
|
+
if (examplesDir === null) return [];
|
|
292
|
+
|
|
293
|
+
const registered: string[] = [];
|
|
294
|
+
const files = readdirSync(examplesDir)
|
|
295
|
+
.filter((f) => f.endsWith(".yaml"))
|
|
296
|
+
.sort();
|
|
297
|
+
|
|
298
|
+
for (const file of files) {
|
|
299
|
+
try {
|
|
300
|
+
const result = await cmdWorkflowAdd(storageRoot, join(examplesDir, file));
|
|
301
|
+
registered.push(result.name);
|
|
302
|
+
console.error(` ✓ ${result.name}`);
|
|
303
|
+
} catch {
|
|
304
|
+
// Skip silently — workflow may already exist or be invalid
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return registered;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
264
313
|
/**
|
|
265
314
|
* Non-interactive setup. Engine config is LLM-free — only writes
|
|
266
|
-
* agents + defaultAgent.
|
|
267
|
-
* config.yaml under `providers` and `models`.
|
|
315
|
+
* agents + defaultAgent. Each adapter owns its own LLM configuration.
|
|
268
316
|
*/
|
|
269
317
|
export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>> {
|
|
270
318
|
const { storageRoot } = args;
|
|
@@ -287,16 +335,20 @@ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>
|
|
|
287
335
|
console.error(`⚠ ${w}`);
|
|
288
336
|
}
|
|
289
337
|
|
|
338
|
+
// Auto-register bundled example workflows
|
|
339
|
+
const registeredExamples = await _registerBundledExamples(storageRoot);
|
|
340
|
+
|
|
290
341
|
return {
|
|
291
342
|
configPath,
|
|
292
343
|
defaultAgent: merged.defaultAgent,
|
|
293
344
|
adapterWarnings,
|
|
345
|
+
registeredExamples,
|
|
294
346
|
};
|
|
295
347
|
}
|
|
296
348
|
|
|
297
349
|
/**
|
|
298
|
-
* Interactive setup — prompts the user only for the default agent.
|
|
299
|
-
*
|
|
350
|
+
* Interactive setup — prompts the user only for the default agent.
|
|
351
|
+
* Each adapter owns its own LLM configuration.
|
|
300
352
|
*/
|
|
301
353
|
export async function cmdSetupInteractive(storageRoot: string): Promise<Record<string, unknown>> {
|
|
302
354
|
const rl = createInterface({ input, output });
|
|
@@ -313,8 +365,6 @@ export async function cmdSetupInteractive(storageRoot: string): Promise<Record<s
|
|
|
313
365
|
console.log(' uwf thread start <name> -p "..." Start a thread');
|
|
314
366
|
console.log(" uwf thread exec <thread-id> Execute next step");
|
|
315
367
|
console.log("");
|
|
316
|
-
console.log("LLM config: edit ~/.uwf/config.yaml (providers + models sections).");
|
|
317
|
-
console.log("");
|
|
318
368
|
|
|
319
369
|
return null as unknown as Record<string, unknown>;
|
|
320
370
|
} finally {
|