@ricsam/r5dctl 0.0.1 → 0.0.2

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.
@@ -0,0 +1,1246 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var cli_exports = {};
30
+ __export(cli_exports, {
31
+ getCliHelpText: () => getCliHelpText,
32
+ main: () => main,
33
+ parseAnswerFlags: () => parseAnswerFlags,
34
+ parseEnvFlags: () => parseEnvFlags,
35
+ parseGlobalArgs: () => parseGlobalArgs,
36
+ parsePromptArgs: () => parsePromptArgs,
37
+ resolveCommandExecution: () => resolveCommandExecution,
38
+ runBinctlCli: () => runBinctlCli
39
+ });
40
+ module.exports = __toCommonJS(cli_exports);
41
+ var import_node_fs = __toESM(require("node:fs"), 1);
42
+ var import_node_os = __toESM(require("node:os"), 1);
43
+ var import_node_path = __toESM(require("node:path"), 1);
44
+ var import_node_child_process = require("node:child_process");
45
+ var import_promises = require("node:timers/promises");
46
+ var import_r5d_api = require("@ricsam/r5d-api");
47
+ const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent", "explore", "review", "test", "research"]);
48
+ const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
49
+ const CLI_GLOBAL_OPTION_HELP = [
50
+ "--base-url <url>",
51
+ "--json",
52
+ "--config <path>",
53
+ "--token <token>",
54
+ "--api-key <key>",
55
+ "-p, --project <slug|id>",
56
+ "-b, --branch <branch>",
57
+ "-s, --session <sessionId>",
58
+ "-h, --help"
59
+ ];
60
+ const CLI_ONLY_HELP_ENTRIES = [
61
+ {
62
+ section: "auth",
63
+ usage: "auth login [--no-open]",
64
+ description: "Start the browser login flow."
65
+ }
66
+ ];
67
+ const CLI_ONLY_COMMAND_HELP = [
68
+ {
69
+ path: ["auth", "login"],
70
+ usage: "auth login [--no-open]"
71
+ }
72
+ ];
73
+ const SHARED_HELP_ENTRIES = [
74
+ { section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
75
+ { section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
76
+ { section: "auth", usage: "auth api-key create --name <name> [--save]", description: "Create an API key for automation or local scripts." },
77
+ { section: "auth", usage: "auth api-key list", description: "List API keys for the current account." },
78
+ { section: "auth", usage: "auth api-key revoke <key-id>", description: "Revoke an API key." },
79
+ { section: "projects", usage: "get projects", description: "List projects you can access." },
80
+ { section: "projects", usage: "create project [--name <name>] [--org <org-id>]", description: "Create a new project." },
81
+ { section: "projects", usage: "describe project <project-ref>", description: "Show details for a project." },
82
+ { section: "projects", usage: "update project <project-ref> [--name <name>] [--mode <greenfield|prod>]", description: "Update project settings." },
83
+ { section: "projects", usage: "delete project <project-ref>", description: "Delete a project." },
84
+ { section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
85
+ { section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
86
+ { section: "sessions", usage: "-p <project> get sessions [--branch <branch>]", description: "List sessions for a project, optionally filtered by branch." },
87
+ { section: "sessions", usage: "-p <project> create session -b <branch> [--name <name>]", description: "Create a session for a project branch." },
88
+ { section: "sessions", usage: "describe session <session-id>", description: "Show session details." },
89
+ { section: "sessions", usage: "-s <session-id> update session --name <name>", description: "Rename a session or clear its name." },
90
+ { section: "sessions", usage: "-s <session-id> delete session", description: "Delete a session." },
91
+ { section: "sessions", usage: "-s <session-id> conversation [--full] [--compact] [--tail <nodes>]", description: "Read a conversation transcript, pending questions, and required envs." },
92
+ { section: "sessions", usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"', description: "Send a prompt to a session." },
93
+ { section: "sessions", usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...', description: "Answer pending questions in order." },
94
+ { section: "sessions", usage: "-s <session-id> apply-required-envs -e backend/KEY=value -e frontend/KEY=value", description: "Apply required backend or frontend env values." }
95
+ ];
96
+ const HELP_SECTION_ORDER = ["auth", "projects", "branches", "sessions"];
97
+ const HELP_SECTION_TITLES = {
98
+ auth: "Auth",
99
+ projects: "Projects",
100
+ branches: "Branches",
101
+ sessions: "Sessions"
102
+ };
103
+ function getCliHelpText() {
104
+ const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
105
+ const usageWidth = Math.max(...entries.map((entry) => `r5dctl ${entry.usage}`.length), 0);
106
+ const lines = [
107
+ "Usage: r5dctl [global-options] <command>",
108
+ "Run `r5dctl <command> --help` for details on a specific command.",
109
+ "",
110
+ "Global options:",
111
+ ...CLI_GLOBAL_OPTION_HELP.map((option) => ` ${option}`)
112
+ ];
113
+ for (const section of HELP_SECTION_ORDER) {
114
+ const sectionEntries = entries.filter((entry) => entry.section === section);
115
+ if (sectionEntries.length === 0) {
116
+ continue;
117
+ }
118
+ lines.push("");
119
+ lines.push(`${HELP_SECTION_TITLES[section]}:`);
120
+ lines.push(
121
+ ...sectionEntries.map((entry) => {
122
+ const usage = `r5dctl ${entry.usage}`;
123
+ return ` ${usage.padEnd(usageWidth + 2, " ")}${entry.description}`;
124
+ })
125
+ );
126
+ }
127
+ return `${lines.join("\n")}
128
+ `;
129
+ }
130
+ function printHelp() {
131
+ process.stdout.write(getCliHelpText());
132
+ }
133
+ function isHelpFlag(arg) {
134
+ return arg === "--help" || arg === "-h";
135
+ }
136
+ function findCliOnlyCommandHelp(pathSegments) {
137
+ return CLI_ONLY_COMMAND_HELP.find(
138
+ (entry) => entry.path.length <= pathSegments.length && entry.path.every((segment, index) => segment === pathSegments[index])
139
+ );
140
+ }
141
+ function renderCliOnlyCommandHelp(entry) {
142
+ return `Usage:
143
+ r5dctl ${entry.usage}
144
+ `;
145
+ }
146
+ function renderSharedCommandHelp(pathSegments) {
147
+ const entry = SHARED_HELP_ENTRIES.find((candidate) => {
148
+ const usageSegments = candidate.usage.split(" ").filter((segment) => !segment.startsWith("-") && !segment.startsWith("<") && !segment.startsWith("["));
149
+ return usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index]);
150
+ });
151
+ if (!entry) {
152
+ return void 0;
153
+ }
154
+ return `Usage:
155
+ r5dctl ${entry.usage}
156
+
157
+ ${entry.description}
158
+ `;
159
+ }
160
+ function defaultConfigPath() {
161
+ return import_node_path.default.join(import_node_os.default.homedir(), ".config", "r5d", "r5dctl", "config.json");
162
+ }
163
+ function requireValue(value, message) {
164
+ if (!value) {
165
+ throw new Error(message);
166
+ }
167
+ return value;
168
+ }
169
+ function parseLongOptionWithEquals(arg, optionName) {
170
+ const prefix = `${optionName}=`;
171
+ if (!arg.startsWith(prefix)) {
172
+ return void 0;
173
+ }
174
+ return arg.slice(prefix.length);
175
+ }
176
+ function parseOptionalFlagValue(args, flag, shortFlag) {
177
+ for (let i = 0; i < args.length; i += 1) {
178
+ const arg = args[i];
179
+ if (!arg) {
180
+ continue;
181
+ }
182
+ if (arg === flag || shortFlag && arg === shortFlag) {
183
+ return args[i + 1];
184
+ }
185
+ const equalsValue = parseLongOptionWithEquals(arg, flag);
186
+ if (equalsValue !== void 0) {
187
+ return equalsValue;
188
+ }
189
+ }
190
+ return void 0;
191
+ }
192
+ function hasBooleanFlag(args, flag) {
193
+ return args.includes(flag);
194
+ }
195
+ function parseGlobalArgs(argv) {
196
+ const options = {
197
+ json: false,
198
+ configPath: defaultConfigPath(),
199
+ help: false
200
+ };
201
+ const rest = [];
202
+ for (let i = 0; i < argv.length; i += 1) {
203
+ const arg = argv[i];
204
+ if (!arg) {
205
+ continue;
206
+ }
207
+ if (arg === "--") {
208
+ rest.push(...argv.slice(i + 1));
209
+ break;
210
+ }
211
+ if (arg === "--json") {
212
+ options.json = true;
213
+ continue;
214
+ }
215
+ if (arg === "--help" || arg === "-h") {
216
+ if (rest.length === 0) {
217
+ options.help = true;
218
+ } else {
219
+ rest.push(arg);
220
+ }
221
+ continue;
222
+ }
223
+ if (arg === "--base-url") {
224
+ options.baseUrl = requireValue(argv[i + 1], "Missing value for --base-url");
225
+ i += 1;
226
+ continue;
227
+ }
228
+ const baseUrl = parseLongOptionWithEquals(arg, "--base-url");
229
+ if (baseUrl !== void 0) {
230
+ options.baseUrl = baseUrl;
231
+ continue;
232
+ }
233
+ if (arg === "--config") {
234
+ options.configPath = requireValue(argv[i + 1], "Missing value for --config");
235
+ i += 1;
236
+ continue;
237
+ }
238
+ const configPath = parseLongOptionWithEquals(arg, "--config");
239
+ if (configPath !== void 0) {
240
+ options.configPath = configPath;
241
+ continue;
242
+ }
243
+ if (arg === "--token") {
244
+ options.token = requireValue(argv[i + 1], "Missing value for --token");
245
+ i += 1;
246
+ continue;
247
+ }
248
+ const token = parseLongOptionWithEquals(arg, "--token");
249
+ if (token !== void 0) {
250
+ options.token = token;
251
+ continue;
252
+ }
253
+ if (arg === "--api-key") {
254
+ options.apiKey = requireValue(argv[i + 1], "Missing value for --api-key");
255
+ i += 1;
256
+ continue;
257
+ }
258
+ const apiKey = parseLongOptionWithEquals(arg, "--api-key");
259
+ if (apiKey !== void 0) {
260
+ options.apiKey = apiKey;
261
+ continue;
262
+ }
263
+ if (arg === "--project" || arg === "-p") {
264
+ options.project = requireValue(argv[i + 1], "Missing value for --project");
265
+ i += 1;
266
+ continue;
267
+ }
268
+ if (arg === "--branch" || arg === "-b") {
269
+ options.branch = requireValue(argv[i + 1], "Missing value for --branch");
270
+ i += 1;
271
+ continue;
272
+ }
273
+ if (arg === "--session" || arg === "-s") {
274
+ options.session = requireValue(argv[i + 1], "Missing value for --session");
275
+ i += 1;
276
+ continue;
277
+ }
278
+ rest.push(arg);
279
+ }
280
+ return { options, rest };
281
+ }
282
+ function parseConfig(configPath) {
283
+ if (!import_node_fs.default.existsSync(configPath)) {
284
+ return {};
285
+ }
286
+ const raw = import_node_fs.default.readFileSync(configPath, "utf-8");
287
+ if (raw.trim().length === 0) {
288
+ return {};
289
+ }
290
+ const parsed = JSON.parse(raw);
291
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
292
+ throw new Error(`Invalid config file format at ${configPath}`);
293
+ }
294
+ const value = parsed;
295
+ return {
296
+ baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : void 0,
297
+ token: typeof value.token === "string" ? value.token : void 0,
298
+ apiKey: typeof value.apiKey === "string" ? value.apiKey : void 0
299
+ };
300
+ }
301
+ function compactConfig(config) {
302
+ const output = {};
303
+ if (config.baseUrl) {
304
+ output.baseUrl = config.baseUrl;
305
+ }
306
+ if (config.token) {
307
+ output.token = config.token;
308
+ }
309
+ if (config.apiKey) {
310
+ output.apiKey = config.apiKey;
311
+ }
312
+ return output;
313
+ }
314
+ function writeConfig(configPath, config) {
315
+ const dir = import_node_path.default.dirname(configPath);
316
+ import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
317
+ const compacted = compactConfig(config);
318
+ import_node_fs.default.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
319
+ `, {
320
+ mode: 384
321
+ });
322
+ import_node_fs.default.chmodSync(configPath, 384);
323
+ }
324
+ function resolveClientOptions(options, config) {
325
+ return {
326
+ baseUrl: options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.BINCTL_BASE_URL ?? config.baseUrl,
327
+ token: options.token ?? process.env.R5D_TOKEN ?? process.env.BINCTL_TOKEN ?? config.token,
328
+ apiKey: options.apiKey ?? process.env.R5D_API_KEY ?? process.env.BINCTL_API_KEY ?? config.apiKey
329
+ };
330
+ }
331
+ function parseAnswerFlags(args) {
332
+ const answers = /* @__PURE__ */ new Map();
333
+ for (let i = 0; i < args.length; i += 1) {
334
+ const arg = args[i];
335
+ if (!arg) {
336
+ continue;
337
+ }
338
+ const inlineMatch = arg.match(/^-a(\d+)=(.*)$/);
339
+ if (inlineMatch) {
340
+ const position2 = Number(inlineMatch[1]);
341
+ const value2 = inlineMatch[2] ?? "";
342
+ answers.set(position2, value2);
343
+ continue;
344
+ }
345
+ const match = arg.match(/^-a(\d+)$/);
346
+ if (!match) {
347
+ if (arg.startsWith("-")) {
348
+ throw new Error(`Unknown answer flag: ${arg}`);
349
+ }
350
+ throw new Error(`Unexpected value '${arg}'. Use positional flags like -a1, -a2, ...`);
351
+ }
352
+ const position = Number(match[1]);
353
+ const value = args[i + 1];
354
+ if (value === void 0) {
355
+ throw new Error(`Missing value for ${arg}`);
356
+ }
357
+ answers.set(position, value);
358
+ i += 1;
359
+ }
360
+ if (answers.size === 0) {
361
+ throw new Error("Provide answers using positional flags like -a1, -a2, ...");
362
+ }
363
+ const ordered = Array.from(answers.entries()).sort(([left], [right]) => left - right);
364
+ for (let index = 0; index < ordered.length; index += 1) {
365
+ const expectedPosition = index + 1;
366
+ if (ordered[index]?.[0] !== expectedPosition) {
367
+ throw new Error("Answers must start at -a1 and be contiguous (-a1, -a2, ...)");
368
+ }
369
+ }
370
+ return ordered.map(([, value]) => value);
371
+ }
372
+ function validateEnvAssignment(value) {
373
+ const equalsIndex = value.indexOf("=");
374
+ if (equalsIndex <= 0) {
375
+ throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
376
+ }
377
+ const assignment = value.slice(0, equalsIndex);
378
+ const slashIndex = assignment.indexOf("/");
379
+ if (slashIndex <= 0) {
380
+ throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
381
+ }
382
+ const target = assignment.slice(0, slashIndex);
383
+ const key = assignment.slice(slashIndex + 1);
384
+ if (target !== "backend" && target !== "frontend" || key.length === 0) {
385
+ throw new Error(`Invalid env assignment '${value}'. Expected backend/KEY=value or frontend/KEY=value`);
386
+ }
387
+ }
388
+ function parseEnvFlags(args) {
389
+ const envs = [];
390
+ for (let i = 0; i < args.length; i += 1) {
391
+ const arg = args[i];
392
+ if (!arg) {
393
+ continue;
394
+ }
395
+ if (arg === "-e" || arg === "--env") {
396
+ const value = args[i + 1];
397
+ if (!value) {
398
+ throw new Error(`Missing value for ${arg}`);
399
+ }
400
+ validateEnvAssignment(value);
401
+ envs.push(value);
402
+ i += 1;
403
+ continue;
404
+ }
405
+ const inline = parseLongOptionWithEquals(arg, "--env");
406
+ if (inline !== void 0) {
407
+ validateEnvAssignment(inline);
408
+ envs.push(inline);
409
+ continue;
410
+ }
411
+ if (arg.startsWith("-")) {
412
+ throw new Error(`Unknown env flag: ${arg}`);
413
+ }
414
+ throw new Error(`Unexpected value '${arg}'. Use -e backend/KEY=value.`);
415
+ }
416
+ if (envs.length === 0) {
417
+ throw new Error("At least one -e/--env value is required.");
418
+ }
419
+ return envs;
420
+ }
421
+ function parsePromptArgs(args) {
422
+ let modeRaw;
423
+ let modelRaw;
424
+ const messageParts = [];
425
+ for (let i = 0; i < args.length; i += 1) {
426
+ const arg = args[i];
427
+ if (!arg) {
428
+ continue;
429
+ }
430
+ if (arg === "--") {
431
+ messageParts.push(...args.slice(i + 1));
432
+ break;
433
+ }
434
+ if (arg === "--mode") {
435
+ modeRaw = requireValue(args[i + 1], "Missing value for --mode");
436
+ i += 1;
437
+ continue;
438
+ }
439
+ const inlineMode = parseLongOptionWithEquals(arg, "--mode");
440
+ if (inlineMode !== void 0) {
441
+ modeRaw = inlineMode;
442
+ continue;
443
+ }
444
+ if (arg === "--model") {
445
+ modelRaw = requireValue(args[i + 1], "Missing value for --model");
446
+ i += 1;
447
+ continue;
448
+ }
449
+ const inlineModel = parseLongOptionWithEquals(arg, "--model");
450
+ if (inlineModel !== void 0) {
451
+ modelRaw = inlineModel;
452
+ continue;
453
+ }
454
+ if (arg.startsWith("-")) {
455
+ throw new Error(`Unknown prompt flag: ${arg}`);
456
+ }
457
+ messageParts.push(arg);
458
+ }
459
+ if (!modeRaw) {
460
+ throw new Error("--mode is required for `prompt`");
461
+ }
462
+ if (!CHAT_MODES.has(modeRaw)) {
463
+ throw new Error(`Invalid --mode '${modeRaw}'. Expected one of: ask, plan, build, agent, explore, review, test, research`);
464
+ }
465
+ if (!modelRaw) {
466
+ throw new Error("--model is required for `prompt`");
467
+ }
468
+ if (!MODEL_TIERS.has(modelRaw)) {
469
+ throw new Error("Invalid --model. Expected one of: low, medium, high, max");
470
+ }
471
+ const message = messageParts.join(" ").trim();
472
+ if (message.length === 0) {
473
+ throw new Error("Prompt message is required");
474
+ }
475
+ return {
476
+ mode: modeRaw,
477
+ model: modelRaw,
478
+ message
479
+ };
480
+ }
481
+ async function openInBrowser(url) {
482
+ if (process.platform === "darwin") {
483
+ (0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
484
+ return;
485
+ }
486
+ if (process.platform === "win32") {
487
+ (0, import_node_child_process.spawn)("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
488
+ return;
489
+ }
490
+ (0, import_node_child_process.spawn)("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
491
+ }
492
+ function writeDataOutput(json, data, humanText) {
493
+ if (json) {
494
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
495
+ `);
496
+ return;
497
+ }
498
+ process.stdout.write(humanText);
499
+ }
500
+ function createSavedApiKeyConfig(options, config, apiKey) {
501
+ return {
502
+ baseUrl: options.baseUrl ?? config.baseUrl,
503
+ apiKey,
504
+ token: void 0
505
+ };
506
+ }
507
+ async function handleAuthLogin(client, options, commandArgs, config) {
508
+ const noOpen = hasBooleanFlag(commandArgs, "--no-open");
509
+ const start = await client.auth.loginStart({
510
+ baseUrl: options.baseUrl ?? config.baseUrl
511
+ });
512
+ if (options.json) {
513
+ writeDataOutput(true, {
514
+ status: "pending",
515
+ requestId: start.requestId,
516
+ loginUrl: start.loginUrl,
517
+ expiresAt: start.expiresAt,
518
+ intervalMs: start.intervalMs
519
+ }, "");
520
+ } else {
521
+ process.stdout.write(`Open this URL to approve login:
522
+ ${start.loginUrl}
523
+ `);
524
+ }
525
+ if (!noOpen) {
526
+ try {
527
+ await openInBrowser(start.loginUrl);
528
+ } catch {
529
+ }
530
+ }
531
+ const expiresAtMs = new Date(start.expiresAt).getTime();
532
+ let intervalMs = start.intervalMs;
533
+ while (Date.now() < expiresAtMs) {
534
+ await (0, import_promises.setTimeout)(intervalMs);
535
+ const polled = await client.auth.loginPoll(start.requestId, { pollCode: start.pollCode });
536
+ if (polled.status === "pending") {
537
+ intervalMs = polled.intervalMs;
538
+ continue;
539
+ }
540
+ if (polled.status === "approved") {
541
+ const nextConfig = {
542
+ ...config,
543
+ baseUrl: options.baseUrl ?? config.baseUrl,
544
+ token: polled.token,
545
+ apiKey: void 0
546
+ };
547
+ writeConfig(options.configPath, nextConfig);
548
+ writeDataOutput(
549
+ options.json,
550
+ {
551
+ status: "approved",
552
+ credentialId: polled.credentialId,
553
+ savedConfigPath: options.configPath
554
+ },
555
+ "Login successful. Token saved to config.\n"
556
+ );
557
+ return;
558
+ }
559
+ if (polled.status === "expired") {
560
+ throw new Error("Login request expired. Run `r5dctl auth login` again.");
561
+ }
562
+ if (polled.status === "consumed") {
563
+ throw new Error("Login request was already consumed.");
564
+ }
565
+ }
566
+ throw new Error("Login request timed out.");
567
+ }
568
+ async function handleAuthApiKeyCreate(client, options, config, commandArgs) {
569
+ const name = parseOptionalFlagValue(commandArgs, "--name", "-n");
570
+ if (!name) {
571
+ throw new Error("Missing --name for API key creation");
572
+ }
573
+ const created = await client.auth.apiKeys.create({ name });
574
+ if (hasBooleanFlag(commandArgs, "--save")) {
575
+ writeConfig(options.configPath, createSavedApiKeyConfig(options, config, created.apiKey));
576
+ }
577
+ if (options.json) {
578
+ const payload = hasBooleanFlag(commandArgs, "--save") ? { ...created, saved: true, savedConfigPath: options.configPath } : created;
579
+ writeDataOutput(true, payload, "");
580
+ return;
581
+ }
582
+ const saveMessage = hasBooleanFlag(commandArgs, "--save") ? `Saved API key to ${options.configPath}
583
+ ` : "";
584
+ process.stdout.write(`API key created: ${created.id}
585
+ ${created.apiKey}
586
+ ${saveMessage}`);
587
+ }
588
+ function clearAuthFromConfig(config, options) {
589
+ return {
590
+ baseUrl: options.baseUrl ?? config.baseUrl,
591
+ token: void 0,
592
+ apiKey: void 0
593
+ };
594
+ }
595
+ function renderProjectDescription(project) {
596
+ return [`Project: ${project.name ?? project.id}`, `Ref: ${project.slug ?? project.id}`, `Mode: ${project.mode}`].join("\n") + "\n";
597
+ }
598
+ function renderProjectList(projects) {
599
+ if (projects.length === 0) {
600
+ return "No projects found.\n";
601
+ }
602
+ return `${projects.map((project) => `${project.name ?? "(unnamed project)"}
603
+ Ref: ${project.slug ?? project.id}
604
+ Mode: ${project.mode}`).join("\n\n")}
605
+ `;
606
+ }
607
+ function renderSessionList(sessions) {
608
+ if (sessions.length === 0) {
609
+ return "No sessions found.\n";
610
+ }
611
+ return `${sessions.map((session) => `${session.id} ${session.branchName} ${session.name ?? "(unnamed)"}`).join("\n")}
612
+ `;
613
+ }
614
+ function renderSessionDescription(session) {
615
+ return [`Session: ${session.id}`, `Project: ${session.projectName ?? session.projectId}`, `Branch: ${session.branchName}`].join("\n") + "\n";
616
+ }
617
+ function parseProjectCreateArgs(args) {
618
+ const input = {};
619
+ for (let index = 0; index < args.length; index += 1) {
620
+ const arg = args[index];
621
+ if (arg === "--name") {
622
+ input.name = requireValue(args[index + 1], "Missing value for --name");
623
+ index += 1;
624
+ continue;
625
+ }
626
+ const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
627
+ if (inlineName !== void 0) {
628
+ input.name = inlineName;
629
+ continue;
630
+ }
631
+ if (arg === "--org" || arg === "--org-id") {
632
+ input.orgId = requireValue(args[index + 1], `Missing value for ${arg}`);
633
+ index += 1;
634
+ continue;
635
+ }
636
+ const inlineOrg = arg ? parseLongOptionWithEquals(arg, "--org") ?? parseLongOptionWithEquals(arg, "--org-id") : void 0;
637
+ if (inlineOrg !== void 0) {
638
+ input.orgId = inlineOrg;
639
+ continue;
640
+ }
641
+ throw new Error(`Unknown project create argument: ${arg}`);
642
+ }
643
+ return input;
644
+ }
645
+ function parseProjectUpdateArgs(args) {
646
+ const input = {};
647
+ for (let index = 0; index < args.length; index += 1) {
648
+ const arg = args[index];
649
+ if (arg === "--name") {
650
+ input.name = requireValue(args[index + 1], "Missing value for --name");
651
+ index += 1;
652
+ continue;
653
+ }
654
+ const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
655
+ if (inlineName !== void 0) {
656
+ input.name = inlineName;
657
+ continue;
658
+ }
659
+ if (arg === "--mode") {
660
+ const mode = requireValue(args[index + 1], "Missing value for --mode");
661
+ if (mode !== "greenfield" && mode !== "prod") throw new Error("Invalid value for --mode: expected greenfield or prod");
662
+ input.mode = mode;
663
+ index += 1;
664
+ continue;
665
+ }
666
+ const inlineMode = arg ? parseLongOptionWithEquals(arg, "--mode") : void 0;
667
+ if (inlineMode !== void 0) {
668
+ if (inlineMode !== "greenfield" && inlineMode !== "prod") throw new Error("Invalid value for --mode: expected greenfield or prod");
669
+ input.mode = inlineMode;
670
+ continue;
671
+ }
672
+ throw new Error(`Unknown project update argument: ${arg}`);
673
+ }
674
+ if (Object.keys(input).length === 0) {
675
+ throw new Error("At least one project update flag is required");
676
+ }
677
+ return input;
678
+ }
679
+ function parseSessionCreateArgs(args) {
680
+ const branchName = requireValue(args[0], "Missing branch name");
681
+ const input = { branchName };
682
+ for (let index = 1; index < args.length; index += 1) {
683
+ const arg = args[index];
684
+ if (arg === "--name") {
685
+ input.name = requireValue(args[index + 1], "Missing value for --name");
686
+ index += 1;
687
+ continue;
688
+ }
689
+ const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
690
+ if (inlineName !== void 0) {
691
+ input.name = inlineName;
692
+ continue;
693
+ }
694
+ throw new Error(`Unknown session create argument: ${arg}`);
695
+ }
696
+ return input;
697
+ }
698
+ function parseSessionUpdateArgs(args) {
699
+ let hasName = false;
700
+ const input = { name: null };
701
+ for (let index = 0; index < args.length; index += 1) {
702
+ const arg = args[index];
703
+ if (arg === "--name") {
704
+ input.name = requireValue(args[index + 1], "Missing value for --name");
705
+ hasName = true;
706
+ index += 1;
707
+ continue;
708
+ }
709
+ const inlineName = arg ? parseLongOptionWithEquals(arg, "--name") : void 0;
710
+ if (inlineName !== void 0) {
711
+ input.name = inlineName;
712
+ hasName = true;
713
+ continue;
714
+ }
715
+ if (arg === "--clear-name") {
716
+ input.name = null;
717
+ hasName = true;
718
+ continue;
719
+ }
720
+ throw new Error(`Unknown session update argument: ${arg}`);
721
+ }
722
+ if (!hasName) {
723
+ throw new Error("Use --name <name> or --clear-name");
724
+ }
725
+ return input;
726
+ }
727
+ function parseConversationRenderArgs(args) {
728
+ const options = { full: false, compact: false };
729
+ for (let index = 0; index < args.length; index += 1) {
730
+ const arg = args[index];
731
+ if (arg === "--full" || arg === "--verbose") {
732
+ options.full = true;
733
+ continue;
734
+ }
735
+ if (arg === "--compact") {
736
+ options.compact = true;
737
+ continue;
738
+ }
739
+ if (arg === "--tail") {
740
+ const value = Number(requireValue(args[index + 1], "Missing value for --tail"));
741
+ if (!Number.isInteger(value) || value <= 0) throw new Error("--tail must be a positive integer");
742
+ options.tail = value;
743
+ index += 1;
744
+ continue;
745
+ }
746
+ const inlineTail = arg ? parseLongOptionWithEquals(arg, "--tail") : void 0;
747
+ if (inlineTail !== void 0) {
748
+ const value = Number(inlineTail);
749
+ if (!Number.isInteger(value) || value <= 0) throw new Error("--tail must be a positive integer");
750
+ options.tail = value;
751
+ continue;
752
+ }
753
+ throw new Error(`Unknown conversation argument: ${arg}`);
754
+ }
755
+ if (options.full && options.compact) {
756
+ throw new Error("Use either --full or --compact, not both");
757
+ }
758
+ return options;
759
+ }
760
+ function renderConversationResponse(read, options = { full: false, compact: false }) {
761
+ const thread = Array.isArray(read.thread) ? read.thread : [];
762
+ const visibleThread = options.tail ? thread.slice(-options.tail) : thread;
763
+ const lines = [`Session: ${read.session.id}`, `Project: ${read.session.projectName ?? read.session.projectId}`, `Branch: ${read.session.branchName}`, ""];
764
+ lines.push(`Conversation nodes: ${visibleThread.length}${options.tail ? ` of ${thread.length}` : ""}`);
765
+ if (!options.compact) {
766
+ lines.push(JSON.stringify(visibleThread, null, options.full ? 2 : 0));
767
+ }
768
+ if (read.pendingQuestions.length > 0) {
769
+ lines.push("", "Pending questions:");
770
+ read.pendingQuestions.forEach((question, index) => lines.push(`${index + 1}. ${question.question}`));
771
+ }
772
+ if (read.requiredEnvs.length > 0) {
773
+ lines.push("", "Required envs:");
774
+ read.requiredEnvs.forEach((env) => lines.push(`- ${env.target}/${env.name}${env.optional ? "?" : ""}${env.description ? `: ${env.description}` : ""}`));
775
+ }
776
+ return `${lines.join("\n")}
777
+ `;
778
+ }
779
+ async function executeBinctlCommand(client, json, args) {
780
+ const write = (data, human) => writeDataOutput(json, data, human);
781
+ const [first, second, third] = args;
782
+ if (first === "auth" && second === "status") {
783
+ const status = await client.auth.status();
784
+ write(status, `Authenticated as ${status.user.email} via ${status.source}.
785
+ `);
786
+ return;
787
+ }
788
+ if (first === "auth" && second === "logout") {
789
+ const response = await client.auth.logout();
790
+ write(response, response.revokedCredential ? "Logged out and revoked current credential.\n" : "Logged out.\n");
791
+ return;
792
+ }
793
+ if (first === "auth" && second === "api-key" && third === "list") {
794
+ const keys = await client.auth.apiKeys.list();
795
+ write(keys, keys.length === 0 ? "No API keys.\n" : `${keys.map((key) => `${key.id} ${key.name}`).join("\n")}
796
+ `);
797
+ return;
798
+ }
799
+ if (first === "auth" && second === "api-key" && third === "revoke") {
800
+ const result = await client.auth.apiKeys.revoke(requireValue(args[3], "Missing API key id"));
801
+ write(result, "API key revoked.\n");
802
+ return;
803
+ }
804
+ if (first === "projects" && second === "list" || first === "get" && second === "projects") {
805
+ const projects = await client.projects.list();
806
+ write(projects, renderProjectList(projects));
807
+ return;
808
+ }
809
+ if (first === "projects" && second === "create" || first === "create" && second === "project") {
810
+ const project = await client.projects.create(parseProjectCreateArgs(args.slice(2)));
811
+ write(project, renderProjectDescription(project));
812
+ return;
813
+ }
814
+ if (first === "projects" && second === "describe" || first === "describe" && second === "project") {
815
+ const project = await client.projects.describe(requireValue(args[2], "Missing project reference"));
816
+ write(project, renderProjectDescription(project));
817
+ return;
818
+ }
819
+ if (first === "projects" && second === "update" || first === "update" && second === "project") {
820
+ const projectRef = requireValue(args[2], "Missing project reference");
821
+ const project = await client.projects.update(projectRef, parseProjectUpdateArgs(args.slice(3)));
822
+ write(project, renderProjectDescription(project));
823
+ return;
824
+ }
825
+ if (first === "projects" && second === "delete" || first === "delete" && second === "project") {
826
+ const result = await client.projects.delete(requireValue(args[2], "Missing project reference"));
827
+ write(result, "Project deleted.\n");
828
+ return;
829
+ }
830
+ if (first === "projects" && second === "branches" && third === "list" || first === "get" && second === "branches") {
831
+ const projectRef = requireValue(first === "get" ? args[2] : args[3], "Missing project reference");
832
+ const branches = await client.projects.branches.list(projectRef);
833
+ write(branches, branches.length === 0 ? "No branches found.\n" : `${branches.map((branch) => `${branch.branchName} sessions=${branch.sessionCount}`).join("\n")}
834
+ `);
835
+ return;
836
+ }
837
+ if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
838
+ const offset = first === "describe" ? 2 : 3;
839
+ const result = await client.projects.branches.describe(requireValue(args[offset], "Missing project reference"), requireValue(args[offset + 1], "Missing branch name"));
840
+ write(result, `Project: ${result.project.name ?? result.project.id}
841
+ Branch: ${result.branchName}
842
+ Sessions: ${result.sessions.length}
843
+ `);
844
+ return;
845
+ }
846
+ if (first === "projects" && second === "sessions" && third === "list" || first === "get" && second === "sessions") {
847
+ const offset = first === "get" ? 2 : 3;
848
+ const projectRef = requireValue(args[offset], "Missing project reference");
849
+ const branch = parseOptionalFlagValue(args.slice(offset + 1), "--branch");
850
+ const sessions = await client.projects.sessions.list(projectRef, { branch });
851
+ write(sessions, renderSessionList(sessions));
852
+ return;
853
+ }
854
+ if (first === "projects" && second === "sessions" && third === "create" || first === "create" && second === "session") {
855
+ const offset = first === "create" ? 2 : 3;
856
+ const session = await client.projects.sessions.create(requireValue(args[offset], "Missing project reference"), parseSessionCreateArgs(args.slice(offset + 1)));
857
+ write(session, renderSessionDescription(session));
858
+ return;
859
+ }
860
+ if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
861
+ const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
862
+ write(session, renderSessionDescription(session));
863
+ return;
864
+ }
865
+ if (first === "sessions" && second === "update" || first === "update" && second === "session") {
866
+ const session = await client.sessions.update(requireValue(args[2], "Missing session id"), parseSessionUpdateArgs(args.slice(3)));
867
+ write(session, renderSessionDescription(session));
868
+ return;
869
+ }
870
+ if (first === "sessions" && second === "delete" || first === "delete" && second === "session") {
871
+ const result = await client.sessions.delete(requireValue(args[2], "Missing session id"));
872
+ write(result, "Session deleted.\n");
873
+ return;
874
+ }
875
+ if (first === "sessions" && second === "conversation" || first === "conversation") {
876
+ const offset = first === "conversation" ? 1 : 2;
877
+ const read = await client.sessions.conversation(requireValue(args[offset], "Missing session id"));
878
+ write(read, renderConversationResponse(read, parseConversationRenderArgs(args.slice(offset + 1))));
879
+ return;
880
+ }
881
+ if (first === "sessions" && second === "prompt" || first === "prompt") {
882
+ const offset = first === "prompt" ? 1 : 2;
883
+ const read = await client.sessions.prompt(requireValue(args[offset], "Missing session id"), {
884
+ mode: requireValue(args[offset + 1], "Missing mode"),
885
+ model: requireValue(args[offset + 2], "Missing model"),
886
+ message: requireValue(args[offset + 3], "Missing prompt message")
887
+ });
888
+ write(read, renderConversationResponse(read));
889
+ return;
890
+ }
891
+ if (first === "sessions" && second === "answer-questions" || first === "answer-questions") {
892
+ const offset = first === "answer-questions" ? 1 : 2;
893
+ const answers = args.slice(offset + 1);
894
+ if (answers.length === 0) throw new Error("At least one answer is required");
895
+ const read = await client.sessions.answerQuestions(requireValue(args[offset], "Missing session id"), { answers });
896
+ write(read, renderConversationResponse(read));
897
+ return;
898
+ }
899
+ if (first === "sessions" && second === "apply-required-envs" || first === "apply-required-envs") {
900
+ const offset = first === "apply-required-envs" ? 1 : 2;
901
+ const envs = args.slice(offset + 1);
902
+ if (envs.length === 0) throw new Error("At least one env assignment is required");
903
+ const read = await client.sessions.applyRequiredEnvs(requireValue(args[offset], "Missing session id"), { envs });
904
+ write(read, renderConversationResponse(read));
905
+ return;
906
+ }
907
+ throw new Error(`Unknown command: ${args.join(" ")}`);
908
+ }
909
+ function resolveCommandExecution(options, rest) {
910
+ const trailingHelp = isHelpFlag(rest.at(-1));
911
+ const normalizedRest = trailingHelp ? rest.slice(0, -1) : rest;
912
+ const command = normalizedRest[0];
913
+ if (!command) {
914
+ throw new Error("Missing command");
915
+ }
916
+ const commandArgs = normalizedRest.slice(1);
917
+ if (trailingHelp) {
918
+ const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
919
+ if (cliOnlyHelp) {
920
+ return {
921
+ kind: "cli-help",
922
+ text: renderCliOnlyCommandHelp(cliOnlyHelp)
923
+ };
924
+ }
925
+ const sharedHelp = renderSharedCommandHelp(normalizedRest);
926
+ if (sharedHelp) {
927
+ return {
928
+ kind: "cli-help",
929
+ text: sharedHelp
930
+ };
931
+ }
932
+ return {
933
+ kind: "plugin",
934
+ pluginArgs: [...normalizedRest, "--help"]
935
+ };
936
+ }
937
+ if (command === "projects" || command === "sessions") {
938
+ return {
939
+ kind: "plugin",
940
+ pluginArgs: normalizedRest
941
+ };
942
+ }
943
+ if (command === "auth") {
944
+ const subcommand = commandArgs[0];
945
+ const subArgs = commandArgs.slice(1);
946
+ if (subcommand === "login") {
947
+ return {
948
+ kind: "auth-login",
949
+ commandArgs: subArgs
950
+ };
951
+ }
952
+ if (subcommand === "status") {
953
+ return {
954
+ kind: "plugin",
955
+ pluginArgs: ["auth", "status"]
956
+ };
957
+ }
958
+ if (subcommand === "logout") {
959
+ return {
960
+ kind: "plugin",
961
+ pluginArgs: ["auth", "logout"],
962
+ clearAuthOnSuccess: true
963
+ };
964
+ }
965
+ if (subcommand === "api-key") {
966
+ const apiKeyCommand = subArgs[0];
967
+ const apiKeyArgs = subArgs.slice(1);
968
+ if (apiKeyCommand === "create") {
969
+ return {
970
+ kind: "auth-api-key-create",
971
+ commandArgs: apiKeyArgs
972
+ };
973
+ }
974
+ if (apiKeyCommand === "list") {
975
+ return {
976
+ kind: "plugin",
977
+ pluginArgs: ["auth", "api-key", "list"]
978
+ };
979
+ }
980
+ if (apiKeyCommand === "revoke") {
981
+ return {
982
+ kind: "plugin",
983
+ pluginArgs: ["auth", "api-key", "revoke", requireValue(apiKeyArgs[0], "Missing API key id")]
984
+ };
985
+ }
986
+ throw new Error("Unknown auth api-key command");
987
+ }
988
+ throw new Error("Unknown auth command");
989
+ }
990
+ if (command === "get") {
991
+ const target = commandArgs[0];
992
+ if (target === "projects") {
993
+ return {
994
+ kind: "plugin",
995
+ pluginArgs: ["get", "projects"]
996
+ };
997
+ }
998
+ if (target === "branches") {
999
+ if (!options.project) {
1000
+ throw new Error("--project/-p is required for `get branches`");
1001
+ }
1002
+ return {
1003
+ kind: "plugin",
1004
+ pluginArgs: ["get", "branches", options.project]
1005
+ };
1006
+ }
1007
+ if (target === "sessions") {
1008
+ if (!options.project) {
1009
+ throw new Error("--project/-p is required for `get sessions`");
1010
+ }
1011
+ const pluginArgs = ["get", "sessions", options.project];
1012
+ if (options.branch) {
1013
+ pluginArgs.push("--branch", options.branch);
1014
+ }
1015
+ return {
1016
+ kind: "plugin",
1017
+ pluginArgs
1018
+ };
1019
+ }
1020
+ throw new Error("Unknown get command");
1021
+ }
1022
+ if (command === "describe") {
1023
+ const target = commandArgs[0];
1024
+ if (target === "project") {
1025
+ const projectRef = commandArgs[1] ?? options.project;
1026
+ if (!projectRef) {
1027
+ throw new Error("Project reference is required for `describe project`");
1028
+ }
1029
+ return {
1030
+ kind: "plugin",
1031
+ pluginArgs: ["describe", "project", projectRef]
1032
+ };
1033
+ }
1034
+ if (target === "branch") {
1035
+ if (!options.project) {
1036
+ throw new Error("--project/-p is required for `describe branch`");
1037
+ }
1038
+ const branch = commandArgs[1] ?? options.branch;
1039
+ if (!branch) {
1040
+ throw new Error("Branch name is required for `describe branch`");
1041
+ }
1042
+ return {
1043
+ kind: "plugin",
1044
+ pluginArgs: ["describe", "branch", options.project, branch]
1045
+ };
1046
+ }
1047
+ if (target === "session") {
1048
+ const sessionId = commandArgs[1] ?? options.session;
1049
+ if (!sessionId) {
1050
+ throw new Error("Session id is required for `describe session`");
1051
+ }
1052
+ return {
1053
+ kind: "plugin",
1054
+ pluginArgs: ["describe", "session", sessionId]
1055
+ };
1056
+ }
1057
+ throw new Error("Unknown describe command");
1058
+ }
1059
+ if (command === "create") {
1060
+ const target = commandArgs[0];
1061
+ if (target === "project") {
1062
+ return {
1063
+ kind: "plugin",
1064
+ pluginArgs: ["create", "project", ...commandArgs.slice(1)]
1065
+ };
1066
+ }
1067
+ if (target === "session") {
1068
+ const rawArgs = commandArgs.slice(1);
1069
+ const firstFlagIndex = rawArgs.findIndex((arg) => arg.startsWith("-"));
1070
+ const positionalArgs = firstFlagIndex === -1 ? rawArgs : rawArgs.slice(0, firstFlagIndex);
1071
+ const flagArgs = firstFlagIndex === -1 ? [] : rawArgs.slice(firstFlagIndex);
1072
+ const projectRef = positionalArgs[0] ?? options.project;
1073
+ if (!projectRef) {
1074
+ throw new Error("Project reference is required for `create session`");
1075
+ }
1076
+ const branch = positionalArgs[1] ?? options.branch;
1077
+ if (!branch) {
1078
+ throw new Error("Branch name is required for `create session`");
1079
+ }
1080
+ return {
1081
+ kind: "plugin",
1082
+ pluginArgs: ["create", "session", projectRef, branch, ...flagArgs]
1083
+ };
1084
+ }
1085
+ throw new Error("Unknown create command");
1086
+ }
1087
+ if (command === "update") {
1088
+ const target = commandArgs[0];
1089
+ if (target === "project") {
1090
+ const projectRef = commandArgs[1] && !commandArgs[1].startsWith("-") ? commandArgs[1] : options.project;
1091
+ if (!projectRef) {
1092
+ throw new Error("Project reference is required for `update project`");
1093
+ }
1094
+ const remainingArgs = commandArgs[1] && !commandArgs[1].startsWith("-") ? commandArgs.slice(2) : commandArgs.slice(1);
1095
+ return {
1096
+ kind: "plugin",
1097
+ pluginArgs: ["update", "project", projectRef, ...remainingArgs]
1098
+ };
1099
+ }
1100
+ if (target === "session") {
1101
+ const sessionId = commandArgs[1] && !commandArgs[1].startsWith("-") ? commandArgs[1] : options.session;
1102
+ if (!sessionId) {
1103
+ throw new Error("Session id is required for `update session`");
1104
+ }
1105
+ const remainingArgs = commandArgs[1] && !commandArgs[1].startsWith("-") ? commandArgs.slice(2) : commandArgs.slice(1);
1106
+ return {
1107
+ kind: "plugin",
1108
+ pluginArgs: ["update", "session", sessionId, ...remainingArgs]
1109
+ };
1110
+ }
1111
+ throw new Error("Unknown update command");
1112
+ }
1113
+ if (command === "delete") {
1114
+ const target = commandArgs[0];
1115
+ if (target === "project") {
1116
+ const projectRef = commandArgs[1] ?? options.project;
1117
+ if (!projectRef) {
1118
+ throw new Error("Project reference is required for `delete project`");
1119
+ }
1120
+ return {
1121
+ kind: "plugin",
1122
+ pluginArgs: ["delete", "project", projectRef]
1123
+ };
1124
+ }
1125
+ if (target === "session") {
1126
+ const sessionId = commandArgs[1] ?? options.session;
1127
+ if (!sessionId) {
1128
+ throw new Error("Session id is required for `delete session`");
1129
+ }
1130
+ return {
1131
+ kind: "plugin",
1132
+ pluginArgs: ["delete", "session", sessionId]
1133
+ };
1134
+ }
1135
+ throw new Error("Unknown delete command");
1136
+ }
1137
+ if (command === "conversation") {
1138
+ const sessionId = options.session;
1139
+ if (!sessionId) {
1140
+ throw new Error("--session/-s is required for `conversation`");
1141
+ }
1142
+ return {
1143
+ kind: "plugin",
1144
+ pluginArgs: ["conversation", sessionId, ...commandArgs]
1145
+ };
1146
+ }
1147
+ if (command === "prompt") {
1148
+ const sessionId = options.session;
1149
+ if (!sessionId) {
1150
+ throw new Error("--session/-s is required for `prompt`");
1151
+ }
1152
+ const parsed = parsePromptArgs(commandArgs);
1153
+ return {
1154
+ kind: "plugin",
1155
+ pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
1156
+ };
1157
+ }
1158
+ if (command === "answer-questions") {
1159
+ const sessionId = options.session;
1160
+ if (!sessionId) {
1161
+ throw new Error("--session/-s is required for `answer-questions`");
1162
+ }
1163
+ return {
1164
+ kind: "plugin",
1165
+ pluginArgs: ["answer-questions", sessionId, ...parseAnswerFlags(commandArgs)]
1166
+ };
1167
+ }
1168
+ if (command === "apply-required-envs") {
1169
+ const sessionId = options.session;
1170
+ if (!sessionId) {
1171
+ throw new Error("--session/-s is required for `apply-required-envs`");
1172
+ }
1173
+ return {
1174
+ kind: "plugin",
1175
+ pluginArgs: ["apply-required-envs", sessionId, ...parseEnvFlags(commandArgs)]
1176
+ };
1177
+ }
1178
+ throw new Error(`Unknown command: ${command}`);
1179
+ }
1180
+ async function runCommand(argv) {
1181
+ const { options, rest } = parseGlobalArgs(argv);
1182
+ if (options.help || rest.length === 0) {
1183
+ printHelp();
1184
+ return;
1185
+ }
1186
+ const config = parseConfig(options.configPath);
1187
+ const client = new import_r5d_api.BinctlClient(resolveClientOptions(options, config));
1188
+ const execution = resolveCommandExecution(options, rest);
1189
+ if (execution.kind === "auth-login") {
1190
+ await handleAuthLogin(client, options, execution.commandArgs, config);
1191
+ return;
1192
+ }
1193
+ if (execution.kind === "auth-api-key-create") {
1194
+ await handleAuthApiKeyCreate(client, options, config, execution.commandArgs);
1195
+ return;
1196
+ }
1197
+ if (execution.kind === "cli-help") {
1198
+ process.stdout.write(execution.text);
1199
+ return;
1200
+ }
1201
+ await executeBinctlCommand(client, options.json, execution.pluginArgs);
1202
+ if (execution.clearAuthOnSuccess) {
1203
+ writeConfig(options.configPath, clearAuthFromConfig(config, options));
1204
+ }
1205
+ }
1206
+ function extractApiErrorMessage(error) {
1207
+ if (typeof error.body === "object" && error.body !== null) {
1208
+ const body = error.body;
1209
+ if (typeof body.error === "string" && body.error.trim().length > 0) {
1210
+ return body.error;
1211
+ }
1212
+ }
1213
+ return error.message;
1214
+ }
1215
+ async function runBinctlCli(argv) {
1216
+ try {
1217
+ await runCommand(argv);
1218
+ return 0;
1219
+ } catch (error) {
1220
+ if (error instanceof import_r5d_api.BinctlApiError) {
1221
+ process.stderr.write(`${extractApiErrorMessage(error)}
1222
+ `);
1223
+ return 1;
1224
+ }
1225
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
1226
+ `);
1227
+ return 1;
1228
+ }
1229
+ }
1230
+ async function main(argv = process.argv.slice(2)) {
1231
+ const exitCode = await runBinctlCli(argv);
1232
+ if (exitCode !== 0) {
1233
+ process.exit(exitCode);
1234
+ }
1235
+ }
1236
+ // Annotate the CommonJS export names for ESM import in node:
1237
+ 0 && (module.exports = {
1238
+ getCliHelpText,
1239
+ main,
1240
+ parseAnswerFlags,
1241
+ parseEnvFlags,
1242
+ parseGlobalArgs,
1243
+ parsePromptArgs,
1244
+ resolveCommandExecution,
1245
+ runBinctlCli
1246
+ });