lettokenburn-cli 0.1.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -1,326 +1,841 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { execFile } from 'node:child_process';
4
- import { createInterface } from 'node:readline/promises';
5
- import { stdin as input, stdout as output } from 'node:process';
6
- import { agentApi, CliApiError } from './api.js';
7
- import { loadConfig, resolveRuntimeOptions, saveConfig } from './config.js';
8
- import { printData } from './output.js';
9
- import { startRepl } from './repl.js';
10
- import { resolveContext } from './skills.js';
11
- import { startMcpServer } from './mcp.js';
12
- import { startAcpServer } from './acp.js';
13
- const program = new Command();
14
- program
15
- .name('ltb')
16
- .description('LetTokenBurn CLI for agents, tasks, skills, and MCP integration')
17
- .option('--base-url <url>', 'API base URL')
18
- .option('--api-key <key>', 'LTB agent API key')
19
- .option('--json', 'Output JSON');
20
- const auth = program
21
- .command('auth')
22
- .description('Manage CLI authentication');
23
- auth
24
- .command('login')
25
- .description('Store an API key in ~/.ltb/config.json')
26
- .option('--api-key <key>', 'LTB API key to store')
27
- .option('--browser', 'Open the browser to the agent console before prompting for the key')
28
- .action(async (options, command) => {
29
- const runtime = getRuntime(command);
30
- const mergedOptions = command.optsWithGlobals();
31
- if (options.browser) {
32
- const url = `${runtime.baseUrl}/agent`;
33
- await openBrowser(url);
34
- process.stdout.write(`Opened ${url}\n`);
35
- process.stdout.write('Create or copy an API key from the Agent Console, then paste it here.\n');
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { execFile } from "child_process";
6
+ import { createInterface as createInterface2 } from "readline/promises";
7
+ import { stdin as input2, stdout as output2 } from "process";
8
+
9
+ // src/config.ts
10
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "fs";
11
+ import { homedir } from "os";
12
+ import { dirname, join, resolve } from "path";
13
+ function getConfigPath() {
14
+ return process.env.LTB_CONFIG || join(homedir(), ".ltb", "config.json");
15
+ }
16
+ function loadConfig() {
17
+ const configPath = getConfigPath();
18
+ if (!existsSync(configPath)) return {};
19
+ try {
20
+ return JSON.parse(readFileSync(configPath, "utf8"));
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+ function saveConfig(config) {
26
+ const configPath = getConfigPath();
27
+ mkdirSync(dirname(configPath), { recursive: true });
28
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
29
+ }
30
+ function resolveRuntimeOptions(input3 = {}) {
31
+ const config = loadConfig();
32
+ return {
33
+ baseUrl: input3.baseUrl || process.env.LTB_BASE_URL || config.baseUrl || "https://api.lettokenburn.com",
34
+ apiKey: input3.apiKey || process.env.LTB_API_KEY || config.apiKey || "",
35
+ output: input3.output || process.env.LTB_OUTPUT || config.defaultOutput || "text",
36
+ projectDir: resolve(input3.projectDir || process.env.LTB_PROJECT_DIR || process.cwd())
37
+ };
38
+ }
39
+ function requireApiKey(baseUrl, apiKey) {
40
+ if (!apiKey) {
41
+ throw new Error(`Missing API key. Run "ltb auth login --api-key <ltb-...>" or set LTB_API_KEY for ${baseUrl}.`);
42
+ }
43
+ }
44
+
45
+ // src/api.ts
46
+ var CliApiError = class extends Error {
47
+ constructor(status, body) {
48
+ super(`API request failed with status ${status}`);
49
+ this.status = status;
50
+ this.body = body;
51
+ this.name = "CliApiError";
52
+ }
53
+ status;
54
+ body;
55
+ };
56
+ async function apiRequest(method, baseUrl, path, apiKey, body) {
57
+ requireApiKey(baseUrl, apiKey);
58
+ const response = await fetch(`${baseUrl}${path}`, {
59
+ method,
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ Authorization: `Bearer ${apiKey}`
63
+ },
64
+ body: body === void 0 ? void 0 : JSON.stringify(body)
65
+ });
66
+ const text = await response.text();
67
+ let payload = null;
68
+ try {
69
+ payload = text ? JSON.parse(text) : null;
70
+ } catch {
71
+ payload = text;
72
+ }
73
+ if (!response.ok) {
74
+ throw new CliApiError(response.status, payload);
75
+ }
76
+ return payload;
77
+ }
78
+ var agentApi = {
79
+ whoami(baseUrl, apiKey) {
80
+ return apiRequest("GET", baseUrl, "/v1/agent/me", apiKey);
81
+ },
82
+ health(baseUrl, apiKey) {
83
+ return apiRequest("GET", baseUrl, "/v1/agent/health", apiKey);
84
+ },
85
+ listTasks(baseUrl, apiKey, input3) {
86
+ const params = new URLSearchParams();
87
+ if (input3.module) params.set("module", input3.module);
88
+ if (input3.status) params.set("status", input3.status);
89
+ if (input3.page) params.set("page", String(input3.page));
90
+ if (input3.limit) params.set("limit", String(input3.limit));
91
+ const query = params.toString();
92
+ return apiRequest("GET", baseUrl, `/v1/agent/tasks${query ? `?${query}` : ""}`, apiKey);
93
+ },
94
+ claimTask(baseUrl, apiKey, taskId) {
95
+ return apiRequest("POST", baseUrl, `/v1/agent/tasks/${taskId}/claim`, apiKey);
96
+ },
97
+ claimStatus(baseUrl, apiKey, claimId) {
98
+ return apiRequest("GET", baseUrl, `/v1/agent/claims/${claimId}/status`, apiKey);
99
+ },
100
+ submitClaim(baseUrl, apiKey, claimId, input3) {
101
+ return apiRequest("POST", baseUrl, `/v1/agent/claims/${claimId}/submit`, apiKey, input3);
102
+ },
103
+ listEvents(baseUrl, apiKey, input3) {
104
+ const params = new URLSearchParams();
105
+ if (input3.status && input3.status !== "all") params.set("status", input3.status);
106
+ if (input3.limit) params.set("limit", String(input3.limit));
107
+ if (input3.cursor) params.set("cursor", input3.cursor);
108
+ const query = params.toString();
109
+ return apiRequest(
110
+ "GET",
111
+ baseUrl,
112
+ `/v1/agent/events${query ? `?${query}` : ""}`,
113
+ apiKey
114
+ );
115
+ },
116
+ listWebhooks(baseUrl, apiKey) {
117
+ return apiRequest("GET", baseUrl, "/v1/agent/webhooks", apiKey);
118
+ },
119
+ createWebhook(baseUrl, apiKey, input3) {
120
+ return apiRequest("POST", baseUrl, "/v1/agent/webhooks", apiKey, input3);
121
+ },
122
+ pauseWebhook(baseUrl, apiKey, webhookId) {
123
+ return apiRequest("POST", baseUrl, `/v1/agent/webhooks/${webhookId}/pause`, apiKey);
124
+ },
125
+ resumeWebhook(baseUrl, apiKey, webhookId) {
126
+ return apiRequest("POST", baseUrl, `/v1/agent/webhooks/${webhookId}/resume`, apiKey);
127
+ },
128
+ deleteWebhook(baseUrl, apiKey, webhookId) {
129
+ return apiRequest("DELETE", baseUrl, `/v1/agent/webhooks/${webhookId}`, apiKey);
130
+ },
131
+ retryEvent(baseUrl, apiKey, deliveryId) {
132
+ return apiRequest("POST", baseUrl, `/v1/agent/events/${deliveryId}/retry`, apiKey);
133
+ }
134
+ };
135
+
136
+ // src/output.ts
137
+ function printData(data, json) {
138
+ if (json) {
139
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
140
+ return;
141
+ }
142
+ if (Array.isArray(data)) {
143
+ if (data.length === 0) {
144
+ process.stdout.write("No results.\n");
145
+ return;
146
+ }
147
+ const rows = data.map((item) => normalizeRow(item));
148
+ const headers = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
149
+ const widths = headers.map((header) => Math.max(header.length, ...rows.map((row) => String(row[header] ?? "").length)));
150
+ process.stdout.write(
151
+ headers.map((header, index) => header.padEnd(widths[index])).join(" ") + "\n"
152
+ );
153
+ process.stdout.write(
154
+ headers.map((_, index) => "-".repeat(widths[index])).join(" ") + "\n"
155
+ );
156
+ rows.forEach((row) => {
157
+ process.stdout.write(
158
+ headers.map((header, index) => String(row[header] ?? "").padEnd(widths[index])).join(" ") + "\n"
159
+ );
160
+ });
161
+ return;
162
+ }
163
+ process.stdout.write(formatObject(data) + "\n");
164
+ }
165
+ function formatObject(data) {
166
+ if (data == null) return "null";
167
+ if (typeof data === "string") return data;
168
+ return JSON.stringify(data, null, 2);
169
+ }
170
+ function normalizeRow(value) {
171
+ if (!value || typeof value !== "object") return { value: String(value) };
172
+ const record = value;
173
+ const next = {};
174
+ for (const [key, field] of Object.entries(record)) {
175
+ if (field == null) {
176
+ next[key] = "";
177
+ } else if (typeof field === "object") {
178
+ next[key] = JSON.stringify(field);
179
+ } else {
180
+ next[key] = String(field);
181
+ }
182
+ }
183
+ return next;
184
+ }
185
+
186
+ // src/repl.ts
187
+ import { createInterface } from "readline/promises";
188
+ import { stdin as input, stdout as output } from "process";
189
+
190
+ // src/skills.ts
191
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
192
+ import { join as join2, resolve as resolve2 } from "path";
193
+ import { homedir as homedir2 } from "os";
194
+ function listSkills(projectDir) {
195
+ const projectSkills = join2(projectDir, ".ltb", "skills");
196
+ const globalSkills = join2(homedir2(), ".ltb", "skills");
197
+ return [
198
+ ...readSkillDir(projectSkills, "project"),
199
+ ...readSkillDir(globalSkills, "global")
200
+ ];
201
+ }
202
+ function readContextFiles(projectDir) {
203
+ const files = ["LTB.md", "AGENTS.md"].map((name) => ({ name, path: resolve2(projectDir, name) })).filter((entry) => existsSync2(entry.path)).map((entry) => ({
204
+ ...entry,
205
+ content: readFileSync2(entry.path, "utf8")
206
+ }));
207
+ return files;
208
+ }
209
+ function resolveContext(projectDir) {
210
+ return {
211
+ projectDir,
212
+ files: readContextFiles(projectDir),
213
+ skills: listSkills(projectDir)
214
+ };
215
+ }
216
+ function readSkillDir(baseDir, scope) {
217
+ if (!existsSync2(baseDir)) return [];
218
+ return readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => ({
219
+ scope,
220
+ name: entry.name,
221
+ path: join2(baseDir, entry.name, "SKILL.md")
222
+ })).filter((entry) => existsSync2(entry.path));
223
+ }
224
+
225
+ // src/repl.ts
226
+ async function startRepl(runtime) {
227
+ const rl = createInterface({ input, output });
228
+ process.stdout.write("LTB REPL. Commands: help, whoami, tasks, claim <id>, status <id>, events, skills, context, exit\n");
229
+ while (true) {
230
+ const line = (await rl.question("ltb> ")).trim();
231
+ if (!line) continue;
232
+ if (line === "exit" || line === "quit") break;
233
+ if (line === "help") {
234
+ process.stdout.write("whoami | tasks [module] [status] | claim <taskId> | status <claimId> | events | skills | context | exit\n");
235
+ continue;
236
+ }
237
+ const [command, arg1, arg2] = line.split(/\s+/);
238
+ try {
239
+ switch (command) {
240
+ case "whoami":
241
+ printData((await agentApi.whoami(runtime.baseUrl, runtime.apiKey)).data, false);
242
+ break;
243
+ case "tasks":
244
+ printData((await agentApi.listTasks(runtime.baseUrl, runtime.apiKey, {
245
+ module: arg1,
246
+ status: arg2,
247
+ limit: 10
248
+ })).data, false);
249
+ break;
250
+ case "claim":
251
+ if (!arg1) throw new Error("Usage: claim <taskId>");
252
+ printData((await agentApi.claimTask(runtime.baseUrl, runtime.apiKey, arg1)).data, false);
253
+ break;
254
+ case "status":
255
+ if (!arg1) throw new Error("Usage: status <claimId>");
256
+ printData((await agentApi.claimStatus(runtime.baseUrl, runtime.apiKey, arg1)).data, false);
257
+ break;
258
+ case "events":
259
+ printData((await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, { limit: 10 })).data, false);
260
+ break;
261
+ case "skills":
262
+ printData(resolveContext(runtime.projectDir).skills, false);
263
+ break;
264
+ case "context":
265
+ printData(resolveContext(runtime.projectDir), false);
266
+ break;
267
+ default:
268
+ process.stdout.write(`Unknown command: ${command}
269
+ `);
270
+ }
271
+ } catch (error) {
272
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
273
+ `);
36
274
  }
37
- const apiKey = options.apiKey || mergedOptions.apiKey || await prompt('LTB API key: ');
38
- const current = loadConfig();
39
- saveConfig({ ...current, baseUrl: runtime.baseUrl, apiKey });
40
- process.stdout.write(`Saved API key to config.\n`);
275
+ }
276
+ rl.close();
277
+ }
278
+
279
+ // src/mcp.ts
280
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
281
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
282
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
283
+ async function startMcpServer(runtime) {
284
+ const server = new Server(
285
+ { name: "ltb-cli", version: "0.1.0" },
286
+ { capabilities: { tools: {} } }
287
+ );
288
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
289
+ tools: [
290
+ {
291
+ name: "ltb_task_list",
292
+ description: "List LetTokenBurn tasks from the agent API",
293
+ inputSchema: {
294
+ type: "object",
295
+ properties: {
296
+ module: { type: "string" },
297
+ status: { type: "string" },
298
+ page: { type: "number" },
299
+ limit: { type: "number" }
300
+ }
301
+ }
302
+ },
303
+ {
304
+ name: "ltb_task_claim",
305
+ description: "Claim a LetTokenBurn task by id",
306
+ inputSchema: {
307
+ type: "object",
308
+ required: ["taskId"],
309
+ properties: {
310
+ taskId: { type: "string" }
311
+ }
312
+ }
313
+ },
314
+ {
315
+ name: "ltb_claim_status",
316
+ description: "Fetch structured claim status",
317
+ inputSchema: {
318
+ type: "object",
319
+ required: ["claimId"],
320
+ properties: {
321
+ claimId: { type: "string" }
322
+ }
323
+ }
324
+ },
325
+ {
326
+ name: "ltb_claim_submit",
327
+ description: "Submit a claim result",
328
+ inputSchema: {
329
+ type: "object",
330
+ required: ["claimId", "resultType", "content"],
331
+ properties: {
332
+ claimId: { type: "string" },
333
+ resultType: { type: "string" },
334
+ content: { type: "string" }
335
+ }
336
+ }
337
+ },
338
+ {
339
+ name: "ltb_events_list",
340
+ description: "List webhook delivery events for the agent key",
341
+ inputSchema: {
342
+ type: "object",
343
+ properties: {
344
+ status: { type: "string" },
345
+ limit: { type: "number" },
346
+ cursor: { type: "string" }
347
+ }
348
+ }
349
+ },
350
+ {
351
+ name: "ltb_skills_list",
352
+ description: "List project and global skills visible to the LTB CLI",
353
+ inputSchema: {
354
+ type: "object",
355
+ properties: {}
356
+ }
357
+ },
358
+ {
359
+ name: "ltb_context_show",
360
+ description: "Show project context files and discovered skills",
361
+ inputSchema: {
362
+ type: "object",
363
+ properties: {}
364
+ }
365
+ }
366
+ ]
367
+ }));
368
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
369
+ const args = request.params.arguments ?? {};
370
+ switch (request.params.name) {
371
+ case "ltb_task_list":
372
+ return asToolResult(await agentApi.listTasks(runtime.baseUrl, runtime.apiKey, {
373
+ module: stringArg(args.module),
374
+ status: stringArg(args.status),
375
+ page: numberArg(args.page),
376
+ limit: numberArg(args.limit)
377
+ }));
378
+ case "ltb_task_claim":
379
+ return asToolResult(await agentApi.claimTask(runtime.baseUrl, runtime.apiKey, stringRequired(args.taskId, "taskId")));
380
+ case "ltb_claim_status":
381
+ return asToolResult(await agentApi.claimStatus(runtime.baseUrl, runtime.apiKey, stringRequired(args.claimId, "claimId")));
382
+ case "ltb_claim_submit":
383
+ return asToolResult(await agentApi.submitClaim(runtime.baseUrl, runtime.apiKey, stringRequired(args.claimId, "claimId"), {
384
+ resultType: stringRequired(args.resultType, "resultType"),
385
+ content: stringRequired(args.content, "content")
386
+ }));
387
+ case "ltb_events_list":
388
+ return asToolResult(await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, {
389
+ status: stringArg(args.status),
390
+ limit: numberArg(args.limit),
391
+ cursor: stringArg(args.cursor)
392
+ }));
393
+ case "ltb_skills_list":
394
+ return asToolResult(resolveContext(runtime.projectDir).skills);
395
+ case "ltb_context_show":
396
+ return asToolResult(resolveContext(runtime.projectDir));
397
+ default:
398
+ throw new Error(`Unknown MCP tool: ${request.params.name}`);
399
+ }
400
+ });
401
+ const transport = new StdioServerTransport();
402
+ await server.connect(transport);
403
+ }
404
+ function asToolResult(value) {
405
+ return {
406
+ content: [
407
+ {
408
+ type: "text",
409
+ text: JSON.stringify(value, null, 2)
410
+ }
411
+ ]
412
+ };
413
+ }
414
+ function stringArg(value) {
415
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
416
+ }
417
+ function numberArg(value) {
418
+ if (typeof value === "number") return value;
419
+ if (typeof value === "string" && value.trim()) return Number(value);
420
+ return void 0;
421
+ }
422
+ function stringRequired(value, name) {
423
+ if (typeof value === "string" && value.trim()) return value.trim();
424
+ throw new Error(`Missing required argument: ${name}`);
425
+ }
426
+
427
+ // src/acp.ts
428
+ import * as acp from "@agentclientprotocol/sdk";
429
+ import { Readable, Writable } from "stream";
430
+ import { randomUUID } from "crypto";
431
+ async function startAcpServer(runtime) {
432
+ const input3 = Writable.toWeb(process.stdout);
433
+ const output3 = Readable.toWeb(process.stdin);
434
+ const stream = acp.ndJsonStream(input3, output3);
435
+ new acp.AgentSideConnection((conn) => new LtbAcpAgent(conn, runtime), stream);
436
+ }
437
+ var LtbAcpAgent = class {
438
+ constructor(connection, runtime) {
439
+ this.connection = connection;
440
+ this.runtime = runtime;
441
+ }
442
+ connection;
443
+ runtime;
444
+ sessions = /* @__PURE__ */ new Map();
445
+ async initialize() {
446
+ return {
447
+ protocolVersion: acp.PROTOCOL_VERSION,
448
+ agentInfo: {
449
+ name: "ltb-cli",
450
+ version: "0.1.0"
451
+ },
452
+ agentCapabilities: {
453
+ loadSession: false
454
+ }
455
+ };
456
+ }
457
+ async newSession() {
458
+ const sessionId = randomUUID();
459
+ this.sessions.set(sessionId, { mode: "ops", cancelled: false });
460
+ return {
461
+ sessionId,
462
+ modes: {
463
+ currentModeId: "ops",
464
+ availableModes: [
465
+ { id: "ops", name: "Operations", description: "Run LTB task, claim, event, and webhook operations." },
466
+ { id: "ask", name: "Ask", description: "Explain how to use the LTB CLI and agent workflow." }
467
+ ]
468
+ }
469
+ };
470
+ }
471
+ async authenticate() {
472
+ return {};
473
+ }
474
+ async setSessionMode(params) {
475
+ const session = this.requireSession(params.sessionId);
476
+ session.mode = params.modeId === "ask" ? "ask" : "ops";
477
+ return {};
478
+ }
479
+ async prompt(params) {
480
+ const session = this.requireSession(params.sessionId);
481
+ session.cancelled = false;
482
+ const text = params.prompt.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
483
+ const userMessageId = params.messageId ?? randomUUID();
484
+ const result = await this.handlePrompt(params.sessionId, text, session.mode);
485
+ if (session.cancelled) {
486
+ return { stopReason: "cancelled", userMessageId };
487
+ }
488
+ await this.connection.sessionUpdate({
489
+ sessionId: params.sessionId,
490
+ update: {
491
+ sessionUpdate: "agent_message_chunk",
492
+ content: {
493
+ type: "text",
494
+ text: result
495
+ },
496
+ messageId: randomUUID()
497
+ }
498
+ });
499
+ return {
500
+ stopReason: "end_turn",
501
+ userMessageId
502
+ };
503
+ }
504
+ async cancel(params) {
505
+ const session = this.sessions.get(params.sessionId);
506
+ if (session) {
507
+ session.cancelled = true;
508
+ }
509
+ }
510
+ async handlePrompt(sessionId, text, mode) {
511
+ if (!text) {
512
+ return this.helpText();
513
+ }
514
+ if (mode === "ask") {
515
+ return this.helpText();
516
+ }
517
+ const normalized = text.trim();
518
+ if (normalized === "help" || normalized === "/help") {
519
+ return this.helpText();
520
+ }
521
+ if (normalized === "/skills") {
522
+ return JSON.stringify(resolveContext(this.runtime.projectDir).skills, null, 2);
523
+ }
524
+ if (normalized === "/context") {
525
+ return JSON.stringify(resolveContext(this.runtime.projectDir), null, 2);
526
+ }
527
+ if (normalized.startsWith("/tasks")) {
528
+ const [, module, status] = normalized.split(/\s+/);
529
+ await this.sendToolCall(sessionId, "ltb_task_list", { module, status });
530
+ const result = await agentApi.listTasks(this.runtime.baseUrl, this.runtime.apiKey, { module, status, limit: 20 });
531
+ return JSON.stringify(result, null, 2);
532
+ }
533
+ if (normalized.startsWith("/claim ")) {
534
+ const [, taskId] = normalized.split(/\s+/);
535
+ if (!taskId) {
536
+ return "Usage: /claim <taskId>";
537
+ }
538
+ await this.sendToolCall(sessionId, "ltb_task_claim", { taskId });
539
+ const result = await agentApi.claimTask(this.runtime.baseUrl, this.runtime.apiKey, taskId);
540
+ return JSON.stringify(result, null, 2);
541
+ }
542
+ if (normalized.startsWith("/status ")) {
543
+ const [, claimId] = normalized.split(/\s+/);
544
+ if (!claimId) {
545
+ return "Usage: /status <claimId>";
546
+ }
547
+ await this.sendToolCall(sessionId, "ltb_claim_status", { claimId });
548
+ const result = await agentApi.claimStatus(this.runtime.baseUrl, this.runtime.apiKey, claimId);
549
+ return JSON.stringify(result, null, 2);
550
+ }
551
+ if (normalized.startsWith("/events")) {
552
+ const [, status] = normalized.split(/\s+/);
553
+ await this.sendToolCall(sessionId, "ltb_events_list", { status });
554
+ const result = await agentApi.listEvents(this.runtime.baseUrl, this.runtime.apiKey, { status, limit: 20 });
555
+ return JSON.stringify(result, null, 2);
556
+ }
557
+ if (normalized === "/webhooks") {
558
+ await this.sendToolCall(sessionId, "ltb_webhook_list", {});
559
+ const result = await agentApi.listWebhooks(this.runtime.baseUrl, this.runtime.apiKey);
560
+ return JSON.stringify(result, null, 2);
561
+ }
562
+ return this.helpText();
563
+ }
564
+ async sendToolCall(sessionId, title, rawInput) {
565
+ const toolCallId = randomUUID();
566
+ await this.connection.sessionUpdate({
567
+ sessionId,
568
+ update: {
569
+ sessionUpdate: "tool_call",
570
+ toolCallId,
571
+ title,
572
+ kind: "execute",
573
+ status: "completed",
574
+ rawInput,
575
+ rawOutput: { ok: true }
576
+ }
577
+ });
578
+ }
579
+ requireSession(sessionId) {
580
+ const session = this.sessions.get(sessionId);
581
+ if (!session) {
582
+ throw new Error(`Unknown ACP session: ${sessionId}`);
583
+ }
584
+ return session;
585
+ }
586
+ helpText() {
587
+ return [
588
+ "LTB ACP agent is command-oriented.",
589
+ "Supported prompts:",
590
+ "/tasks [module] [status]",
591
+ "/claim <taskId>",
592
+ "/status <claimId>",
593
+ "/events [status]",
594
+ "/webhooks",
595
+ "/skills",
596
+ "/context",
597
+ "/help"
598
+ ].join("\n");
599
+ }
600
+ };
601
+
602
+ // src/index.ts
603
+ var program = new Command();
604
+ program.name("ltb").description("LetTokenBurn CLI for agents, tasks, skills, and MCP integration").option("--base-url <url>", "API base URL").option("--api-key <key>", "LTB agent API key").option("--json", "Output JSON");
605
+ var auth = program.command("auth").description("Manage CLI authentication");
606
+ auth.command("login").description("Store an API key in ~/.ltb/config.json").option("--api-key <key>", "LTB API key to store").option("--browser", "Open the browser to the agent console before prompting for the key").action(async (options, command) => {
607
+ const runtime = getRuntime(command);
608
+ const mergedOptions = command.optsWithGlobals();
609
+ if (options.browser) {
610
+ const url = `${runtime.baseUrl}/agent`;
611
+ await openBrowser(url);
612
+ process.stdout.write(`Opened ${url}
613
+ `);
614
+ process.stdout.write("Create or copy an API key from the Agent Console, then paste it here.\n");
615
+ }
616
+ const apiKey = options.apiKey || mergedOptions.apiKey || await prompt("LTB API key: ");
617
+ const current = loadConfig();
618
+ saveConfig({ ...current, baseUrl: runtime.baseUrl, apiKey });
619
+ process.stdout.write(`Saved API key to config.
620
+ `);
41
621
  });
42
- const authWhoami = new Command('whoami')
43
- .description('Show current agent identity')
44
- .action(async (_opts, command) => {
45
- const runtime = getRuntime(command);
46
- const result = await agentApi.whoami(runtime.baseUrl, runtime.apiKey);
47
- printData(result.data, runtime.output === 'json');
622
+ var authWhoami = new Command("whoami").description("Show current agent identity").action(async (_opts, command) => {
623
+ const runtime = getRuntime(command);
624
+ const result = await agentApi.whoami(runtime.baseUrl, runtime.apiKey);
625
+ printData(result.data, runtime.output === "json");
48
626
  });
49
627
  auth.addCommand(authWhoami);
50
- const task = program.command('task').description('Task operations');
51
- task.command('list')
52
- .description('List agent-friendly tasks')
53
- .option('--module <module>')
54
- .option('--status <status>')
55
- .option('--page <page>', 'Page number', parseInt)
56
- .option('--limit <limit>', 'Page size', parseInt)
57
- .action(async (options, command) => {
58
- const runtime = getRuntime(command);
59
- const result = await agentApi.listTasks(runtime.baseUrl, runtime.apiKey, options);
60
- printData(runtime.output === 'json' ? result : result.data, runtime.output === 'json');
628
+ var task = program.command("task").description("Task operations");
629
+ task.command("list").description("List agent-friendly tasks").option("--module <module>").option("--status <status>").option("--page <page>", "Page number", parseInt).option("--limit <limit>", "Page size", parseInt).action(async (options, command) => {
630
+ const runtime = getRuntime(command);
631
+ const result = await agentApi.listTasks(runtime.baseUrl, runtime.apiKey, options);
632
+ printData(runtime.output === "json" ? result : result.data, runtime.output === "json");
61
633
  });
62
- task.command('claim')
63
- .description('Claim a task')
64
- .argument('<taskId>')
65
- .action(async (taskId, _options, command) => {
66
- const runtime = getRuntime(command);
67
- const result = await agentApi.claimTask(runtime.baseUrl, runtime.apiKey, taskId);
68
- printData(result.data, runtime.output === 'json');
634
+ task.command("claim").description("Claim a task").argument("<taskId>").action(async (taskId, _options, command) => {
635
+ const runtime = getRuntime(command);
636
+ const result = await agentApi.claimTask(runtime.baseUrl, runtime.apiKey, taskId);
637
+ printData(result.data, runtime.output === "json");
69
638
  });
70
- const claim = program.command('claim').description('Claim operations');
71
- claim.command('status')
72
- .description('Get structured claim status')
73
- .argument('<claimId>')
74
- .action(async (claimId, _options, command) => {
75
- const runtime = getRuntime(command);
76
- const result = await agentApi.claimStatus(runtime.baseUrl, runtime.apiKey, claimId);
77
- printData(result.data, runtime.output === 'json');
639
+ var claim = program.command("claim").description("Claim operations");
640
+ claim.command("status").description("Get structured claim status").argument("<claimId>").action(async (claimId, _options, command) => {
641
+ const runtime = getRuntime(command);
642
+ const result = await agentApi.claimStatus(runtime.baseUrl, runtime.apiKey, claimId);
643
+ printData(result.data, runtime.output === "json");
78
644
  });
79
- claim.command('submit')
80
- .description('Submit a claim result')
81
- .argument('<claimId>')
82
- .requiredOption('--result-type <resultType>')
83
- .requiredOption('--content <content>')
84
- .action(async (claimId, options, command) => {
85
- const runtime = getRuntime(command);
86
- const result = await agentApi.submitClaim(runtime.baseUrl, runtime.apiKey, claimId, {
87
- resultType: options.resultType,
88
- content: options.content,
89
- });
90
- printData(result.data, runtime.output === 'json');
645
+ claim.command("submit").description("Submit a claim result").argument("<claimId>").requiredOption("--result-type <resultType>").requiredOption("--content <content>").action(async (claimId, options, command) => {
646
+ const runtime = getRuntime(command);
647
+ const result = await agentApi.submitClaim(runtime.baseUrl, runtime.apiKey, claimId, {
648
+ resultType: options.resultType,
649
+ content: options.content
650
+ });
651
+ printData(result.data, runtime.output === "json");
91
652
  });
92
- const events = program.command('events').description('Webhook delivery and agent event polling');
93
- events.command('list')
94
- .description('List delivery events')
95
- .option('--status <status>', 'all|delivered|dead_letter')
96
- .option('--limit <limit>', 'Result size', parseInt)
97
- .option('--cursor <cursor>')
98
- .action(async (options, command) => {
99
- const runtime = getRuntime(command);
100
- const result = await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, options);
101
- printData(runtime.output === 'json' ? result : result.data, runtime.output === 'json');
653
+ var events = program.command("events").description("Webhook delivery and agent event polling");
654
+ events.command("list").description("List delivery events").option("--status <status>", "all|delivered|dead_letter").option("--limit <limit>", "Result size", parseInt).option("--cursor <cursor>").action(async (options, command) => {
655
+ const runtime = getRuntime(command);
656
+ const result = await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, options);
657
+ printData(runtime.output === "json" ? result : result.data, runtime.output === "json");
102
658
  });
103
- events.command('watch')
104
- .description('Poll delivery events continuously')
105
- .option('--status <status>', 'all|delivered|dead_letter', 'all')
106
- .option('--limit <limit>', 'Result size', parseInt, 10)
107
- .option('--interval <seconds>', 'Polling interval', parseInt, 15)
108
- .action(async (options, command) => {
109
- const runtime = getRuntime(command);
110
- const seen = new Set();
111
- process.stdout.write(`Watching events every ${options.interval}s\n`);
112
- while (true) {
113
- const result = await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, {
114
- status: options.status,
115
- limit: options.limit,
116
- });
117
- const items = result.data.filter((entry) => !entry.id || !seen.has(entry.id));
118
- for (const item of items) {
119
- if (item.id)
120
- seen.add(item.id);
121
- }
122
- if (items.length > 0) {
123
- printData(runtime.output === 'json' ? { data: items } : items, runtime.output === 'json');
124
- }
125
- await sleep(options.interval * 1000);
659
+ events.command("watch").description("Poll delivery events continuously").option("--status <status>", "all|delivered|dead_letter", "all").option("--limit <limit>", "Result size", parseInt, 10).option("--interval <seconds>", "Polling interval", parseInt, 15).action(async (options, command) => {
660
+ const runtime = getRuntime(command);
661
+ const seen = /* @__PURE__ */ new Set();
662
+ process.stdout.write(`Watching events every ${options.interval}s
663
+ `);
664
+ while (true) {
665
+ const result = await agentApi.listEvents(runtime.baseUrl, runtime.apiKey, {
666
+ status: options.status,
667
+ limit: options.limit
668
+ });
669
+ const items = result.data.filter((entry) => !entry.id || !seen.has(entry.id));
670
+ for (const item of items) {
671
+ if (item.id) seen.add(item.id);
126
672
  }
673
+ if (items.length > 0) {
674
+ printData(runtime.output === "json" ? { data: items } : items, runtime.output === "json");
675
+ }
676
+ await sleep(options.interval * 1e3);
677
+ }
127
678
  });
128
- events.command('retry')
129
- .description('Retry a failed or dead-letter delivery')
130
- .argument('<deliveryId>')
131
- .action(async (deliveryId, _options, command) => {
132
- const runtime = getRuntime(command);
133
- const result = await agentApi.retryEvent(runtime.baseUrl, runtime.apiKey, deliveryId);
134
- printData(result.data, runtime.output === 'json');
679
+ events.command("retry").description("Retry a failed or dead-letter delivery").argument("<deliveryId>").action(async (deliveryId, _options, command) => {
680
+ const runtime = getRuntime(command);
681
+ const result = await agentApi.retryEvent(runtime.baseUrl, runtime.apiKey, deliveryId);
682
+ printData(result.data, runtime.output === "json");
135
683
  });
136
- const webhook = program.command('webhook').description('Manage outbound webhook subscriptions');
137
- webhook.command('list')
138
- .description('List webhook subscriptions')
139
- .action(async (_options, command) => {
140
- const runtime = getRuntime(command);
141
- const result = await agentApi.listWebhooks(runtime.baseUrl, runtime.apiKey);
142
- printData(result.data, runtime.output === 'json');
684
+ var webhook = program.command("webhook").description("Manage outbound webhook subscriptions");
685
+ webhook.command("list").description("List webhook subscriptions").action(async (_options, command) => {
686
+ const runtime = getRuntime(command);
687
+ const result = await agentApi.listWebhooks(runtime.baseUrl, runtime.apiKey);
688
+ printData(result.data, runtime.output === "json");
143
689
  });
144
- webhook.command('create')
145
- .description('Create a webhook subscription')
146
- .requiredOption('--name <name>')
147
- .requiredOption('--target-url <url>')
148
- .option('--secret <secret>')
149
- .option('--bot-agent-id <id>')
150
- .option('--events <events>', 'Comma-separated events', 'claim.reviewed,appeal.resolved')
151
- .action(async (options, command) => {
152
- const runtime = getRuntime(command);
153
- const result = await agentApi.createWebhook(runtime.baseUrl, runtime.apiKey, {
154
- name: options.name,
155
- targetUrl: options.targetUrl,
156
- secret: options.secret ?? null,
157
- botAgentId: options.botAgentId ?? null,
158
- eventTypes: String(options.events).split(',').map((v) => v.trim()).filter(Boolean),
159
- });
160
- printData(result.data, runtime.output === 'json');
690
+ webhook.command("create").description("Create a webhook subscription").requiredOption("--name <name>").requiredOption("--target-url <url>").option("--secret <secret>").option("--bot-agent-id <id>").option("--events <events>", "Comma-separated events", "claim.reviewed,appeal.resolved").action(async (options, command) => {
691
+ const runtime = getRuntime(command);
692
+ const result = await agentApi.createWebhook(runtime.baseUrl, runtime.apiKey, {
693
+ name: options.name,
694
+ targetUrl: options.targetUrl,
695
+ secret: options.secret ?? null,
696
+ botAgentId: options.botAgentId ?? null,
697
+ eventTypes: String(options.events).split(",").map((v) => v.trim()).filter(Boolean)
698
+ });
699
+ printData(result.data, runtime.output === "json");
161
700
  });
162
- webhook.command('pause')
163
- .description('Pause a webhook subscription')
164
- .argument('<webhookId>')
165
- .action(async (webhookId, _options, command) => {
166
- const runtime = getRuntime(command);
167
- const result = await agentApi.pauseWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
168
- printData(result.data, runtime.output === 'json');
701
+ webhook.command("pause").description("Pause a webhook subscription").argument("<webhookId>").action(async (webhookId, _options, command) => {
702
+ const runtime = getRuntime(command);
703
+ const result = await agentApi.pauseWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
704
+ printData(result.data, runtime.output === "json");
169
705
  });
170
- webhook.command('resume')
171
- .description('Resume a paused webhook subscription')
172
- .argument('<webhookId>')
173
- .action(async (webhookId, _options, command) => {
174
- const runtime = getRuntime(command);
175
- const result = await agentApi.resumeWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
176
- printData(result.data, runtime.output === 'json');
706
+ webhook.command("resume").description("Resume a paused webhook subscription").argument("<webhookId>").action(async (webhookId, _options, command) => {
707
+ const runtime = getRuntime(command);
708
+ const result = await agentApi.resumeWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
709
+ printData(result.data, runtime.output === "json");
177
710
  });
178
- webhook.command('delete')
179
- .description('Delete a webhook subscription')
180
- .argument('<webhookId>')
181
- .action(async (webhookId, _options, command) => {
182
- const runtime = getRuntime(command);
183
- const result = await agentApi.deleteWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
184
- printData(result.data, runtime.output === 'json');
711
+ webhook.command("delete").description("Delete a webhook subscription").argument("<webhookId>").action(async (webhookId, _options, command) => {
712
+ const runtime = getRuntime(command);
713
+ const result = await agentApi.deleteWebhook(runtime.baseUrl, runtime.apiKey, webhookId);
714
+ printData(result.data, runtime.output === "json");
185
715
  });
186
- const skills = program.command('skills').description('Project and global skill discovery');
187
- skills.command('list')
188
- .description('List discovered skills')
189
- .action(async (_options, command) => {
190
- const runtime = getRuntime(command);
191
- printData(resolveContext(runtime.projectDir).skills, runtime.output === 'json');
716
+ var skills = program.command("skills").description("Project and global skill discovery");
717
+ skills.command("list").description("List discovered skills").action(async (_options, command) => {
718
+ const runtime = getRuntime(command);
719
+ printData(resolveContext(runtime.projectDir).skills, runtime.output === "json");
192
720
  });
193
- program.command('context')
194
- .description('Show resolved LTB project context')
195
- .action(async (_options, command) => {
196
- const runtime = getRuntime(command);
197
- printData(resolveContext(runtime.projectDir), runtime.output === 'json');
721
+ program.command("context").description("Show resolved LTB project context").action(async (_options, command) => {
722
+ const runtime = getRuntime(command);
723
+ printData(resolveContext(runtime.projectDir), runtime.output === "json");
198
724
  });
199
- program.command('doctor')
200
- .description('Run connectivity and integration health checks for the current API key')
201
- .action(async (_options, command) => {
202
- const runtime = getRuntime(command);
203
- const [manifest, actor, health] = await Promise.all([
204
- fetch(`${runtime.baseUrl}/v1/meta/agent-manifest`).then(async (response) => {
205
- if (!response.ok)
206
- throw new Error(`Manifest request failed with ${response.status}`);
207
- return response.json();
208
- }),
209
- agentApi.whoami(runtime.baseUrl, runtime.apiKey),
210
- agentApi.health(runtime.baseUrl, runtime.apiKey),
211
- ]);
212
- const context = resolveContext(runtime.projectDir);
213
- const payload = {
214
- baseUrl: runtime.baseUrl,
215
- actor: actor.data,
216
- manifest: {
217
- workflowCount: manifest?.data?.workflows?.length ?? 0,
218
- webhookEventCount: manifest?.data?.webhookEvents?.length ?? 0,
219
- scopes: manifest?.data?.auth?.scopes ?? [],
220
- },
221
- context: {
222
- hasLtbMd: context.files.some((file) => file.name === 'LTB.md'),
223
- skillCount: context.skills.length,
224
- },
225
- health: health.data,
226
- };
227
- printData(payload, runtime.output === 'json');
725
+ program.command("doctor").description("Run connectivity and integration health checks for the current API key").action(async (_options, command) => {
726
+ const runtime = getRuntime(command);
727
+ const [manifest, actor, health] = await Promise.all([
728
+ fetch(`${runtime.baseUrl}/v1/meta/agent-manifest`).then(async (response) => {
729
+ if (!response.ok) throw new Error(`Manifest request failed with ${response.status}`);
730
+ return response.json();
731
+ }),
732
+ agentApi.whoami(runtime.baseUrl, runtime.apiKey),
733
+ agentApi.health(runtime.baseUrl, runtime.apiKey)
734
+ ]);
735
+ const context = resolveContext(runtime.projectDir);
736
+ const payload = {
737
+ baseUrl: runtime.baseUrl,
738
+ actor: actor.data,
739
+ manifest: {
740
+ workflowCount: manifest?.data?.workflows?.length ?? 0,
741
+ webhookEventCount: manifest?.data?.webhookEvents?.length ?? 0,
742
+ scopes: manifest?.data?.auth?.scopes ?? []
743
+ },
744
+ context: {
745
+ hasLtbMd: context.files.some((file) => file.name === "LTB.md"),
746
+ skillCount: context.skills.length
747
+ },
748
+ health: health.data
749
+ };
750
+ printData(payload, runtime.output === "json");
228
751
  });
229
- program.command('repl')
230
- .description('Start a minimal interactive shell')
231
- .action(async (_options, command) => {
232
- const runtime = getRuntime(command);
233
- await startRepl(runtime);
752
+ program.command("repl").description("Start a minimal interactive shell").action(async (_options, command) => {
753
+ const runtime = getRuntime(command);
754
+ await startRepl(runtime);
234
755
  });
235
- const mcp = program.command('mcp').description('Run LTB as an MCP stdio server');
236
- mcp.command('serve')
237
- .description('Start MCP stdio server')
238
- .action(async (_options, command) => {
239
- const runtime = getRuntime(command);
240
- await startMcpServer(runtime);
756
+ var mcp = program.command("mcp").description("Run LTB as an MCP stdio server");
757
+ mcp.command("serve").description("Start MCP stdio server").action(async (_options, command) => {
758
+ const runtime = getRuntime(command);
759
+ await startMcpServer(runtime);
241
760
  });
242
- const acpCommand = program.command('acp').description('Run LTB as an ACP agent server');
243
- acpCommand.command('serve')
244
- .description('Start ACP ndjson server')
245
- .action(async (_options, command) => {
246
- const runtime = getRuntime(command);
247
- await startAcpServer(runtime);
761
+ var acpCommand = program.command("acp").description("Run LTB as an ACP agent server");
762
+ acpCommand.command("serve").description("Start ACP ndjson server").action(async (_options, command) => {
763
+ const runtime = getRuntime(command);
764
+ await startAcpServer(runtime);
248
765
  });
249
766
  program.configureOutput({
250
- outputError: (str, write) => write(str),
767
+ outputError: (str, write) => write(str)
251
768
  });
252
- program.hook('preAction', (_thisCommand, actionCommand) => {
253
- const commandPath = getCommandPath(actionCommand);
254
- const requiresApiKey = [
255
- 'ltb auth whoami',
256
- 'ltb task list',
257
- 'ltb task claim',
258
- 'ltb claim status',
259
- 'ltb claim submit',
260
- 'ltb events list',
261
- 'ltb events watch',
262
- 'ltb events retry',
263
- 'ltb webhook list',
264
- 'ltb webhook create',
265
- 'ltb webhook pause',
266
- 'ltb webhook resume',
267
- 'ltb webhook delete',
268
- 'ltb repl',
269
- 'ltb mcp serve',
270
- 'ltb acp serve',
271
- ].includes(commandPath);
272
- if (requiresApiKey) {
273
- const runtime = getRuntime(actionCommand);
274
- if (!runtime.apiKey) {
275
- throw new Error('Missing API key. Run "ltb auth login --api-key <ltb-...>" first.');
276
- }
769
+ program.hook("preAction", (_thisCommand, actionCommand) => {
770
+ const commandPath = getCommandPath(actionCommand);
771
+ const requiresApiKey = [
772
+ "ltb auth whoami",
773
+ "ltb task list",
774
+ "ltb task claim",
775
+ "ltb claim status",
776
+ "ltb claim submit",
777
+ "ltb events list",
778
+ "ltb events watch",
779
+ "ltb events retry",
780
+ "ltb webhook list",
781
+ "ltb webhook create",
782
+ "ltb webhook pause",
783
+ "ltb webhook resume",
784
+ "ltb webhook delete",
785
+ "ltb repl",
786
+ "ltb mcp serve",
787
+ "ltb acp serve"
788
+ ].includes(commandPath);
789
+ if (requiresApiKey) {
790
+ const runtime = getRuntime(actionCommand);
791
+ if (!runtime.apiKey) {
792
+ throw new Error('Missing API key. Run "ltb auth login --api-key <ltb-...>" first.');
277
793
  }
794
+ }
278
795
  });
279
796
  program.parseAsync(process.argv).catch((error) => {
280
- if (error instanceof CliApiError) {
281
- process.stderr.write(`${JSON.stringify(error.body, null, 2)}\n`);
282
- }
283
- else {
284
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
285
- }
286
- process.exit(1);
797
+ if (error instanceof CliApiError) {
798
+ process.stderr.write(`${JSON.stringify(error.body, null, 2)}
799
+ `);
800
+ } else {
801
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
802
+ `);
803
+ }
804
+ process.exit(1);
287
805
  });
288
806
  function getRuntime(command) {
289
- const opts = command.optsWithGlobals();
290
- return resolveRuntimeOptions({
291
- baseUrl: opts.baseUrl,
292
- apiKey: opts.apiKey,
293
- output: opts.json ? 'json' : undefined,
294
- });
807
+ const opts = command.optsWithGlobals();
808
+ return resolveRuntimeOptions({
809
+ baseUrl: opts.baseUrl,
810
+ apiKey: opts.apiKey,
811
+ output: opts.json ? "json" : void 0
812
+ });
295
813
  }
296
814
  async function prompt(promptText) {
297
- const rl = createInterface({ input, output });
298
- const value = await rl.question(promptText);
299
- rl.close();
300
- return value.trim();
815
+ const rl = createInterface2({ input: input2, output: output2 });
816
+ const value = await rl.question(promptText);
817
+ rl.close();
818
+ return value.trim();
301
819
  }
302
820
  function sleep(ms) {
303
- return new Promise((resolve) => setTimeout(resolve, ms));
821
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
304
822
  }
305
823
  function getCommandPath(command) {
306
- const names = [];
307
- let current = command;
308
- while (current) {
309
- names.unshift(current.name());
310
- current = current.parent ?? null;
311
- }
312
- return names.join(' ');
824
+ const names = [];
825
+ let current = command;
826
+ while (current) {
827
+ names.unshift(current.name());
828
+ current = current.parent ?? null;
829
+ }
830
+ return names.join(" ");
313
831
  }
314
832
  async function openBrowser(url) {
315
- const platform = process.platform;
316
- const command = platform === 'darwin' ? 'open' : 'xdg-open';
317
- await new Promise((resolve, reject) => {
318
- execFile(command, [url], (error) => {
319
- if (error)
320
- reject(error);
321
- else
322
- resolve();
323
- });
833
+ const platform = process.platform;
834
+ const command = platform === "darwin" ? "open" : "xdg-open";
835
+ await new Promise((resolve3, reject) => {
836
+ execFile(command, [url], (error) => {
837
+ if (error) reject(error);
838
+ else resolve3();
324
839
  });
840
+ });
325
841
  }
326
- //# sourceMappingURL=index.js.map