@devboxer/cli 0.1.21

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.
Files changed (3) hide show
  1. package/README.md +157 -0
  2. package/dist/index.js +1214 -0
  3. package/package.json +72 -0
package/dist/index.js ADDED
@@ -0,0 +1,1214 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.tsx
4
+ import { program } from "commander";
5
+ import { render } from "ink";
6
+ import React9 from "react";
7
+ import { readFile } from "fs/promises";
8
+ import { fileURLToPath as fileURLToPath2 } from "url";
9
+ import { dirname as dirname2, join as join4 } from "path";
10
+
11
+ // src/commands/auth.tsx
12
+ import React, { useState, useEffect } from "react";
13
+ import { Box, Text } from "ink";
14
+ import Spinner from "ink-spinner";
15
+ import TextInput from "ink-text-input";
16
+ import { createServer } from "http";
17
+ import { exec } from "child_process";
18
+
19
+ // src/hooks/useApi.ts
20
+ import { useQuery, useMutation } from "@tanstack/react-query";
21
+
22
+ // src/utils/apiClient.ts
23
+ import { createORPCClient } from "@orpc/client";
24
+ import { RPCLink } from "@orpc/client/fetch";
25
+
26
+ // src/utils/config.ts
27
+ import { promises as fs, existsSync, mkdirSync, copyFileSync } from "fs";
28
+ import { homedir } from "os";
29
+ import { join, resolve } from "path";
30
+ function expandTilde(input) {
31
+ if (!input) return input;
32
+ if (input === "~") return homedir();
33
+ if (input.startsWith("~/")) return join(homedir(), input.slice(2));
34
+ return input;
35
+ }
36
+ function getSettingsDir() {
37
+ const override = process.env.DEVBOXER_SETTINGS_DIR;
38
+ if (override && override.trim().length > 0) {
39
+ return resolve(expandTilde(override.trim()));
40
+ }
41
+ const legacy = process.env.TERRY_SETTINGS_DIR;
42
+ if (legacy && legacy.trim().length > 0) {
43
+ console.warn(
44
+ "TERRY_SETTINGS_DIR is deprecated. Use DEVBOXER_SETTINGS_DIR instead."
45
+ );
46
+ return resolve(expandTilde(legacy.trim()));
47
+ }
48
+ const newDir = join(homedir(), ".devboxer");
49
+ const legacyDir = join(homedir(), ".terry");
50
+ if (!existsSync(newDir) && existsSync(legacyDir)) {
51
+ mkdirSync(newDir, { recursive: true });
52
+ const legacyConfig = join(legacyDir, "config.json");
53
+ if (existsSync(legacyConfig)) {
54
+ copyFileSync(legacyConfig, join(newDir, "config.json"));
55
+ console.warn("Migrated config from ~/.terry to ~/.devboxer");
56
+ }
57
+ }
58
+ return newDir;
59
+ }
60
+ var CONFIG_DIR = getSettingsDir();
61
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
62
+ async function readConfig() {
63
+ try {
64
+ const content = await fs.readFile(CONFIG_FILE, "utf-8");
65
+ return JSON.parse(content);
66
+ } catch {
67
+ return {};
68
+ }
69
+ }
70
+ async function getApiKey() {
71
+ const config = await readConfig();
72
+ return config.apiKey || null;
73
+ }
74
+ async function saveApiKey(apiKey) {
75
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
76
+ const config = await readConfig();
77
+ config.apiKey = apiKey;
78
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
79
+ }
80
+
81
+ // src/utils/apiClient.ts
82
+ var link = new RPCLink({
83
+ url: `${"https://www.devboxer.com"}/api/cli`,
84
+ headers: async () => ({
85
+ "X-Daemon-Token": await getApiKey() ?? ""
86
+ })
87
+ });
88
+ var apiClient = createORPCClient(link);
89
+
90
+ // src/hooks/useApi.ts
91
+ import { safe, isDefinedError } from "@orpc/client";
92
+ async function fetchThreads(repo) {
93
+ const [error, result] = await safe(
94
+ apiClient.threads.list({
95
+ repo
96
+ })
97
+ );
98
+ if (isDefinedError(error)) {
99
+ switch (error.code) {
100
+ case "UNAUTHORIZED":
101
+ throw new Error("Authentication failed. Try running 'devboxer auth'.");
102
+ case "NOT_FOUND":
103
+ throw new Error("No tasks found");
104
+ case "INTERNAL_ERROR":
105
+ throw new Error("Internal server error");
106
+ case "RATE_LIMIT_EXCEEDED":
107
+ throw new Error("Rate limit exceeded. Please try again later.");
108
+ default:
109
+ const _exhaustiveCheck = error;
110
+ throw new Error(`Unknown error: ${_exhaustiveCheck}`);
111
+ }
112
+ } else if (error) {
113
+ throw new Error("Failed to fetch tasks");
114
+ }
115
+ return result;
116
+ }
117
+ async function fetchThreadDetail(threadId) {
118
+ const [error, result] = await safe(
119
+ apiClient.threads.detail({
120
+ threadId
121
+ })
122
+ );
123
+ if (isDefinedError(error)) {
124
+ switch (error.code) {
125
+ case "UNAUTHORIZED":
126
+ throw new Error("Authentication failed. Try running 'devboxer auth'.");
127
+ case "NOT_FOUND":
128
+ throw new Error("Task not found");
129
+ case "INTERNAL_ERROR":
130
+ throw new Error("Internal server error");
131
+ case "RATE_LIMIT_EXCEEDED":
132
+ throw new Error("Rate limit exceeded. Please try again later.");
133
+ default:
134
+ const _exhaustiveCheck = error;
135
+ throw new Error(`Unknown error: ${_exhaustiveCheck}`);
136
+ }
137
+ } else if (error) {
138
+ throw new Error("Failed to fetch task detail");
139
+ }
140
+ return result;
141
+ }
142
+ function useThreads(repo) {
143
+ return useQuery({
144
+ queryKey: ["threads", repo],
145
+ queryFn: () => fetchThreads(repo)
146
+ });
147
+ }
148
+ function useThreadDetail(threadId) {
149
+ return useQuery({
150
+ queryKey: ["thread", threadId],
151
+ queryFn: () => fetchThreadDetail(threadId),
152
+ enabled: !!threadId
153
+ });
154
+ }
155
+ function useSaveApiKey() {
156
+ return useMutation({
157
+ mutationFn: async (apiKey) => {
158
+ await saveApiKey(apiKey);
159
+ }
160
+ });
161
+ }
162
+
163
+ // src/commands/auth.tsx
164
+ var AUTH_PORT = 8742;
165
+ var DEVBOXER_WEB_URL = "https://www.devboxer.com";
166
+ function openBrowser(url) {
167
+ const start = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
168
+ exec(`${start} ${url}`, (error) => {
169
+ if (error) {
170
+ console.error("Failed to open browser:", error);
171
+ }
172
+ });
173
+ }
174
+ function createAuthServer({ onError, onApiKeyReceived }) {
175
+ return createServer(async (req, res) => {
176
+ res.setHeader("Access-Control-Allow-Origin", "*");
177
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
178
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
179
+ if (req.method === "OPTIONS") {
180
+ res.writeHead(200);
181
+ res.end();
182
+ return;
183
+ }
184
+ if (req.method === "POST" && req.url === "/auth") {
185
+ let body = "";
186
+ req.on("data", (chunk) => {
187
+ body += chunk.toString();
188
+ });
189
+ req.on("end", async () => {
190
+ try {
191
+ const { apiKey } = JSON.parse(body);
192
+ if (!apiKey) {
193
+ res.writeHead(400, { "Content-Type": "application/json" });
194
+ res.end(JSON.stringify({ error: "API key required" }));
195
+ return;
196
+ }
197
+ res.writeHead(200, { "Content-Type": "application/json" });
198
+ res.end(JSON.stringify({ success: true }));
199
+ onApiKeyReceived(apiKey);
200
+ } catch (error) {
201
+ res.writeHead(500, { "Content-Type": "application/json" });
202
+ res.end(JSON.stringify({ error: "Failed to process API key" }));
203
+ onError(error instanceof Error ? error : new Error("Unknown error"));
204
+ }
205
+ });
206
+ } else {
207
+ res.writeHead(404);
208
+ res.end();
209
+ }
210
+ });
211
+ }
212
+ function AuthCommand({ apiKey: providedApiKey }) {
213
+ const [status, setStatus] = useState("waiting");
214
+ const [message, setMessage] = useState("");
215
+ const [manualApiKey, setManualApiKey] = useState("");
216
+ const [browserOpened, setBrowserOpened] = useState(false);
217
+ const saveApiKeyMutation = useSaveApiKey();
218
+ const handleApiKey = async (apiKey) => {
219
+ setStatus("authenticating");
220
+ try {
221
+ await saveApiKeyMutation.mutateAsync(apiKey);
222
+ setStatus("success");
223
+ setMessage("API key saved successfully!");
224
+ setTimeout(() => {
225
+ process.exit(0);
226
+ }, 2e3);
227
+ } catch (error) {
228
+ setStatus("error");
229
+ setMessage(
230
+ `Failed to save API key: ${error instanceof Error ? error.message : "Unknown error"}`
231
+ );
232
+ }
233
+ };
234
+ useEffect(() => {
235
+ if (providedApiKey) {
236
+ handleApiKey(providedApiKey);
237
+ return;
238
+ }
239
+ if (status !== "waiting") {
240
+ return;
241
+ }
242
+ const server = createAuthServer({
243
+ onError: (error) => {
244
+ },
245
+ onApiKeyReceived: handleApiKey
246
+ });
247
+ server.listen(AUTH_PORT, () => {
248
+ setBrowserOpened(true);
249
+ openBrowser(`${DEVBOXER_WEB_URL}/cli/auth`);
250
+ });
251
+ server.on("error", (error) => {
252
+ if (error.code === "EADDRINUSE") {
253
+ }
254
+ });
255
+ return () => {
256
+ server.close();
257
+ };
258
+ }, [providedApiKey, status]);
259
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, status === "success" ? /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2713 ", message) : /* @__PURE__ */ React.createElement(React.Fragment, null, browserOpened && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, "A browser window should open for automatic authentication."), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "If auto auth doesn't work, you can manually paste the code below.")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Visit: ", /* @__PURE__ */ React.createElement(Text, { color: "blue" }, DEVBOXER_WEB_URL, "/cli/auth"))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Paste the code from the browser: ")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
260
+ TextInput,
261
+ {
262
+ value: manualApiKey,
263
+ onChange: setManualApiKey,
264
+ onSubmit: () => {
265
+ if (manualApiKey.trim()) {
266
+ handleApiKey(manualApiKey.trim());
267
+ }
268
+ },
269
+ placeholder: "ter_..."
270
+ }
271
+ )), status === "authenticating" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " Authenticating...")), status === "error" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u2717 ", message))));
272
+ }
273
+
274
+ // src/commands/pull.tsx
275
+ import React3, { useEffect as useEffect2, useState as useState3 } from "react";
276
+ import { Box as Box3, Text as Text3 } from "ink";
277
+ import Spinner2 from "ink-spinner";
278
+ import { promisify as promisify2 } from "util";
279
+ import { exec as execCallback2 } from "child_process";
280
+ import { promises as fs2 } from "fs";
281
+ import { join as join2 } from "path";
282
+ import { homedir as homedir2 } from "os";
283
+
284
+ // src/components/ThreadSelector.tsx
285
+ import React2, { useState as useState2 } from "react";
286
+ import { Box as Box2, Text as Text2, useInput } from "ink";
287
+ import SelectInput from "ink-select-input";
288
+ function getTimeAgo(date) {
289
+ const seconds = Math.floor(((/* @__PURE__ */ new Date()).getTime() - date.getTime()) / 1e3);
290
+ if (seconds < 60) return "just now";
291
+ const minutes = Math.floor(seconds / 60);
292
+ if (minutes < 60) return `${minutes}m ago`;
293
+ const hours = Math.floor(minutes / 60);
294
+ if (hours < 24) return `${hours}h ago`;
295
+ const days = Math.floor(hours / 24);
296
+ if (days < 7) return `${days}d ago`;
297
+ const weeks = Math.floor(days / 7);
298
+ if (weeks < 4) return `${weeks}w ago`;
299
+ const months = Math.floor(days / 30);
300
+ if (months < 12) return `${months}mo ago`;
301
+ const years = Math.floor(days / 365);
302
+ return `${years}y ago`;
303
+ }
304
+ function ThreadSelector({ onSelect, currentRepo }) {
305
+ const [page, setPage] = useState2(0);
306
+ const { data: threads = [], isLoading, error } = useThreads(currentRepo);
307
+ const ITEMS_PER_PAGE = 10;
308
+ const totalPages = Math.ceil(threads.length / ITEMS_PER_PAGE);
309
+ const startIndex = page * ITEMS_PER_PAGE;
310
+ const endIndex = Math.min(startIndex + ITEMS_PER_PAGE, threads.length);
311
+ const visibleThreads = threads.slice(startIndex, endIndex);
312
+ const items = visibleThreads.map((thread) => {
313
+ const unreadIndicator = thread.isUnread ? "\u25CF " : " ";
314
+ const threadName = thread.name || "Untitled";
315
+ const date = new Date(thread.updatedAt);
316
+ const timeAgo = getTimeAgo(date);
317
+ const label = `${unreadIndicator}${threadName} \u2022 ${timeAgo}`;
318
+ return {
319
+ label,
320
+ value: thread.id
321
+ };
322
+ });
323
+ useInput((_input, key) => {
324
+ if (!isLoading && !error && threads.length > 0) {
325
+ if (key.leftArrow && page > 0) {
326
+ setPage(page - 1);
327
+ } else if (key.rightArrow && page < totalPages - 1) {
328
+ setPage(page + 1);
329
+ }
330
+ }
331
+ });
332
+ if (isLoading) {
333
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, null, "Loading tasks..."));
334
+ }
335
+ if (error) {
336
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, { color: "red" }, error instanceof Error ? error.message : String(error)));
337
+ }
338
+ if (threads.length === 0) {
339
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Text2, null, currentRepo ? "No tasks found for the current repository." : "No tasks found."));
340
+ }
341
+ return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, /* @__PURE__ */ React2.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Text2, { bold: true }, "Select a task to pull:")), /* @__PURE__ */ React2.createElement(SelectInput, { items, onSelect: (item) => onSelect(item.value) }), totalPages > 1 && /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "Page ", page + 1, " of ", totalPages, " (\u2190 \u2192 to navigate pages, \u2191 \u2193 to select)")));
342
+ }
343
+
344
+ // src/utils/claude.ts
345
+ import { spawn, execSync } from "child_process";
346
+ function launchClaude(sessionId) {
347
+ try {
348
+ let claudeCommand = "claude";
349
+ try {
350
+ const parentShell = process.env.SHELL || "/bin/sh";
351
+ console.log(`Using parent shell: ${parentShell}`);
352
+ const shellCommand = `${parentShell} -lic 'which claude 2>/dev/null || type -p claude 2>/dev/null || command -v claude 2>/dev/null'`;
353
+ const result = execSync(shellCommand, {
354
+ encoding: "utf8",
355
+ timeout: 5e3
356
+ }).trim();
357
+ if (result) {
358
+ const aliasMatch = result.match(/aliased to (.+)/);
359
+ if (aliasMatch && aliasMatch[1]) {
360
+ claudeCommand = aliasMatch[1].trim();
361
+ } else {
362
+ claudeCommand = result;
363
+ }
364
+ console.log(`Found Claude CLI at: ${claudeCommand}`);
365
+ }
366
+ } catch (error) {
367
+ console.error(
368
+ "Could not locate claude via shell, attempting direct execution"
369
+ );
370
+ }
371
+ const claudeProcess = spawn(claudeCommand, ["--resume", sessionId], {
372
+ stdio: "inherit",
373
+ env: process.env,
374
+ cwd: process.cwd()
375
+ });
376
+ claudeProcess.on("error", (err) => {
377
+ if (err.message.includes("ENOENT")) {
378
+ console.error(
379
+ "Error: 'claude' command not found. Please ensure Claude CLI is installed and in your PATH."
380
+ );
381
+ } else {
382
+ console.error(`Failed to launch Claude: ${err.message}`);
383
+ }
384
+ process.exit(1);
385
+ });
386
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
387
+ signals.forEach((signal) => {
388
+ process.on(signal, () => {
389
+ claudeProcess.kill(signal);
390
+ });
391
+ });
392
+ claudeProcess.on("exit", (code, signal) => {
393
+ if (signal) {
394
+ process.kill(process.pid, signal);
395
+ } else {
396
+ process.exit(code || 0);
397
+ }
398
+ });
399
+ } catch (err) {
400
+ console.error(
401
+ `Failed to launch Claude: ${err instanceof Error ? err.message : String(err)}`
402
+ );
403
+ process.exit(1);
404
+ }
405
+ }
406
+
407
+ // src/hooks/useGitInfo.ts
408
+ import { useQuery as useQuery2 } from "@tanstack/react-query";
409
+ import { promisify } from "util";
410
+ import { exec as execCallback } from "child_process";
411
+ var exec2 = promisify(execCallback);
412
+ async function getCurrentGitHubRepo() {
413
+ try {
414
+ const { stdout } = await exec2("git config --get remote.origin.url", {
415
+ encoding: "utf8"
416
+ });
417
+ const remoteUrl = stdout.trim();
418
+ const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^.]+)(\.git)?$/);
419
+ if (match && match[1]) {
420
+ return match[1];
421
+ }
422
+ return null;
423
+ } catch (err) {
424
+ return null;
425
+ }
426
+ }
427
+ async function getCurrentBranch() {
428
+ try {
429
+ const { stdout } = await exec2("git branch --show-current", {
430
+ encoding: "utf8"
431
+ });
432
+ return stdout.trim() || null;
433
+ } catch (err) {
434
+ return null;
435
+ }
436
+ }
437
+ function useCurrentGitHubRepo() {
438
+ return useQuery2({
439
+ queryKey: ["git", "repo"],
440
+ queryFn: getCurrentGitHubRepo,
441
+ staleTime: Infinity
442
+ // Git repo doesn't change during session
443
+ });
444
+ }
445
+ function useCurrentBranch() {
446
+ return useQuery2({
447
+ queryKey: ["git", "branch"],
448
+ queryFn: getCurrentBranch,
449
+ staleTime: 5e3
450
+ // Refresh every 5 seconds in case branch changes
451
+ });
452
+ }
453
+ function useGitInfo() {
454
+ const repoQuery = useCurrentGitHubRepo();
455
+ const branchQuery = useCurrentBranch();
456
+ return {
457
+ repo: repoQuery.data,
458
+ branch: branchQuery.data,
459
+ isLoading: repoQuery.isLoading || branchQuery.isLoading,
460
+ error: repoQuery.error || branchQuery.error
461
+ };
462
+ }
463
+
464
+ // src/commands/pull.tsx
465
+ var exec3 = promisify2(execCallback2);
466
+ async function findGitRoot() {
467
+ try {
468
+ const { stdout } = await exec3("git rev-parse --show-toplevel", {
469
+ encoding: "utf8"
470
+ });
471
+ const gitRoot = stdout.trim();
472
+ return { gitRoot };
473
+ } catch (err) {
474
+ return { error: "Not in a git repository" };
475
+ }
476
+ }
477
+ async function switchToBranch(branchName) {
478
+ try {
479
+ const { stdout: statusOutput } = await exec3("git status --porcelain", {
480
+ encoding: "utf8"
481
+ });
482
+ if (statusOutput.trim()) {
483
+ return {
484
+ success: false,
485
+ error: `Cannot switch branches: you have uncommitted changes. Please commit or stash your changes before running 'devboxer pull'.`
486
+ };
487
+ }
488
+ await exec3("git fetch", { encoding: "utf8" });
489
+ let branchExists = false;
490
+ try {
491
+ await exec3(`git rev-parse --verify ${branchName}`, { encoding: "utf8" });
492
+ branchExists = true;
493
+ } catch {
494
+ }
495
+ if (branchExists) {
496
+ await exec3(`git checkout ${branchName}`, { encoding: "utf8" });
497
+ await exec3("git pull", { encoding: "utf8" });
498
+ } else {
499
+ try {
500
+ await exec3(`git checkout -b ${branchName} origin/${branchName}`, {
501
+ encoding: "utf8"
502
+ });
503
+ } catch {
504
+ return {
505
+ success: false,
506
+ error: `Branch ${branchName} not found on remote`
507
+ };
508
+ }
509
+ }
510
+ return { success: true };
511
+ } catch (err) {
512
+ return {
513
+ success: false,
514
+ error: `Failed to pull branch: ${err instanceof Error ? err.message : String(err)}`
515
+ };
516
+ }
517
+ }
518
+ async function saveSessionData(sessionId, cwdWithHyphens, jsonl) {
519
+ const claudeDir = join2(homedir2(), ".claude", "projects", cwdWithHyphens);
520
+ await fs2.mkdir(claudeDir, { recursive: true });
521
+ const jsonlPath = join2(claudeDir, `${sessionId}.jsonl`);
522
+ const jsonlContent = jsonl.map((item) => JSON.stringify(item)).join("\n");
523
+ await fs2.writeFile(jsonlPath, jsonlContent);
524
+ return jsonlPath;
525
+ }
526
+ async function processSessionData(data, setProcessingStatus, onComplete) {
527
+ try {
528
+ setProcessingStatus("Finding git repository root...");
529
+ const { gitRoot, error: gitError } = await findGitRoot();
530
+ if (gitError || !gitRoot) {
531
+ return { success: false, error: gitError };
532
+ }
533
+ process.chdir(gitRoot);
534
+ setProcessingStatus(`Changed to git root: ${gitRoot}`);
535
+ if (data.branchName) {
536
+ setProcessingStatus(
537
+ `Pulling latest version of branch: ${data.branchName}`
538
+ );
539
+ const { success, error: branchError } = await switchToBranch(
540
+ data.branchName
541
+ );
542
+ if (!success) {
543
+ return { success: false, error: branchError };
544
+ }
545
+ setProcessingStatus(
546
+ `Successfully switched to branch: ${data.branchName}`
547
+ );
548
+ }
549
+ const cwd = process.cwd();
550
+ const cwdWithHyphens = cwd.replace(/\//g, "-");
551
+ setProcessingStatus(`Project directory: ${cwdWithHyphens}`);
552
+ if (data.jsonl && data.jsonl.length > 0) {
553
+ const jsonlPath = await saveSessionData(
554
+ data.sessionId,
555
+ cwdWithHyphens,
556
+ data.jsonl
557
+ );
558
+ setProcessingStatus(`Saved session data to: ${jsonlPath}`);
559
+ }
560
+ setProcessingStatus(`Session ready: ${data.sessionId}`);
561
+ setTimeout(() => {
562
+ onComplete(data.sessionId);
563
+ }, 1e3);
564
+ return {
565
+ success: true,
566
+ gitRoot,
567
+ cwdWithHyphens,
568
+ sessionId: data.sessionId
569
+ };
570
+ } catch (err) {
571
+ return {
572
+ success: false,
573
+ error: `Processing error: ${err instanceof Error ? err.message : String(err)}`
574
+ };
575
+ }
576
+ }
577
+ function PullCommand({
578
+ threadId,
579
+ resume
580
+ }) {
581
+ const [selectedThreadId, setSelectedThreadId] = useState3(
582
+ threadId
583
+ );
584
+ const [processingStatus, setProcessingStatus] = useState3("");
585
+ const [processingError, setProcessingError] = useState3("");
586
+ const [isProcessing, setIsProcessing] = useState3(false);
587
+ const [completedSessionId, setCompletedSessionId] = useState3(
588
+ null
589
+ );
590
+ const repoQuery = useCurrentGitHubRepo();
591
+ const currentRepo = repoQuery.data;
592
+ const {
593
+ data: sessionData,
594
+ isLoading,
595
+ error: sessionFetchError
596
+ } = useThreadDetail(selectedThreadId);
597
+ useEffect2(() => {
598
+ if (!sessionData) return;
599
+ const process2 = async () => {
600
+ setIsProcessing(true);
601
+ const result = await processSessionData(
602
+ sessionData,
603
+ setProcessingStatus,
604
+ (sessionId) => {
605
+ setCompletedSessionId(sessionId);
606
+ setIsProcessing(false);
607
+ if (resume) {
608
+ setTimeout(() => {
609
+ launchClaude(sessionId);
610
+ }, 100);
611
+ }
612
+ }
613
+ );
614
+ if (!result.success) {
615
+ setProcessingError(result.error || "Unknown error during processing");
616
+ setIsProcessing(false);
617
+ }
618
+ };
619
+ process2();
620
+ }, [sessionData, resume]);
621
+ const handleThreadSelect = (threadId2) => {
622
+ setSelectedThreadId(threadId2);
623
+ };
624
+ if (!selectedThreadId) {
625
+ return /* @__PURE__ */ React3.createElement(
626
+ ThreadSelector,
627
+ {
628
+ onSelect: handleThreadSelect,
629
+ currentRepo: currentRepo || void 0
630
+ }
631
+ );
632
+ }
633
+ if (sessionFetchError) {
634
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, "Error:", " ", sessionFetchError instanceof Error ? sessionFetchError.message : String(sessionFetchError)));
635
+ }
636
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Box3, null, isLoading ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Spinner2, { type: "dots" })), /* @__PURE__ */ React3.createElement(Text3, null, " Fetching session for task ", selectedThreadId, "...")) : sessionData ? /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, "\u2713 Session fetched successfully") : /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, "Error: No session data")), sessionData && !isLoading && /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Box3, { width: 15 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Name")), /* @__PURE__ */ React3.createElement(Text3, null, sessionData.name)), /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Box3, { width: 15 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Branch")), /* @__PURE__ */ React3.createElement(Text3, null, sessionData.branchName || "N/A")), /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Box3, { width: 15 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Repository")), /* @__PURE__ */ React3.createElement(Text3, null, sessionData.githubRepoFullName || "N/A")), /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Box3, { width: 15 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "PR Number")), /* @__PURE__ */ React3.createElement(Text3, null, sessionData.githubPRNumber ? `#${sessionData.githubPRNumber}` : "N/A"))), processingError && /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, "Error: ", processingError)), completedSessionId ? /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, null, " "), resume ? /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, "Launching Claude...") : sessionData?.agent === "claudeCode" ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, "To continue this session, run:"), /* @__PURE__ */ React3.createElement(
637
+ Box3,
638
+ {
639
+ marginLeft: 2,
640
+ borderStyle: "round",
641
+ borderColor: "cyan",
642
+ paddingX: 1
643
+ },
644
+ /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, "claude --resume ", completedSessionId)
645
+ )) : /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, "Session ready")) : isProcessing && processingStatus && /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Spinner2, { type: "dots" })), /* @__PURE__ */ React3.createElement(Text3, null, " ", processingStatus)));
646
+ }
647
+
648
+ // src/commands/create.tsx
649
+ import React4, { useState as useState4, useEffect as useEffect3 } from "react";
650
+ import { Box as Box4, Text as Text4 } from "ink";
651
+ import Spinner3 from "ink-spinner";
652
+ import { useMutation as useMutation2 } from "@tanstack/react-query";
653
+ function CreateCommand({
654
+ message,
655
+ repo,
656
+ branch,
657
+ createNewBranch = true,
658
+ mode = "execute",
659
+ model
660
+ }) {
661
+ const [error, setError] = useState4(null);
662
+ const gitInfo = useGitInfo();
663
+ const createMutation = useMutation2({
664
+ mutationFn: async () => {
665
+ const finalRepo = repo || gitInfo.repo;
666
+ if (!finalRepo) {
667
+ throw new Error(
668
+ "No repository specified and could not detect from current directory"
669
+ );
670
+ }
671
+ const finalBranch = branch || gitInfo.branch || "main";
672
+ const normalizedMode = mode === "plan" ? "plan" : "execute";
673
+ const result = await apiClient.threads.create({
674
+ message,
675
+ githubRepoFullName: finalRepo,
676
+ repoBaseBranchName: finalBranch,
677
+ createNewBranch,
678
+ mode: normalizedMode,
679
+ model
680
+ });
681
+ return result;
682
+ },
683
+ onError: (error2) => {
684
+ console.error("Error creating thread:", error2);
685
+ setError(error2.message || "Failed to create thread");
686
+ }
687
+ });
688
+ useEffect3(() => {
689
+ if (!gitInfo.isLoading) {
690
+ createMutation.mutate();
691
+ }
692
+ }, [gitInfo.isLoading]);
693
+ if (gitInfo.isLoading || createMutation.isPending) {
694
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, null, /* @__PURE__ */ React4.createElement(Spinner3, { type: "dots" }), " ", gitInfo.isLoading ? "Detecting repository..." : "Creating new task..."));
695
+ }
696
+ if (createMutation.isError || error) {
697
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "red" }, "\u274C Error: ", error || "Failed to create thread"));
698
+ }
699
+ if (createMutation.isSuccess && createMutation.data) {
700
+ const finalRepo = repo || gitInfo.repo;
701
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { color: "green" }, "\u2713 Task created successfully!"), /* @__PURE__ */ React4.createElement(Text4, null, "Repository: ", finalRepo), /* @__PURE__ */ React4.createElement(Text4, null, "Thread ID: ", createMutation.data.threadId), createMutation.data.branchName && /* @__PURE__ */ React4.createElement(Text4, null, "Branch: ", createMutation.data.branchName), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "Visit https://www.devboxer.com/task/", createMutation.data.threadId, " to view your task"));
702
+ }
703
+ return null;
704
+ }
705
+
706
+ // src/commands/list.tsx
707
+ import React5, { useEffect as useEffect4, useState as useState5 } from "react";
708
+ import { Box as Box5, Text as Text5, useApp } from "ink";
709
+ function ListCommand() {
710
+ const { exit } = useApp();
711
+ const repoQuery = useCurrentGitHubRepo();
712
+ const currentRepo = repoQuery.data;
713
+ const {
714
+ data: threads = [],
715
+ isLoading,
716
+ error
717
+ } = useThreads(currentRepo || void 0);
718
+ const [authError, setAuthError] = useState5(null);
719
+ useEffect4(() => {
720
+ const checkAuth = async () => {
721
+ const apiKey = await getApiKey();
722
+ if (!apiKey) {
723
+ setAuthError("Not authenticated. Run 'devboxer auth' first.");
724
+ }
725
+ };
726
+ checkAuth();
727
+ }, []);
728
+ useEffect4(() => {
729
+ if (authError) {
730
+ exit();
731
+ }
732
+ }, [authError, exit]);
733
+ useEffect4(() => {
734
+ if (error) {
735
+ exit();
736
+ }
737
+ }, [error, exit]);
738
+ useEffect4(() => {
739
+ if (!isLoading && threads) {
740
+ exit();
741
+ }
742
+ }, [threads, isLoading, exit]);
743
+ if (authError) {
744
+ return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, "Error: ", authError));
745
+ }
746
+ if (error) {
747
+ return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, "Error: ", error instanceof Error ? error.message : String(error)));
748
+ }
749
+ if (isLoading) {
750
+ return /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, null, "Loading tasks..."));
751
+ }
752
+ return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, threads.map((thread, index) => /* @__PURE__ */ React5.createElement(Box5, { key: thread.id, flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Task ID "), /* @__PURE__ */ React5.createElement(Text5, null, thread.id)), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Name "), /* @__PURE__ */ React5.createElement(Text5, null, thread.name || "Untitled")), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Branch "), /* @__PURE__ */ React5.createElement(Text5, null, thread.branchName || "N/A")), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Repository "), /* @__PURE__ */ React5.createElement(Text5, null, thread.githubRepoFullName || "N/A")), /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "PR Number "), /* @__PURE__ */ React5.createElement(Text5, null, thread.githubPRNumber ? `#${thread.githubPRNumber}` : "N/A")))), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, null, "Total: ", threads.length, " task", threads.length !== 1 ? "s" : "")));
753
+ }
754
+
755
+ // src/providers/QueryProvider.tsx
756
+ import React6 from "react";
757
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
758
+ var queryClient = new QueryClient({
759
+ defaultOptions: {
760
+ queries: {
761
+ retry: 2,
762
+ staleTime: 5 * 60 * 1e3,
763
+ // 5 minutes
764
+ refetchOnMount: false,
765
+ refetchOnWindowFocus: false
766
+ }
767
+ }
768
+ });
769
+ function QueryProvider({ children }) {
770
+ return /* @__PURE__ */ React6.createElement(QueryClientProvider, { client: queryClient }, children);
771
+ }
772
+
773
+ // src/components/RootLayout.tsx
774
+ import React8 from "react";
775
+ import { Box as Box7 } from "ink";
776
+
777
+ // src/components/UpdateNotifier.tsx
778
+ import React7 from "react";
779
+ import { Box as Box6, Text as Text6 } from "ink";
780
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
781
+ import updateNotifier from "update-notifier";
782
+
783
+ // package.json
784
+ var package_default = {
785
+ name: "@devboxer/cli",
786
+ version: "0.1.21",
787
+ type: "module",
788
+ bin: {
789
+ devboxer: "./dist/index.js"
790
+ },
791
+ scripts: {
792
+ dev: "tsup --watch",
793
+ build: "tsup",
794
+ start: "node dist/index.js",
795
+ "tsc-watch": "tsc --watch --noEmit --preserveWatchOutput",
796
+ "tsc-check": "tsc --noEmit",
797
+ format: "prettier --write . --ignore-path ../../.prettierignore --config ../../.prettierrc",
798
+ "format-check": "prettier --check . --ignore-path ../../.prettierignore --config ../../.prettierrc",
799
+ lint: "tsc --noEmit",
800
+ "install:dev": "./scripts/install-cli-dev.sh",
801
+ "uninstall:dev": "./scripts/uninstall-cli.sh",
802
+ release: "./scripts/release.sh"
803
+ },
804
+ dependencies: {
805
+ "@modelcontextprotocol/sdk": "^1.0.6",
806
+ "@orpc/client": "^1.6.0",
807
+ "@orpc/contract": "1.6.0",
808
+ "@tanstack/react-query": "^5.76.1",
809
+ commander: "^12.1.0",
810
+ ink: "^6.0.0",
811
+ "ink-select-input": "^6.2.0",
812
+ "ink-spinner": "^5.0.0",
813
+ "ink-text-input": "^6.0.0",
814
+ "node-fetch": "^3.3.2",
815
+ react: "19.1.2",
816
+ "update-notifier": "^7.3.1"
817
+ },
818
+ devDependencies: {
819
+ "@devboxer/agent": "workspace:*",
820
+ "@devboxer/cli-api-contract": "workspace:*",
821
+ "@types/node": "^22.6.1",
822
+ "@types/react": "^19.1.8",
823
+ "@types/update-notifier": "^6.0.8",
824
+ dotenv: "^16.5.0",
825
+ tsup: "^8.3.5",
826
+ tsx: "^4.19.1",
827
+ typescript: "^5.7.2"
828
+ },
829
+ publishConfig: {
830
+ access: "public",
831
+ registry: "https://registry.npmjs.org/"
832
+ },
833
+ files: [
834
+ "dist",
835
+ "package.json",
836
+ "README.md"
837
+ ],
838
+ keywords: [
839
+ "devboxer",
840
+ "cli",
841
+ "ai",
842
+ "coding-assistant"
843
+ ],
844
+ author: "DevBoxer, LLC",
845
+ license: "UNLICENSED",
846
+ repository: {
847
+ type: "git",
848
+ url: "https://github.com/devboxerhub/devboxer.git",
849
+ directory: "apps/cli"
850
+ },
851
+ homepage: "https://www.devboxer.com",
852
+ bugs: {
853
+ url: "https://github.com/devboxerhub/devboxer/issues"
854
+ }
855
+ };
856
+
857
+ // src/components/UpdateNotifier.tsx
858
+ async function checkForUpdate() {
859
+ if (process.env.DEVBOXER_NO_UPDATE_CHECK === "1") {
860
+ return null;
861
+ }
862
+ const notifier = updateNotifier({
863
+ pkg: package_default,
864
+ updateCheckInterval: 1e3 * 60 * 60
865
+ // 1 hour
866
+ });
867
+ const info = await notifier.fetchInfo();
868
+ if (info.latest !== info.current) {
869
+ return {
870
+ current: info.current,
871
+ latest: info.latest
872
+ };
873
+ }
874
+ return null;
875
+ }
876
+ function UpdateNotifier() {
877
+ const { data: updateInfo } = useQuery3({
878
+ queryKey: ["update-check"],
879
+ queryFn: checkForUpdate,
880
+ staleTime: 1e3 * 60 * 60,
881
+ // 1 hour
882
+ retry: false
883
+ // Don't retry update checks
884
+ });
885
+ if (!updateInfo) {
886
+ return null;
887
+ }
888
+ return /* @__PURE__ */ React7.createElement(Box6, { marginBottom: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text6, { color: "yellow" }, "Update available: ", updateInfo.current, " \u2192 ", updateInfo.latest), /* @__PURE__ */ React7.createElement(Text6, { color: "gray" }, " Run "), /* @__PURE__ */ React7.createElement(Text6, { color: "cyan" }, "npm install -g @devboxerhub/cli"), /* @__PURE__ */ React7.createElement(Text6, { color: "gray" }, " to update"));
889
+ }
890
+
891
+ // src/components/RootLayout.tsx
892
+ function RootLayout({ children }) {
893
+ return /* @__PURE__ */ React8.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React8.createElement(UpdateNotifier, null), children);
894
+ }
895
+
896
+ // src/mcp-server/index.ts
897
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
898
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
899
+ import {
900
+ CallToolRequestSchema,
901
+ ListToolsRequestSchema
902
+ } from "@modelcontextprotocol/sdk/types.js";
903
+ import { spawnSync } from "child_process";
904
+ import { existsSync as existsSync2 } from "fs";
905
+ import { join as join3, dirname } from "path";
906
+ import { fileURLToPath } from "url";
907
+ function getDevboxerPath() {
908
+ try {
909
+ const result = spawnSync("which", ["devboxer"], {
910
+ encoding: "utf-8",
911
+ stdio: "pipe"
912
+ });
913
+ if (result.status === 0) {
914
+ return "devboxer";
915
+ }
916
+ } catch {
917
+ }
918
+ const __filename3 = fileURLToPath(import.meta.url);
919
+ const __dirname3 = dirname(__filename3);
920
+ const cliPath = join3(__dirname3, "../index.js");
921
+ if (existsSync2(cliPath)) {
922
+ return cliPath;
923
+ }
924
+ throw new Error("DevBoxer CLI not found");
925
+ }
926
+ async function executeDevboxerCommand(command, args = []) {
927
+ const devboxerPath = getDevboxerPath();
928
+ const commandArgs = [command, ...args];
929
+ const spawnOptions = {
930
+ encoding: "utf-8",
931
+ env: {
932
+ ...process.env,
933
+ // Ensure the CLI runs in non-interactive mode
934
+ CI: "true"
935
+ }
936
+ };
937
+ let executable;
938
+ let execArgs;
939
+ if (devboxerPath === "devboxer") {
940
+ executable = "devboxer";
941
+ execArgs = commandArgs;
942
+ } else {
943
+ executable = "node";
944
+ execArgs = [devboxerPath, ...commandArgs];
945
+ }
946
+ const result = spawnSync(executable, execArgs, spawnOptions);
947
+ if (result.error) {
948
+ throw new Error(
949
+ `Failed to execute devboxer ${command}: ${result.error.message}`
950
+ );
951
+ }
952
+ if (result.status !== 0) {
953
+ const stderr = result.stderr?.toString().trim() || "";
954
+ const stdout = result.stdout?.toString().trim() || "";
955
+ const errorMessage = stderr || stdout || `Command failed with exit code ${result.status}`;
956
+ throw new Error(`devboxer ${command} failed: ${errorMessage}`);
957
+ }
958
+ return result.stdout?.toString().trim() || "";
959
+ }
960
+ async function startMCPServer() {
961
+ const server = new Server(
962
+ {
963
+ name: "devboxer-mcp-server",
964
+ version: "0.1.0"
965
+ },
966
+ {
967
+ capabilities: {
968
+ tools: {}
969
+ }
970
+ }
971
+ );
972
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
973
+ return {
974
+ tools: [
975
+ {
976
+ name: "devboxer_list",
977
+ description: "List all tasks in DevBoxer (calls 'devboxer list')",
978
+ inputSchema: {
979
+ type: "object",
980
+ properties: {}
981
+ }
982
+ },
983
+ {
984
+ name: "devboxer_create",
985
+ description: "Create a new task in DevBoxer (calls 'devboxer create')",
986
+ inputSchema: {
987
+ type: "object",
988
+ properties: {
989
+ message: {
990
+ type: "string",
991
+ description: "The task message/description"
992
+ },
993
+ repo: {
994
+ type: "string",
995
+ description: "GitHub repository (optional, uses current repo if not specified)"
996
+ },
997
+ branch: {
998
+ type: "string",
999
+ description: "Base branch name (optional, uses current branch if not specified)"
1000
+ },
1001
+ createNewBranch: {
1002
+ type: "boolean",
1003
+ description: "Whether to create a new branch (default: true)",
1004
+ default: true
1005
+ }
1006
+ },
1007
+ required: ["message"]
1008
+ }
1009
+ },
1010
+ {
1011
+ name: "devboxer_pull",
1012
+ description: "Pull/fetch session data for a task (calls 'devboxer pull')",
1013
+ inputSchema: {
1014
+ type: "object",
1015
+ properties: {
1016
+ threadId: {
1017
+ type: "string",
1018
+ description: "The thread/task ID to pull (optional, shows interactive selection if not provided)"
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+ ]
1024
+ };
1025
+ });
1026
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1027
+ const { name, arguments: args } = request.params;
1028
+ try {
1029
+ switch (name) {
1030
+ case "devboxer_list":
1031
+ const listOutput = await executeDevboxerCommand("list");
1032
+ return {
1033
+ content: [
1034
+ {
1035
+ type: "text",
1036
+ text: listOutput || "No tasks found"
1037
+ }
1038
+ ]
1039
+ };
1040
+ case "devboxer_create":
1041
+ const createParams = args;
1042
+ if (!createParams.message) {
1043
+ throw new Error("Message is required for creating a task");
1044
+ }
1045
+ const createArgs = [createParams.message];
1046
+ if (createParams.repo) {
1047
+ createArgs.push("-r", createParams.repo);
1048
+ }
1049
+ if (createParams.branch) {
1050
+ createArgs.push("-b", createParams.branch);
1051
+ }
1052
+ if (createParams.createNewBranch === false) {
1053
+ createArgs.push("--no-new-branch");
1054
+ }
1055
+ const createOutput = await executeDevboxerCommand(
1056
+ "create",
1057
+ createArgs
1058
+ );
1059
+ return {
1060
+ content: [
1061
+ {
1062
+ type: "text",
1063
+ text: createOutput || "Task created successfully"
1064
+ }
1065
+ ]
1066
+ };
1067
+ case "devboxer_pull":
1068
+ const pullParams = args;
1069
+ const pullArgs = [];
1070
+ if (pullParams.threadId) {
1071
+ pullArgs.push(pullParams.threadId);
1072
+ }
1073
+ const pullOutput = await executeDevboxerCommand("pull", pullArgs);
1074
+ return {
1075
+ content: [
1076
+ {
1077
+ type: "text",
1078
+ text: pullOutput || "Pull completed successfully"
1079
+ }
1080
+ ]
1081
+ };
1082
+ default:
1083
+ throw new Error(`Unknown tool: ${name}`);
1084
+ }
1085
+ } catch (error) {
1086
+ return {
1087
+ content: [
1088
+ {
1089
+ type: "text",
1090
+ text: `Error: ${error.message}`
1091
+ }
1092
+ ]
1093
+ };
1094
+ }
1095
+ });
1096
+ const transport = new StdioServerTransport();
1097
+ await server.connect(transport);
1098
+ }
1099
+
1100
+ // src/index.tsx
1101
+ process.on("uncaughtException", (error) => {
1102
+ console.error("Uncaught Exception:", error);
1103
+ console.error("Stack trace:", error.stack);
1104
+ process.exit(1);
1105
+ });
1106
+ process.on("unhandledRejection", (reason, promise) => {
1107
+ console.error("Unhandled Rejection at:", promise);
1108
+ console.error("Reason:", reason);
1109
+ process.exit(1);
1110
+ });
1111
+ var __filename2 = fileURLToPath2(import.meta.url);
1112
+ var __dirname2 = dirname2(__filename2);
1113
+ var packageJson = JSON.parse(
1114
+ await readFile(join4(__dirname2, "../package.json"), "utf-8")
1115
+ );
1116
+ program.name("devboxer").description("DevBoxer CLI - AI-powered coding assistant").version(packageJson.version);
1117
+ program.command("auth [apiKey]").description("Authenticate with your DevBoxer API key").action((apiKey) => {
1118
+ render(
1119
+ /* @__PURE__ */ React9.createElement(QueryProvider, null, /* @__PURE__ */ React9.createElement(RootLayout, null, /* @__PURE__ */ React9.createElement(AuthCommand, { apiKey })))
1120
+ );
1121
+ });
1122
+ program.command("pull [threadId]").description("Fetch session data for a task").option("-r, --resume", "Automatically launch Claude after pulling").action((threadId, options) => {
1123
+ render(
1124
+ /* @__PURE__ */ React9.createElement(QueryProvider, null, /* @__PURE__ */ React9.createElement(RootLayout, null, /* @__PURE__ */ React9.createElement(PullCommand, { threadId, resume: options.resume })))
1125
+ );
1126
+ });
1127
+ var CLI_MODEL_OPTIONS = [
1128
+ "amp",
1129
+ "haiku",
1130
+ "opus",
1131
+ "sonnet",
1132
+ "gpt-5",
1133
+ "gpt-5-low",
1134
+ "gpt-5-medium",
1135
+ "gpt-5-high",
1136
+ "gpt-5-codex",
1137
+ "gpt-5-codex-low",
1138
+ "gpt-5-codex-medium",
1139
+ "gpt-5-codex-high",
1140
+ "gpt-5.2",
1141
+ "gpt-5.2-low",
1142
+ "gpt-5.2-medium",
1143
+ "gpt-5.2-high",
1144
+ "gpt-5.1",
1145
+ "gpt-5.1-low",
1146
+ "gpt-5.1-medium",
1147
+ "gpt-5.1-high",
1148
+ "gpt-5.1-codex-max",
1149
+ "gpt-5.1-codex-max-low",
1150
+ "gpt-5.1-codex-max-medium",
1151
+ "gpt-5.1-codex-max-high",
1152
+ "gpt-5.1-codex-max-xhigh",
1153
+ "gpt-5.1-codex",
1154
+ "gpt-5.1-codex-low",
1155
+ "gpt-5.1-codex-medium",
1156
+ "gpt-5.1-codex-high",
1157
+ "grok-code",
1158
+ "qwen3-coder",
1159
+ "kimi-k2",
1160
+ "glm-4.6",
1161
+ "opencode/grok-code",
1162
+ "opencode/qwen3-coder",
1163
+ "opencode/kimi-k2",
1164
+ "opencode/glm-4.6",
1165
+ "opencode/gemini-2.5-pro",
1166
+ "gemini-3-pro",
1167
+ "gemini-2.5-pro"
1168
+ ];
1169
+ program.command("create <message>").description("Create a new task with the given message").option(
1170
+ "-r, --repo <repo>",
1171
+ "GitHub repository (default: current repository)"
1172
+ ).option("-b, --branch <branch>", "Base branch name (default: current branch)").option("--no-new-branch", "Don't create a new branch").option("-m, --model <model>", `AI model: ${CLI_MODEL_OPTIONS.join(", ")}`).option("-M, --mode <mode>", "Task mode: plan or execute (default: execute)").action(
1173
+ (message, options) => {
1174
+ const mode = options.mode === "plan" ? "plan" : "execute";
1175
+ let model;
1176
+ if (options.model) {
1177
+ if (CLI_MODEL_OPTIONS.includes(options.model)) {
1178
+ model = options.model;
1179
+ } else {
1180
+ console.warn(
1181
+ `Warning: Model '${options.model}' is not recognized. Valid models are: ${CLI_MODEL_OPTIONS.join(", ")}. Using default model.`
1182
+ );
1183
+ model = void 0;
1184
+ }
1185
+ }
1186
+ render(
1187
+ /* @__PURE__ */ React9.createElement(QueryProvider, null, /* @__PURE__ */ React9.createElement(RootLayout, null, /* @__PURE__ */ React9.createElement(
1188
+ CreateCommand,
1189
+ {
1190
+ message,
1191
+ repo: options.repo,
1192
+ branch: options.branch,
1193
+ createNewBranch: options.newBranch,
1194
+ mode,
1195
+ model
1196
+ }
1197
+ )))
1198
+ );
1199
+ }
1200
+ );
1201
+ program.command("list").description("List all tasks in a non-interactive format").action(() => {
1202
+ render(
1203
+ /* @__PURE__ */ React9.createElement(QueryProvider, null, /* @__PURE__ */ React9.createElement(RootLayout, null, /* @__PURE__ */ React9.createElement(ListCommand, null)))
1204
+ );
1205
+ });
1206
+ program.command("mcp").description("Run an MCP server for the git repository").action(async () => {
1207
+ try {
1208
+ await startMCPServer();
1209
+ } catch (error) {
1210
+ console.error("Failed to start MCP server:", error);
1211
+ process.exit(1);
1212
+ }
1213
+ });
1214
+ program.parse();