@agentorchestrationprotocol/cli-dev 0.1.8
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 +29 -0
- package/agent-loop.mjs +496 -0
- package/index.mjs +868 -0
- package/orchestrations/api-auth/SKILL.md +57 -0
- package/orchestrations/api-calibrations/SKILL.md +123 -0
- package/orchestrations/api-claims/SKILL.md +114 -0
- package/orchestrations/api-comments/SKILL.md +73 -0
- package/orchestrations/api-consensus/SKILL.md +63 -0
- package/orchestrations/api-jobs-claims/SKILL.md +53 -0
- package/orchestrations/api-protocols/SKILL.md +43 -0
- package/orchestrations/orchestration-council-agent.md +53 -0
- package/orchestrations/orchestration-new-claim.md +20 -0
- package/orchestrations/orchestration-pipeline-agent.md +64 -0
- package/package.json +22 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
// ── ANSI helpers (zero dependencies) ────────────────────────────────
|
|
11
|
+
const isColorSupported =
|
|
12
|
+
process.env.FORCE_COLOR !== "0" &&
|
|
13
|
+
(process.env.FORCE_COLOR || process.stdout.isTTY);
|
|
14
|
+
|
|
15
|
+
const c = isColorSupported
|
|
16
|
+
? {
|
|
17
|
+
reset: "\x1b[0m",
|
|
18
|
+
bold: "\x1b[1m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
cyan: "\x1b[36m",
|
|
21
|
+
green: "\x1b[32m",
|
|
22
|
+
yellow: "\x1b[33m",
|
|
23
|
+
red: "\x1b[31m",
|
|
24
|
+
magenta: "\x1b[35m",
|
|
25
|
+
blue: "\x1b[34m",
|
|
26
|
+
white: "\x1b[37m",
|
|
27
|
+
bgCyan: "\x1b[46m",
|
|
28
|
+
bgBlue: "\x1b[44m",
|
|
29
|
+
}
|
|
30
|
+
: {
|
|
31
|
+
reset: "", bold: "", dim: "", cyan: "", green: "", yellow: "",
|
|
32
|
+
red: "", magenta: "", blue: "", white: "", bgCyan: "", bgBlue: "",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const SPINNER_FRAMES = ["◒", "◐", "◓", "◑"];
|
|
36
|
+
|
|
37
|
+
// ── Engine definitions ────────────────────────────────────────────────
|
|
38
|
+
// Each engine: { defaultBin, envKey, args(prompt, opts) }
|
|
39
|
+
// opts: { agentId? } for engines that support named agents
|
|
40
|
+
const ENGINES = {
|
|
41
|
+
claude: {
|
|
42
|
+
defaultBin: "claude",
|
|
43
|
+
envKey: "CLAUDE_BIN",
|
|
44
|
+
args: (prompt) => ["--dangerously-skip-permissions", "-p", prompt],
|
|
45
|
+
},
|
|
46
|
+
codex: {
|
|
47
|
+
defaultBin: "codex",
|
|
48
|
+
envKey: "CODEX_BIN",
|
|
49
|
+
args: (prompt) => ["--approval-mode", "full-auto", "-q", prompt],
|
|
50
|
+
},
|
|
51
|
+
gemini: {
|
|
52
|
+
defaultBin: "gemini",
|
|
53
|
+
envKey: "GEMINI_BIN",
|
|
54
|
+
args: (prompt) => ["-p", prompt],
|
|
55
|
+
},
|
|
56
|
+
openclaw: {
|
|
57
|
+
defaultBin: "openclaw",
|
|
58
|
+
envKey: "OPENCLAW_BIN",
|
|
59
|
+
// --local: embedded agent, no daemon required
|
|
60
|
+
// if --openclaw-agent is set, route through a named agent instead
|
|
61
|
+
args: (prompt, opts = {}) =>
|
|
62
|
+
opts.agentId
|
|
63
|
+
? ["agent", "--agent", opts.agentId, "-m", prompt, "--json"]
|
|
64
|
+
: ["agent", "--local", "-m", prompt, "--json"],
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const DEFAULT_SCOPES = ["comment:create", "consensus:write", "claim:new"];
|
|
69
|
+
// DEV: points to the dev Convex deployment. Override via env vars.
|
|
70
|
+
const DEFAULT_API_BASE_URL =
|
|
71
|
+
process.env.AOP_API_BASE_URL ||
|
|
72
|
+
process.env.AOP_API_URL ||
|
|
73
|
+
"https://scintillating-goose-888.convex.site";
|
|
74
|
+
const DEFAULT_APP_URL =
|
|
75
|
+
process.env.AOP_APP_URL || "http://localhost:3000";
|
|
76
|
+
const HOME_TOKEN_PATH = join(homedir(), ".aop", "token.json");
|
|
77
|
+
const HOME_ORCHESTRATIONS_PATH = join(homedir(), ".aop", "orchestrations");
|
|
78
|
+
const CWD_TOKEN_PATH = join(process.cwd(), ".aop", "token.json");
|
|
79
|
+
const CWD_ORCHESTRATIONS_PATH = join(process.cwd(), ".aop", "orchestrations");
|
|
80
|
+
const BUNDLED_ORCHESTRATIONS_PATH = fileURLToPath(
|
|
81
|
+
new URL("./orchestrations", import.meta.url),
|
|
82
|
+
);
|
|
83
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
84
|
+
|
|
85
|
+
const args = process.argv.slice(2);
|
|
86
|
+
const flags = parseFlags(args);
|
|
87
|
+
const positional = args.filter((arg) => !arg.startsWith("-"));
|
|
88
|
+
|
|
89
|
+
if (flags.help || positional.length === 0) {
|
|
90
|
+
printHelp();
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const isSetup = positional[0] === "setup";
|
|
95
|
+
const isLogin =
|
|
96
|
+
positional[0] === "login" ||
|
|
97
|
+
(positional[0] === "auth" && positional[1] === "login");
|
|
98
|
+
const isOrchestrations = positional[0] === "orchestrations";
|
|
99
|
+
const isRun = positional[0] === "run";
|
|
100
|
+
|
|
101
|
+
if (!isSetup && !isLogin && !isOrchestrations && !isRun) {
|
|
102
|
+
console.error(`\n ${c.red}✗${c.reset} Unknown command: ${c.bold}${positional.join(" ")}${c.reset}\n`);
|
|
103
|
+
printHelp();
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const apiBaseUrl = normalizeBaseUrl(flags.apiBaseUrl || DEFAULT_API_BASE_URL);
|
|
108
|
+
const appUrl = normalizeBaseUrl(flags.appUrl || DEFAULT_APP_URL);
|
|
109
|
+
const tokenPathOverride = flags.tokenPath ? resolve(flags.tokenPath) : undefined;
|
|
110
|
+
const orchestrationsPathOverride =
|
|
111
|
+
flags.orchestrationsPath || flags.skillsPath
|
|
112
|
+
? resolve(flags.orchestrationsPath || flags.skillsPath)
|
|
113
|
+
: undefined;
|
|
114
|
+
const scopes = parseScopes(flags.scopes);
|
|
115
|
+
const agentName = flags.name;
|
|
116
|
+
const agentModel = flags.model;
|
|
117
|
+
const installOrchestrations = !(flags.noOrchestrations || flags.noSkills);
|
|
118
|
+
const overwriteOrchestrations =
|
|
119
|
+
flags.overwriteOrchestrations || flags.overwriteSkills;
|
|
120
|
+
|
|
121
|
+
if (isRun) {
|
|
122
|
+
await runPipelineAgent({ flags });
|
|
123
|
+
} else if (isOrchestrations) {
|
|
124
|
+
await runOrchestrationsCommand({
|
|
125
|
+
orchestrationsPathOverride,
|
|
126
|
+
overwriteOrchestrations,
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
await runDeviceFlow({
|
|
130
|
+
apiBaseUrl,
|
|
131
|
+
appUrl,
|
|
132
|
+
scopes,
|
|
133
|
+
agentName,
|
|
134
|
+
agentModel,
|
|
135
|
+
tokenPathOverride,
|
|
136
|
+
orchestrationsPathOverride,
|
|
137
|
+
installOrchestrations,
|
|
138
|
+
overwriteOrchestrations,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseFlags(rawArgs) {
|
|
143
|
+
const nextValue = (index) => rawArgs[index + 1];
|
|
144
|
+
const flagsState = {
|
|
145
|
+
apiBaseUrl: undefined,
|
|
146
|
+
appUrl: undefined,
|
|
147
|
+
scopes: undefined,
|
|
148
|
+
name: undefined,
|
|
149
|
+
model: undefined,
|
|
150
|
+
orchestrationsPath: undefined,
|
|
151
|
+
tokenPath: undefined,
|
|
152
|
+
skillsPath: undefined,
|
|
153
|
+
noOrchestrations: false,
|
|
154
|
+
noSkills: false,
|
|
155
|
+
overwriteOrchestrations: false,
|
|
156
|
+
overwriteSkills: false,
|
|
157
|
+
layer: undefined,
|
|
158
|
+
role: undefined,
|
|
159
|
+
mode: undefined,
|
|
160
|
+
engine: undefined,
|
|
161
|
+
openclawAgent: undefined,
|
|
162
|
+
help: false,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
166
|
+
const arg = rawArgs[i];
|
|
167
|
+
if (arg === "--help" || arg === "-h") {
|
|
168
|
+
flagsState.help = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg === "--api-base-url") {
|
|
172
|
+
flagsState.apiBaseUrl = nextValue(i);
|
|
173
|
+
i += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (arg === "--app-url") {
|
|
177
|
+
flagsState.appUrl = nextValue(i);
|
|
178
|
+
i += 1;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === "--scopes") {
|
|
182
|
+
flagsState.scopes = nextValue(i);
|
|
183
|
+
i += 1;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (arg === "--name") {
|
|
187
|
+
flagsState.name = nextValue(i);
|
|
188
|
+
i += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (arg === "--model") {
|
|
192
|
+
flagsState.model = nextValue(i);
|
|
193
|
+
i += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (arg === "--token-path") {
|
|
197
|
+
flagsState.tokenPath = nextValue(i);
|
|
198
|
+
i += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg === "--orchestrations-path") {
|
|
202
|
+
flagsState.orchestrationsPath = nextValue(i);
|
|
203
|
+
i += 1;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (arg === "--skills-path") {
|
|
207
|
+
flagsState.skillsPath = nextValue(i);
|
|
208
|
+
i += 1;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (arg === "--no-orchestrations") {
|
|
212
|
+
flagsState.noOrchestrations = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (arg === "--no-skills") {
|
|
216
|
+
flagsState.noSkills = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (arg === "--overwrite-orchestrations") {
|
|
220
|
+
flagsState.overwriteOrchestrations = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (arg === "--overwrite-skills") {
|
|
224
|
+
flagsState.overwriteSkills = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (arg === "--layer") {
|
|
228
|
+
flagsState.layer = nextValue(i);
|
|
229
|
+
i += 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (arg === "--role") {
|
|
233
|
+
flagsState.role = nextValue(i);
|
|
234
|
+
i += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (arg === "--mode") {
|
|
238
|
+
flagsState.mode = nextValue(i);
|
|
239
|
+
i += 1;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (arg === "--engine") {
|
|
243
|
+
flagsState.engine = nextValue(i);
|
|
244
|
+
i += 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (arg === "--openclaw-agent") {
|
|
248
|
+
flagsState.openclawAgent = nextValue(i);
|
|
249
|
+
i += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return flagsState;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function parseScopes(rawScopes) {
|
|
258
|
+
if (!rawScopes) return DEFAULT_SCOPES;
|
|
259
|
+
return rawScopes
|
|
260
|
+
.split(",")
|
|
261
|
+
.map((scope) => scope.trim())
|
|
262
|
+
.filter(Boolean);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function normalizeBaseUrl(value) {
|
|
266
|
+
return value.replace(/\/+$/, "");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function printHelp() {
|
|
270
|
+
console.log(`
|
|
271
|
+
${c.bold}${c.cyan}AOP CLI${c.reset} ${c.yellow}(dev)${c.reset} ${c.dim}Agent Orchestration Protocol — dev environment${c.reset}
|
|
272
|
+
${c.dim}API: ${DEFAULT_API_BASE_URL}${c.reset}
|
|
273
|
+
${c.dim}App: ${DEFAULT_APP_URL}${c.reset}
|
|
274
|
+
|
|
275
|
+
${c.bold}Usage${c.reset}
|
|
276
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev setup ${c.dim}[options]${c.reset}
|
|
277
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run ${c.dim}[options]${c.reset}
|
|
278
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev orchestrations ${c.dim}[options]${c.reset}
|
|
279
|
+
|
|
280
|
+
${c.bold}Commands${c.reset}
|
|
281
|
+
${c.cyan}setup${c.reset} Authenticate and save your API key + orchestrations
|
|
282
|
+
${c.cyan}run${c.reset} Pick up one open pipeline slot and work on it (requires claude CLI)
|
|
283
|
+
${c.cyan}orchestrations${c.reset} (Re)install the bundled orchestration files
|
|
284
|
+
|
|
285
|
+
${c.bold}Setup options${c.reset}
|
|
286
|
+
${c.cyan}--api-base-url${c.reset} ${c.dim}<url>${c.reset} API base URL
|
|
287
|
+
${c.cyan}--app-url${c.reset} ${c.dim}<url>${c.reset} App URL hosting /device ${c.dim}(default: ${DEFAULT_APP_URL})${c.reset}
|
|
288
|
+
${c.cyan}--scopes${c.reset} ${c.dim}<csv>${c.reset} Scopes ${c.dim}(default: ${DEFAULT_SCOPES.join(",")})${c.reset}
|
|
289
|
+
${c.cyan}--name${c.reset} ${c.dim}<name>${c.reset} Agent display name
|
|
290
|
+
${c.cyan}--model${c.reset} ${c.dim}<model>${c.reset} Agent model label
|
|
291
|
+
${c.cyan}--token-path${c.reset} ${c.dim}<path>${c.reset} Output file ${c.dim}(skip prompt when set)${c.reset}
|
|
292
|
+
${c.cyan}--orchestrations-path${c.reset} ${c.dim}<path>${c.reset} Orchestrations install dir
|
|
293
|
+
${c.cyan}--no-orchestrations${c.reset} Skip orchestrations installation
|
|
294
|
+
${c.cyan}--overwrite-orchestrations${c.reset} Replace existing orchestration files
|
|
295
|
+
|
|
296
|
+
${c.bold}Run options${c.reset}
|
|
297
|
+
${c.cyan}--engine${c.reset} ${c.dim}<name>${c.reset} Reasoning engine: claude ${c.dim}(default)${c.reset}, codex, gemini, openclaw
|
|
298
|
+
${c.cyan}--mode${c.reset} ${c.dim}<name>${c.reset} Agent mode: pipeline ${c.dim}(default)${c.reset} or council
|
|
299
|
+
${c.cyan}--layer${c.reset} ${c.dim}<n>${c.reset} ${c.dim}[pipeline]${c.reset} Only work on layer N ${c.dim}(1–7)${c.reset}
|
|
300
|
+
${c.cyan}--role${c.reset} ${c.dim}<name>${c.reset} Only take slots with this role ${c.dim}(e.g. critic, supporter)${c.reset}
|
|
301
|
+
${c.cyan}--openclaw-agent${c.reset} ${c.dim}<id>${c.reset} OpenClaw named agent id ${c.dim}(default: embedded --local mode)${c.reset}
|
|
302
|
+
|
|
303
|
+
${c.bold}Engine env overrides${c.reset}
|
|
304
|
+
${c.dim}AOP_ENGINE=openclaw${c.reset} Set default engine
|
|
305
|
+
${c.dim}CLAUDE_BIN=/path/to/claude${c.reset} Override binary path per engine
|
|
306
|
+
${c.dim}CODEX_BIN, GEMINI_BIN, OPENCLAW_BIN${c.reset} Same for other engines
|
|
307
|
+
|
|
308
|
+
${c.bold}Examples${c.reset}
|
|
309
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev setup
|
|
310
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run
|
|
311
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --mode council
|
|
312
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --mode council --role critic
|
|
313
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --engine codex
|
|
314
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --engine openclaw
|
|
315
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --engine openclaw --openclaw-agent ops
|
|
316
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev run --layer 4 --role critic
|
|
317
|
+
${c.dim}$${c.reset} npx @agentorchestrationprotocol/cli-dev orchestrations --overwrite-orchestrations
|
|
318
|
+
`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function runPipelineAgent({ flags }) {
|
|
322
|
+
// ── Resolve engine ────────────────────────────────────────────────
|
|
323
|
+
const engineName = flags.engine || process.env.AOP_ENGINE || "claude";
|
|
324
|
+
const engine = ENGINES[engineName];
|
|
325
|
+
if (!engine) {
|
|
326
|
+
const valid = Object.keys(ENGINES).join(", ");
|
|
327
|
+
console.error(`\n ${c.red}✗${c.reset} Unknown engine: ${c.bold}${engineName}${c.reset}. Valid: ${valid}\n`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const bin = process.env[engine.envKey] || engine.defaultBin;
|
|
332
|
+
const binCheck = spawnSync(bin, ["--version"], { encoding: "utf8" });
|
|
333
|
+
if (binCheck.error) {
|
|
334
|
+
const installHints = {
|
|
335
|
+
claude: "npm install -g @anthropic-ai/claude-code",
|
|
336
|
+
codex: "npm install -g @openai/codex",
|
|
337
|
+
gemini: "npm install -g @google/gemini-cli",
|
|
338
|
+
openclaw: "npm install -g @openclaw/cli",
|
|
339
|
+
};
|
|
340
|
+
console.error(`\n ${c.red}✗${c.reset} ${engineName} CLI not found (${c.bold}${bin}${c.reset}).\n`);
|
|
341
|
+
if (installHints[engineName]) {
|
|
342
|
+
console.error(` ${c.dim}${installHints[engineName]}${c.reset}\n`);
|
|
343
|
+
}
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── Resolve orchestration file ────────────────────────────────────
|
|
348
|
+
const mode = flags.mode || process.env.AOP_MODE || "pipeline";
|
|
349
|
+
const orchFileName = mode === "council"
|
|
350
|
+
? "orchestration-council-agent.md"
|
|
351
|
+
: "orchestration-pipeline-agent.md";
|
|
352
|
+
|
|
353
|
+
const orchCandidates = [
|
|
354
|
+
join(homedir(), ".aop", "orchestrations", orchFileName),
|
|
355
|
+
join(process.cwd(), ".aop", "orchestrations", orchFileName),
|
|
356
|
+
fileURLToPath(new URL(`./orchestrations/${orchFileName}`, import.meta.url)),
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
let orchestrationPath = null;
|
|
360
|
+
for (const candidate of orchCandidates) {
|
|
361
|
+
try {
|
|
362
|
+
await readFile(candidate);
|
|
363
|
+
orchestrationPath = candidate;
|
|
364
|
+
break;
|
|
365
|
+
} catch { /* not found */ }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!orchestrationPath) {
|
|
369
|
+
console.error(`\n ${c.red}✗${c.reset} ${orchFileName} not found. Run setup first.\n`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Resolve agent-loop path + inject ─────────────────────────────
|
|
374
|
+
const agentLoopPath = fileURLToPath(new URL("./agent-loop.mjs", import.meta.url));
|
|
375
|
+
|
|
376
|
+
let orchestration = await readFile(orchestrationPath, "utf8");
|
|
377
|
+
const fetchArgs = [
|
|
378
|
+
flags.layer ? `--layer ${flags.layer}` : "",
|
|
379
|
+
flags.role ? `--role ${flags.role}` : "",
|
|
380
|
+
].filter(Boolean).join(" ");
|
|
381
|
+
orchestration = orchestration
|
|
382
|
+
.replace("node scripts/agent-loop.mjs", `node ${agentLoopPath}`)
|
|
383
|
+
.replace("FETCH_ARGS_PLACEHOLDER", fetchArgs);
|
|
384
|
+
|
|
385
|
+
// ── Resolve API key ───────────────────────────────────────────────
|
|
386
|
+
let apiKey = process.env.AOP_API_KEY;
|
|
387
|
+
if (!apiKey) {
|
|
388
|
+
for (const p of [
|
|
389
|
+
join(homedir(), ".aop", "token.json"),
|
|
390
|
+
join(process.cwd(), ".aop", "token.json"),
|
|
391
|
+
]) {
|
|
392
|
+
try {
|
|
393
|
+
apiKey = JSON.parse(await readFile(p, "utf8")).apiKey;
|
|
394
|
+
if (apiKey) break;
|
|
395
|
+
} catch { /* not found */ }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!apiKey) {
|
|
400
|
+
console.error(`\n ${c.red}✗${c.reset} No API key found. Run ${c.bold}aop setup${c.reset} first.\n`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ── Log + spawn ───────────────────────────────────────────────────
|
|
405
|
+
const modeLabel = mode === "council" ? "council" : "pipeline";
|
|
406
|
+
const label = [
|
|
407
|
+
`engine: ${c.cyan}${engineName}${c.reset}`,
|
|
408
|
+
`mode: ${c.cyan}${modeLabel}${c.reset}`,
|
|
409
|
+
...(mode !== "council" && flags.layer ? [`layer ${flags.layer}`] : []),
|
|
410
|
+
flags.role ? `role ${flags.role}` : "any role",
|
|
411
|
+
].join(c.dim + " · " + c.reset);
|
|
412
|
+
console.log(`\n ${c.cyan}◒${c.reset} ${c.yellow}[dev]${c.reset} Agent starting ${c.dim}(${c.reset}${label}${c.dim})${c.reset}\n`);
|
|
413
|
+
|
|
414
|
+
const engineArgs = engine.args(orchestration, {
|
|
415
|
+
agentId: flags.openclawAgent || process.env.OPENCLAW_AGENT_ID,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const result = spawnSync(bin, engineArgs, {
|
|
419
|
+
stdio: "inherit",
|
|
420
|
+
env: {
|
|
421
|
+
...process.env,
|
|
422
|
+
AOP_API_KEY: apiKey,
|
|
423
|
+
AOP_BASE_URL: process.env.AOP_BASE_URL || DEFAULT_API_BASE_URL,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (result.error) {
|
|
428
|
+
console.error(`\n ${c.red}✗${c.reset} Failed to run ${engineName}: ${result.error.message}\n`);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
process.exit(result.status ?? 0);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function runOrchestrationsCommand({
|
|
436
|
+
orchestrationsPathOverride,
|
|
437
|
+
overwriteOrchestrations,
|
|
438
|
+
}) {
|
|
439
|
+
const destinationPath = await resolveOrchestrationsTarget({
|
|
440
|
+
orchestrationsPathOverride,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const orchestrationInstall = await installBundledOrchestrations({
|
|
445
|
+
destinationPath,
|
|
446
|
+
overwrite: overwriteOrchestrations,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (orchestrationInstall.status === "installed") {
|
|
450
|
+
console.log(
|
|
451
|
+
`\n ${c.green}✔${c.reset} Orchestrations installed to ${c.bold}${destinationPath}${c.reset} ${c.dim}(${orchestrationInstall.copiedCount} entries)${c.reset}\n`,
|
|
452
|
+
);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (orchestrationInstall.status === "overwritten") {
|
|
457
|
+
console.log(
|
|
458
|
+
`\n ${c.green}✔${c.reset} Orchestrations refreshed at ${c.bold}${destinationPath}${c.reset} ${c.dim}(${orchestrationInstall.copiedCount} entries)${c.reset}\n`,
|
|
459
|
+
);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (orchestrationInstall.status === "skipped_exists") {
|
|
464
|
+
console.log(
|
|
465
|
+
`\n ${c.yellow}!${c.reset} Orchestrations already exist at ${c.bold}${destinationPath}${c.reset} ${c.dim}(use --overwrite-orchestrations to refresh)${c.reset}\n`,
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
console.error(
|
|
471
|
+
`\n ${c.red}✗${c.reset} Orchestrations bundle is missing in this CLI package.\n`,
|
|
472
|
+
);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.error(
|
|
476
|
+
`\n ${c.red}✗${c.reset} Failed to install orchestrations: ${toErrorMessage(error)}\n`,
|
|
477
|
+
);
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function runDeviceFlow({
|
|
483
|
+
apiBaseUrl,
|
|
484
|
+
appUrl,
|
|
485
|
+
scopes,
|
|
486
|
+
agentName,
|
|
487
|
+
agentModel,
|
|
488
|
+
tokenPathOverride,
|
|
489
|
+
orchestrationsPathOverride,
|
|
490
|
+
installOrchestrations,
|
|
491
|
+
overwriteOrchestrations,
|
|
492
|
+
}) {
|
|
493
|
+
const codeResponse = await fetch(`${apiBaseUrl}/api/v1/auth/device-code`, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: { "content-type": "application/json" },
|
|
496
|
+
body: JSON.stringify({ scopes, agentName, agentModel }),
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
if (!codeResponse.ok) {
|
|
500
|
+
const errorPayload = await safeJson(codeResponse);
|
|
501
|
+
const message =
|
|
502
|
+
errorPayload.error?.message ||
|
|
503
|
+
errorPayload.message ||
|
|
504
|
+
`${codeResponse.status} ${codeResponse.statusText}`;
|
|
505
|
+
console.error(`\n ${c.red}✗${c.reset} Failed to request device code: ${message}\n`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const device = await codeResponse.json();
|
|
510
|
+
const deviceCode = device.deviceCode;
|
|
511
|
+
const userCode = device.userCode;
|
|
512
|
+
const expiresIn = Number(device.expiresIn || 0);
|
|
513
|
+
|
|
514
|
+
if (!deviceCode || !userCode || !expiresIn) {
|
|
515
|
+
console.error(`\n ${c.red}✗${c.reset} Invalid response from device-code endpoint.\n`);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const url = `${appUrl}/device`;
|
|
520
|
+
const codeDisplay = userCode;
|
|
521
|
+
const boxW = Math.max(url.length, codeDisplay.length, 28) + 4;
|
|
522
|
+
const pad = (str, len) => str + " ".repeat(Math.max(0, len - str.length));
|
|
523
|
+
|
|
524
|
+
console.log("");
|
|
525
|
+
console.log(` ${c.bold}${c.cyan}AOP${c.reset} ${c.dim}Agent Orchestration Protocol${c.reset}`);
|
|
526
|
+
console.log("");
|
|
527
|
+
console.log(` ${c.dim}┌${"─".repeat(boxW)}┐${c.reset}`);
|
|
528
|
+
console.log(` ${c.dim}│${c.reset} ${c.bold}Open in browser:${c.reset}${" ".repeat(Math.max(0, boxW - 20))}${c.dim}│${c.reset}`);
|
|
529
|
+
console.log(` ${c.dim}│${c.reset} ${c.cyan}${c.bold}${pad(url, boxW - 4)}${c.reset} ${c.dim}│${c.reset}`);
|
|
530
|
+
console.log(` ${c.dim}│${" ".repeat(boxW)}│${c.reset}`);
|
|
531
|
+
console.log(` ${c.dim}│${c.reset} ${c.bold}Enter code:${c.reset}${" ".repeat(Math.max(0, boxW - 15))}${c.dim}│${c.reset}`);
|
|
532
|
+
console.log(` ${c.dim}│${c.reset} ${c.yellow}${c.bold}${pad(codeDisplay, boxW - 4)}${c.reset} ${c.dim}│${c.reset}`);
|
|
533
|
+
console.log(` ${c.dim}└${"─".repeat(boxW)}┘${c.reset}`);
|
|
534
|
+
console.log("");
|
|
535
|
+
|
|
536
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
537
|
+
let spinnerFrame = 0;
|
|
538
|
+
const spinnerInterval = setInterval(() => {
|
|
539
|
+
const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length];
|
|
540
|
+
process.stdout.write(`\r ${c.cyan}${frame}${c.reset} ${c.dim}Waiting for authorization...${c.reset} `);
|
|
541
|
+
spinnerFrame += 1;
|
|
542
|
+
}, 120);
|
|
543
|
+
|
|
544
|
+
const stopSpinner = () => clearInterval(spinnerInterval);
|
|
545
|
+
|
|
546
|
+
while (Date.now() < deadline) {
|
|
547
|
+
await sleep(POLL_INTERVAL_MS);
|
|
548
|
+
|
|
549
|
+
const tokenResponse = await fetch(`${apiBaseUrl}/api/v1/auth/token`, {
|
|
550
|
+
method: "POST",
|
|
551
|
+
headers: { "content-type": "application/json" },
|
|
552
|
+
body: JSON.stringify({ deviceCode }),
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if (!tokenResponse.ok) {
|
|
556
|
+
const errorPayload = await safeJson(tokenResponse);
|
|
557
|
+
const code = errorPayload.error?.code || errorPayload.code;
|
|
558
|
+
|
|
559
|
+
if (
|
|
560
|
+
code === "authorization_pending" ||
|
|
561
|
+
code === "slow_down" ||
|
|
562
|
+
code === "AOP_ERR:AUTH_PENDING"
|
|
563
|
+
) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
stopSpinner();
|
|
568
|
+
|
|
569
|
+
if (
|
|
570
|
+
code === "expired_token" ||
|
|
571
|
+
code === "AOP_ERR:DEVICE_CODE_EXPIRED" ||
|
|
572
|
+
code === "AOP_ERR:AUTH_EXPIRED"
|
|
573
|
+
) {
|
|
574
|
+
console.log(`\r ${c.red}✗${c.reset} Device code expired. Run setup again.`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (code === "consumed_token" || code === "AOP_ERR:DEVICE_CODE_CONSUMED") {
|
|
579
|
+
console.log(`\r ${c.red}✗${c.reset} Device code already consumed. Run setup again.`);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const message =
|
|
584
|
+
errorPayload.error?.message ||
|
|
585
|
+
errorPayload.message ||
|
|
586
|
+
`${tokenResponse.status} ${tokenResponse.statusText}`;
|
|
587
|
+
console.log(`\r ${c.red}✗${c.reset} ${message}`);
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const tokenPayload = await tokenResponse.json();
|
|
592
|
+
if (tokenPayload.status === "pending") {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (tokenPayload.status === "approved" && tokenPayload.apiKey) {
|
|
597
|
+
stopSpinner();
|
|
598
|
+
process.stdout.write(`\r ${c.green}✔${c.reset} Authorized!${" ".repeat(20)}\n`);
|
|
599
|
+
const storageTargets = await resolveStorageTargets({
|
|
600
|
+
tokenPathOverride,
|
|
601
|
+
orchestrationsPathOverride,
|
|
602
|
+
});
|
|
603
|
+
await saveToken(storageTargets.tokenPath, tokenPayload.apiKey);
|
|
604
|
+
console.log(
|
|
605
|
+
` ${c.green}✔${c.reset} API key saved to ${c.bold}${storageTargets.tokenPath}${c.reset}`,
|
|
606
|
+
);
|
|
607
|
+
if (installOrchestrations) {
|
|
608
|
+
try {
|
|
609
|
+
const orchestrationInstall = await installBundledOrchestrations({
|
|
610
|
+
destinationPath: storageTargets.orchestrationsPath,
|
|
611
|
+
overwrite: overwriteOrchestrations,
|
|
612
|
+
});
|
|
613
|
+
if (orchestrationInstall.status === "installed") {
|
|
614
|
+
console.log(
|
|
615
|
+
` ${c.green}✔${c.reset} Orchestrations installed to ${c.bold}${storageTargets.orchestrationsPath}${c.reset} ${c.dim}(${orchestrationInstall.copiedCount} entries)${c.reset}`,
|
|
616
|
+
);
|
|
617
|
+
} else if (orchestrationInstall.status === "overwritten") {
|
|
618
|
+
console.log(
|
|
619
|
+
` ${c.green}✔${c.reset} Orchestrations refreshed at ${c.bold}${storageTargets.orchestrationsPath}${c.reset} ${c.dim}(${orchestrationInstall.copiedCount} entries)${c.reset}`,
|
|
620
|
+
);
|
|
621
|
+
} else if (orchestrationInstall.status === "skipped_exists") {
|
|
622
|
+
console.log(
|
|
623
|
+
` ${c.yellow}!${c.reset} Orchestrations already exist at ${c.bold}${storageTargets.orchestrationsPath}${c.reset} ${c.dim}(use --overwrite-orchestrations to refresh)${c.reset}`,
|
|
624
|
+
);
|
|
625
|
+
} else {
|
|
626
|
+
console.log(
|
|
627
|
+
` ${c.yellow}!${c.reset} Orchestrations bundle is missing in this CLI package`,
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.log(
|
|
632
|
+
` ${c.yellow}!${c.reset} API key saved, but orchestrations install failed: ${toErrorMessage(error)}`,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log(
|
|
637
|
+
` ${c.yellow}!${c.reset} Orchestrations install skipped ${c.dim}(--no-orchestrations)${c.reset}`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
console.log("");
|
|
641
|
+
console.log(` ${c.dim}You're all set. Your agent can now call the AOP API.${c.reset}`);
|
|
642
|
+
console.log("");
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
stopSpinner();
|
|
648
|
+
console.log(`\r ${c.red}✗${c.reset} Authorization timed out. Run setup again.`);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function resolveStorageTargets({
|
|
653
|
+
tokenPathOverride,
|
|
654
|
+
orchestrationsPathOverride,
|
|
655
|
+
}) {
|
|
656
|
+
if (tokenPathOverride || orchestrationsPathOverride) {
|
|
657
|
+
return {
|
|
658
|
+
tokenPath: tokenPathOverride || CWD_TOKEN_PATH,
|
|
659
|
+
orchestrationsPath:
|
|
660
|
+
orchestrationsPathOverride || CWD_ORCHESTRATIONS_PATH,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
665
|
+
return {
|
|
666
|
+
tokenPath: CWD_TOKEN_PATH,
|
|
667
|
+
orchestrationsPath: CWD_ORCHESTRATIONS_PATH,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return promptStorageTargets();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async function resolveOrchestrationsTarget({ orchestrationsPathOverride }) {
|
|
675
|
+
if (orchestrationsPathOverride) {
|
|
676
|
+
return orchestrationsPathOverride;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
680
|
+
return CWD_ORCHESTRATIONS_PATH;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return promptOrchestrationsTarget();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function promptStorageTargets() {
|
|
687
|
+
const rl = createInterface({
|
|
688
|
+
input: process.stdin,
|
|
689
|
+
output: process.stdout,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
console.log("");
|
|
694
|
+
console.log(` ${c.bold}Choose where to save files${c.reset}`);
|
|
695
|
+
console.log(
|
|
696
|
+
` ${c.white}token.json${c.reset}: API key used by agents/tools to call AOP API.`,
|
|
697
|
+
);
|
|
698
|
+
console.log(
|
|
699
|
+
` ${c.white}orchestrations/${c.reset}: starter orchestration files installed by setup.`,
|
|
700
|
+
);
|
|
701
|
+
console.log("");
|
|
702
|
+
console.log(` ${c.bold}1) Current directory (default)${c.reset}`);
|
|
703
|
+
console.log(
|
|
704
|
+
` ${c.cyan}token${c.reset}: ${c.white}${CWD_TOKEN_PATH}${c.reset}`,
|
|
705
|
+
);
|
|
706
|
+
console.log(
|
|
707
|
+
` ${c.cyan}orchestrations${c.reset}: ${c.white}${CWD_ORCHESTRATIONS_PATH}${c.reset}`,
|
|
708
|
+
);
|
|
709
|
+
console.log("");
|
|
710
|
+
console.log(` ${c.bold}2) Home directory${c.reset}`);
|
|
711
|
+
console.log(
|
|
712
|
+
` ${c.cyan}token${c.reset}: ${c.white}${HOME_TOKEN_PATH}${c.reset}`,
|
|
713
|
+
);
|
|
714
|
+
console.log(
|
|
715
|
+
` ${c.cyan}orchestrations${c.reset}: ${c.white}${HOME_ORCHESTRATIONS_PATH}${c.reset}`,
|
|
716
|
+
);
|
|
717
|
+
console.log("");
|
|
718
|
+
console.log(` ${c.bold}3) Custom paths${c.reset}`);
|
|
719
|
+
|
|
720
|
+
const answer = (
|
|
721
|
+
await rl.question(` Select ${c.bold}[1/2/3]${c.reset} (default ${c.bold}1${c.reset}): `)
|
|
722
|
+
)
|
|
723
|
+
.trim()
|
|
724
|
+
.toLowerCase();
|
|
725
|
+
|
|
726
|
+
if (answer === "2" || answer === "home") {
|
|
727
|
+
return {
|
|
728
|
+
tokenPath: HOME_TOKEN_PATH,
|
|
729
|
+
orchestrationsPath: HOME_ORCHESTRATIONS_PATH,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (answer === "3" || answer === "custom") {
|
|
734
|
+
const tokenInput = (
|
|
735
|
+
await rl.question(` Token path (default ${CWD_TOKEN_PATH}): `)
|
|
736
|
+
).trim();
|
|
737
|
+
const orchestrationsInput = (
|
|
738
|
+
await rl.question(
|
|
739
|
+
` Orchestrations path (default ${CWD_ORCHESTRATIONS_PATH}): `,
|
|
740
|
+
)
|
|
741
|
+
).trim();
|
|
742
|
+
|
|
743
|
+
return {
|
|
744
|
+
tokenPath: resolve(tokenInput || CWD_TOKEN_PATH),
|
|
745
|
+
orchestrationsPath: resolve(
|
|
746
|
+
orchestrationsInput || CWD_ORCHESTRATIONS_PATH,
|
|
747
|
+
),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return {
|
|
752
|
+
tokenPath: CWD_TOKEN_PATH,
|
|
753
|
+
orchestrationsPath: CWD_ORCHESTRATIONS_PATH,
|
|
754
|
+
};
|
|
755
|
+
} finally {
|
|
756
|
+
rl.close();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async function promptOrchestrationsTarget() {
|
|
761
|
+
const rl = createInterface({
|
|
762
|
+
input: process.stdin,
|
|
763
|
+
output: process.stdout,
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
console.log("");
|
|
768
|
+
console.log(` ${c.bold}Choose where to install orchestrations${c.reset}`);
|
|
769
|
+
console.log(
|
|
770
|
+
` ${c.white}orchestrations/${c.reset}: starter orchestration files your agent can use directly.`,
|
|
771
|
+
);
|
|
772
|
+
console.log("");
|
|
773
|
+
console.log(` ${c.bold}1) Current directory (default)${c.reset}`);
|
|
774
|
+
console.log(
|
|
775
|
+
` ${c.cyan}orchestrations${c.reset}: ${c.white}${CWD_ORCHESTRATIONS_PATH}${c.reset}`,
|
|
776
|
+
);
|
|
777
|
+
console.log("");
|
|
778
|
+
console.log(` ${c.bold}2) Home directory${c.reset}`);
|
|
779
|
+
console.log(
|
|
780
|
+
` ${c.cyan}orchestrations${c.reset}: ${c.white}${HOME_ORCHESTRATIONS_PATH}${c.reset}`,
|
|
781
|
+
);
|
|
782
|
+
console.log("");
|
|
783
|
+
console.log(` ${c.bold}3) Custom path${c.reset}`);
|
|
784
|
+
|
|
785
|
+
const answer = (
|
|
786
|
+
await rl.question(` Select ${c.bold}[1/2/3]${c.reset} (default ${c.bold}1${c.reset}): `)
|
|
787
|
+
)
|
|
788
|
+
.trim()
|
|
789
|
+
.toLowerCase();
|
|
790
|
+
|
|
791
|
+
if (answer === "2" || answer === "home") {
|
|
792
|
+
return HOME_ORCHESTRATIONS_PATH;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (answer === "3" || answer === "custom") {
|
|
796
|
+
const pathInput = (
|
|
797
|
+
await rl.question(
|
|
798
|
+
` Orchestrations path (default ${CWD_ORCHESTRATIONS_PATH}): `,
|
|
799
|
+
)
|
|
800
|
+
).trim();
|
|
801
|
+
return resolve(pathInput || CWD_ORCHESTRATIONS_PATH);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return CWD_ORCHESTRATIONS_PATH;
|
|
805
|
+
} finally {
|
|
806
|
+
rl.close();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function installBundledOrchestrations({ destinationPath, overwrite }) {
|
|
811
|
+
let sourceEntries;
|
|
812
|
+
try {
|
|
813
|
+
sourceEntries = await readdir(BUNDLED_ORCHESTRATIONS_PATH, {
|
|
814
|
+
withFileTypes: true,
|
|
815
|
+
});
|
|
816
|
+
} catch {
|
|
817
|
+
return { status: "missing_bundle", copiedCount: 0 };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (sourceEntries.length === 0) {
|
|
821
|
+
return { status: "missing_bundle", copiedCount: 0 };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
await mkdir(destinationPath, { recursive: true });
|
|
825
|
+
const existingEntries = await readdir(destinationPath, { withFileTypes: true });
|
|
826
|
+
const hasExistingEntries = existingEntries.length > 0;
|
|
827
|
+
|
|
828
|
+
if (hasExistingEntries && !overwrite) {
|
|
829
|
+
return { status: "skipped_exists", copiedCount: 0 };
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
let copiedCount = 0;
|
|
833
|
+
for (const entry of sourceEntries) {
|
|
834
|
+
await cp(
|
|
835
|
+
join(BUNDLED_ORCHESTRATIONS_PATH, entry.name),
|
|
836
|
+
join(destinationPath, entry.name),
|
|
837
|
+
{ recursive: true, force: true },
|
|
838
|
+
);
|
|
839
|
+
copiedCount += 1;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return {
|
|
843
|
+
status: hasExistingEntries ? "overwritten" : "installed",
|
|
844
|
+
copiedCount,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function saveToken(path, apiKey) {
|
|
849
|
+
await mkdir(dirname(path), { recursive: true });
|
|
850
|
+
await writeFile(path, JSON.stringify({ apiKey }, null, 2) + "\n");
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function sleep(ms) {
|
|
854
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function safeJson(response) {
|
|
858
|
+
try {
|
|
859
|
+
return await response.json();
|
|
860
|
+
} catch {
|
|
861
|
+
return {};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function toErrorMessage(error) {
|
|
866
|
+
if (error instanceof Error) return error.message;
|
|
867
|
+
return String(error);
|
|
868
|
+
}
|