@waniwani/cli 0.0.45 → 0.0.46-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import { Command } from "commander";
14
14
 
15
15
  // src/lib/config.ts
16
16
  import { existsSync } from "fs";
17
- import { mkdir, readFile, writeFile } from "fs/promises";
17
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
18
18
  import { join } from "path";
19
19
  import { z } from "zod";
20
20
  var LOCAL_CONFIG_DIR = ".waniwani";
@@ -22,6 +22,8 @@ var CONFIG_FILE_NAME = "settings.json";
22
22
  var LOCAL_DIR = join(process.cwd(), LOCAL_CONFIG_DIR);
23
23
  var LOCAL_FILE = join(LOCAL_DIR, CONFIG_FILE_NAME);
24
24
  var DEFAULT_API_URL = "https://app.waniwani.ai";
25
+ var CONFIG_DIR_MODE = 448;
26
+ var CONFIG_FILE_MODE = 384;
25
27
  var ConfigSchema = z.object({
26
28
  // Settings
27
29
  sessionId: z.string().nullable().default(null),
@@ -41,6 +43,10 @@ var Config = class {
41
43
  this.dir = LOCAL_DIR;
42
44
  this.file = LOCAL_FILE;
43
45
  }
46
+ async setSecurePermissions() {
47
+ await chmod(this.dir, CONFIG_DIR_MODE);
48
+ await chmod(this.file, CONFIG_FILE_MODE);
49
+ }
44
50
  async load() {
45
51
  if (!this.cache) {
46
52
  try {
@@ -55,15 +61,17 @@ var Config = class {
55
61
  }
56
62
  async save(data) {
57
63
  this.cache = data;
58
- await mkdir(this.dir, { recursive: true });
64
+ await mkdir(this.dir, { recursive: true, mode: CONFIG_DIR_MODE });
59
65
  await writeFile(this.file, JSON.stringify(data, null, " "));
66
+ await this.setSecurePermissions();
60
67
  }
61
68
  /**
62
69
  * Ensure the .waniwani directory exists in cwd.
63
70
  * Used by login to create config before saving tokens.
64
71
  */
65
72
  async ensureConfigDir() {
66
- await mkdir(this.dir, { recursive: true });
73
+ await mkdir(this.dir, { recursive: true, mode: CONFIG_DIR_MODE });
74
+ await chmod(this.dir, CONFIG_DIR_MODE);
67
75
  }
68
76
  /**
69
77
  * Check if a .waniwani config directory exists in cwd.
@@ -134,9 +142,11 @@ var config = new Config();
134
142
  async function initConfigAt(dir, overrides = {}) {
135
143
  const configDir = join(dir, LOCAL_CONFIG_DIR);
136
144
  const configPath = join(configDir, CONFIG_FILE_NAME);
137
- await mkdir(configDir, { recursive: true });
145
+ await mkdir(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
138
146
  const data = ConfigSchema.parse(overrides);
139
147
  await writeFile(configPath, JSON.stringify(data, null, " "), "utf-8");
148
+ await chmod(configDir, CONFIG_DIR_MODE);
149
+ await chmod(configPath, CONFIG_FILE_MODE);
140
150
  return { path: configPath, config: data };
141
151
  }
142
152
 
@@ -287,12 +297,16 @@ var configInitCommand = new Command("init").description("Initialize .waniwani co
287
297
  // src/commands/config/index.ts
288
298
  var configCommand = new Command2("config").description("Manage WaniWani configuration").addCommand(configInitCommand);
289
299
 
290
- // src/commands/login.ts
291
- import { spawn } from "child_process";
292
- import { createServer } from "http";
293
- import chalk3 from "chalk";
300
+ // src/commands/git-credential-helper.ts
301
+ import { readFileSync } from "fs";
302
+ import { join as join5 } from "path";
294
303
  import { Command as Command3 } from "commander";
295
- import ora from "ora";
304
+
305
+ // src/lib/git-auth.ts
306
+ import { execFileSync } from "child_process";
307
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "fs";
308
+ import { tmpdir } from "os";
309
+ import { join as join3 } from "path";
296
310
 
297
311
  // src/lib/auth.ts
298
312
  var AuthManager = class {
@@ -349,125 +363,581 @@ var AuthManager = class {
349
363
  };
350
364
  var auth = new AuthManager();
351
365
 
352
- // src/commands/login.ts
353
- var LOGO_LINES = [
354
- "\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
355
- "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551",
356
- "\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551",
357
- "\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
358
- "\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551",
359
- " \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D"
360
- ];
361
- var LOGO_COLORS = [
362
- "\x1B[38;5;223m",
363
- // light peach
364
- "\x1B[38;5;216m",
365
- // peach
366
- "\x1B[38;5;209m",
367
- // salmon
368
- "\x1B[38;5;203m",
369
- // coral
370
- "\x1B[38;5;167m",
371
- // warm red
372
- "\x1B[38;5;131m"
373
- // deep terracotta
374
- ];
375
- var RESET = "\x1B[0m";
376
- function showLogo() {
377
- console.log();
378
- for (let i = 0; i < LOGO_LINES.length; i++) {
379
- console.log(`${LOGO_COLORS[i]}${LOGO_LINES[i]}${RESET}`);
366
+ // src/lib/api.ts
367
+ var ApiError = class extends CLIError {
368
+ constructor(message, code, statusCode, details) {
369
+ super(message, code, details);
370
+ this.statusCode = statusCode;
371
+ this.name = "ApiError";
380
372
  }
381
- console.log();
382
- }
383
- var CALLBACK_PORT = 54321;
384
- var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
385
- var CLIENT_NAME = "waniwani-cli";
386
- function generateCodeVerifier() {
387
- const array = new Uint8Array(32);
388
- crypto.getRandomValues(array);
389
- return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
390
- }
391
- async function generateCodeChallenge(verifier) {
392
- const encoder = new TextEncoder();
393
- const data = encoder.encode(verifier);
394
- const hash = await crypto.subtle.digest("SHA-256", data);
395
- return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
396
- }
397
- function generateState() {
398
- const array = new Uint8Array(16);
399
- crypto.getRandomValues(array);
400
- return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
401
- }
402
- async function registerClient() {
403
- const apiUrl = await config.getApiUrl();
404
- const response = await fetch(`${apiUrl}/api/auth/oauth2/register`, {
405
- method: "POST",
406
- headers: {
407
- "Content-Type": "application/json"
408
- },
409
- body: JSON.stringify({
410
- client_name: CLIENT_NAME,
411
- redirect_uris: [CALLBACK_URL],
412
- grant_types: ["authorization_code", "refresh_token"],
413
- response_types: ["code"],
414
- token_endpoint_auth_method: "none"
415
- })
373
+ };
374
+ async function request(method, path, options) {
375
+ const {
376
+ body,
377
+ requireAuth = true,
378
+ headers: extraHeaders = {}
379
+ } = options || {};
380
+ const headers = {
381
+ "Content-Type": "application/json",
382
+ ...extraHeaders
383
+ };
384
+ if (requireAuth) {
385
+ const token = await auth.getAccessToken();
386
+ if (!token) {
387
+ throw new AuthError(
388
+ "Not logged in. Run 'waniwani login' to authenticate."
389
+ );
390
+ }
391
+ headers.Authorization = `Bearer ${token}`;
392
+ }
393
+ const baseUrl = await config.getApiUrl();
394
+ const url = `${baseUrl}${path}`;
395
+ const response = await fetch(url, {
396
+ method,
397
+ headers,
398
+ body: body ? JSON.stringify(body) : void 0
416
399
  });
417
- if (!response.ok) {
418
- const error = await response.json().catch(() => ({}));
419
- throw new CLIError(
420
- error.error_description || "Failed to register OAuth client",
421
- "CLIENT_REGISTRATION_FAILED"
400
+ if (response.status === 204) {
401
+ return void 0;
402
+ }
403
+ let data;
404
+ let rawBody;
405
+ try {
406
+ rawBody = await response.text();
407
+ data = JSON.parse(rawBody);
408
+ } catch {
409
+ throw new ApiError(
410
+ rawBody || `Request failed with status ${response.status}`,
411
+ "API_ERROR",
412
+ response.status,
413
+ { statusText: response.statusText }
422
414
  );
423
415
  }
424
- return response.json();
416
+ if (!response.ok || data.error) {
417
+ const errorObject = typeof data.error === "object" && data.error !== null ? data.error : void 0;
418
+ const errorString = typeof data.error === "string" ? data.error : void 0;
419
+ const errorMessage = errorObject?.message || data.message || errorString || rawBody || `Request failed with status ${response.status}`;
420
+ const errorCode = errorString || errorObject?.code || data.code || data.message || errorObject?.message || "API_ERROR";
421
+ const errorDetails = {
422
+ ...errorObject?.details,
423
+ statusText: response.statusText,
424
+ ...errorObject ? {} : { rawResponse: data }
425
+ };
426
+ const error = {
427
+ code: errorCode,
428
+ message: errorMessage,
429
+ details: errorDetails
430
+ };
431
+ if (response.status === 401) {
432
+ const refreshed = await auth.tryRefreshToken();
433
+ if (refreshed) {
434
+ return request(method, path, options);
435
+ }
436
+ throw new AuthError(
437
+ "Session expired. Run 'waniwani login' to re-authenticate."
438
+ );
439
+ }
440
+ throw new ApiError(
441
+ error.message,
442
+ error.code,
443
+ response.status,
444
+ error.details
445
+ );
446
+ }
447
+ return data.data;
425
448
  }
426
- async function openBrowser(url) {
427
- const [cmd, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", url] : ["xdg-open", url];
428
- spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
449
+ var api = {
450
+ get: (path, options) => request("GET", path, options),
451
+ post: (path, body, options) => request("POST", path, { body, ...options }),
452
+ delete: (path, options) => request("DELETE", path, options),
453
+ getBaseUrl: () => config.getApiUrl()
454
+ };
455
+
456
+ // src/lib/git-auth.ts
457
+ function getGitHubApiBaseUrl(remoteUrl) {
458
+ try {
459
+ const parsed = new URL(remoteUrl);
460
+ return parsed.hostname === "github.com" || parsed.hostname === "www.github.com" ? "https://api.github.com" : `${parsed.protocol}//${parsed.host}/api/v3`;
461
+ } catch {
462
+ return null;
463
+ }
429
464
  }
430
- async function waitForCallback(expectedState, timeoutMs = 3e5) {
431
- return new Promise((resolve, reject) => {
432
- let server = null;
433
- const sockets = /* @__PURE__ */ new Set();
434
- const timeout = setTimeout(() => {
435
- cleanup();
436
- reject(new CLIError("Login timed out", "LOGIN_TIMEOUT"));
437
- }, timeoutMs);
438
- const cleanup = () => {
439
- clearTimeout(timeout);
440
- for (const socket of sockets) {
441
- socket.destroy();
442
- }
443
- sockets.clear();
444
- server?.close();
445
- };
446
- const htmlResponse = (title, message, isSuccess) => `<!DOCTYPE html>
447
- <html>
448
- <head>
449
- <meta charset="UTF-8">
450
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
451
- <title>${title} - WaniWani</title>
452
- <style>
453
- * { margin: 0; padding: 0; box-sizing: border-box; }
454
- body {
455
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
456
- min-height: 100vh;
457
- display: flex;
458
- align-items: center;
459
- justify-content: center;
460
- background: #fafafa;
461
- position: relative;
462
- overflow: hidden;
465
+ function parseCloneUrlAuth(cloneUrl) {
466
+ try {
467
+ const parsed = new URL(cloneUrl);
468
+ const username = decodeURIComponent(parsed.username);
469
+ const password = decodeURIComponent(parsed.password);
470
+ if (username && password) {
471
+ parsed.username = "";
472
+ parsed.password = "";
473
+ const remoteUrl = parsed.toString();
474
+ return {
475
+ cloneUrl,
476
+ remoteUrl,
477
+ credentials: { username, password },
478
+ githubApiBaseUrl: getGitHubApiBaseUrl(remoteUrl)
479
+ };
463
480
  }
464
- .blob {
465
- position: absolute;
466
- border-radius: 50%;
467
- filter: blur(60px);
468
- pointer-events: none;
481
+ } catch {
482
+ }
483
+ return {
484
+ cloneUrl,
485
+ remoteUrl: cloneUrl,
486
+ credentials: null,
487
+ githubApiBaseUrl: null
488
+ };
489
+ }
490
+ async function getGitAuthContext(mcpId) {
491
+ try {
492
+ const gitAuth = await api.get(
493
+ `/api/mcp/repositories/${mcpId}/git-auth`
494
+ );
495
+ const parsedRemote = new URL(gitAuth.remoteUrl);
496
+ parsedRemote.username = gitAuth.username;
497
+ parsedRemote.password = gitAuth.token;
498
+ const cloneUrl = parsedRemote.toString();
499
+ return {
500
+ cloneUrl,
501
+ remoteUrl: gitAuth.remoteUrl,
502
+ credentials: {
503
+ username: gitAuth.username,
504
+ password: gitAuth.token
505
+ },
506
+ githubApiBaseUrl: getGitHubApiBaseUrl(gitAuth.remoteUrl)
507
+ };
508
+ } catch (error) {
509
+ if (error instanceof ApiError && error.statusCode !== 404) {
510
+ throw error;
469
511
  }
470
- .blob-1 {
512
+ const { cloneUrl } = await api.get(
513
+ `/api/mcp/repositories/${mcpId}/clone-url`
514
+ );
515
+ return parseCloneUrlAuth(cloneUrl);
516
+ }
517
+ }
518
+ function runGitWithCredentials(args, options) {
519
+ const { cwd, stdio = "ignore", credentials = null } = options ?? {};
520
+ if (!credentials) {
521
+ execFileSync("git", args, { cwd, stdio });
522
+ return;
523
+ }
524
+ const dir = mkdtempSync(join3(tmpdir(), "waniwani-askpass-"));
525
+ const askpassPath = join3(dir, "askpass.sh");
526
+ try {
527
+ writeFileSync(
528
+ askpassPath,
529
+ `#!/bin/sh
530
+ case "$1" in
531
+ *sername*) printf '%s\\n' "$WANIWANI_GIT_USERNAME" ;;
532
+ *assword*) printf '%s\\n' "$WANIWANI_GIT_PASSWORD" ;;
533
+ *) printf '\\n' ;;
534
+ esac
535
+ `,
536
+ "utf-8"
537
+ );
538
+ chmodSync(askpassPath, 448);
539
+ execFileSync("git", ["-c", "credential.helper=", ...args], {
540
+ cwd,
541
+ stdio,
542
+ env: {
543
+ ...process.env,
544
+ GIT_ASKPASS: askpassPath,
545
+ GIT_TERMINAL_PROMPT: "0",
546
+ WANIWANI_GIT_USERNAME: credentials.username,
547
+ WANIWANI_GIT_PASSWORD: credentials.password
548
+ }
549
+ });
550
+ } finally {
551
+ rmSync(dir, { recursive: true, force: true });
552
+ }
553
+ }
554
+ var REVOKE_TIMEOUT_MS = 5e3;
555
+ async function revokeGitHubInstallationToken(auth2) {
556
+ if (!auth2.credentials?.password || !auth2.githubApiBaseUrl) return;
557
+ const controller = new AbortController();
558
+ const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
559
+ try {
560
+ await fetch(`${auth2.githubApiBaseUrl}/installation/token`, {
561
+ method: "DELETE",
562
+ headers: {
563
+ Accept: "application/vnd.github+json",
564
+ Authorization: `Bearer ${auth2.credentials.password}`,
565
+ "X-GitHub-Api-Version": "2022-11-28"
566
+ },
567
+ signal: controller.signal
568
+ });
569
+ } catch {
570
+ } finally {
571
+ clearTimeout(timeout);
572
+ }
573
+ }
574
+
575
+ // src/lib/sync.ts
576
+ import { existsSync as existsSync3 } from "fs";
577
+ import { mkdir as mkdir2, readdir, readFile as readFile2, stat, writeFile as writeFile2 } from "fs/promises";
578
+ import { dirname, join as join4, relative } from "path";
579
+ import ignore from "ignore";
580
+
581
+ // src/lib/utils.ts
582
+ async function requireMcpId(mcpId) {
583
+ if (mcpId) return mcpId;
584
+ const configMcpId = await config.getMcpId();
585
+ if (!configMcpId) {
586
+ throw new McpError(
587
+ "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
588
+ );
589
+ }
590
+ return configMcpId;
591
+ }
592
+ async function requireSessionId() {
593
+ const sessionId = await config.getSessionId();
594
+ if (!sessionId) {
595
+ throw new McpError(
596
+ "No active session. Run 'waniwani mcp preview' to start development."
597
+ );
598
+ }
599
+ return sessionId;
600
+ }
601
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
602
+ ".png",
603
+ ".jpg",
604
+ ".jpeg",
605
+ ".gif",
606
+ ".ico",
607
+ ".webp",
608
+ ".svg",
609
+ ".woff",
610
+ ".woff2",
611
+ ".ttf",
612
+ ".eot",
613
+ ".otf",
614
+ ".zip",
615
+ ".tar",
616
+ ".gz",
617
+ ".pdf",
618
+ ".exe",
619
+ ".dll",
620
+ ".so",
621
+ ".dylib",
622
+ ".bin",
623
+ ".mp3",
624
+ ".mp4",
625
+ ".wav",
626
+ ".ogg",
627
+ ".webm"
628
+ ]);
629
+ function isBinaryPath(filePath) {
630
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
631
+ return BINARY_EXTENSIONS.has(ext);
632
+ }
633
+ function detectBinary(buffer) {
634
+ const sample = buffer.subarray(0, 8192);
635
+ return sample.includes(0);
636
+ }
637
+
638
+ // src/lib/sync.ts
639
+ var PROJECT_DIR = ".waniwani";
640
+ async function findProjectRoot(startDir) {
641
+ let current = startDir;
642
+ const root = dirname(current);
643
+ while (current !== root) {
644
+ if (existsSync3(join4(current, PROJECT_DIR))) {
645
+ return current;
646
+ }
647
+ const parent = dirname(current);
648
+ if (parent === current) break;
649
+ current = parent;
650
+ }
651
+ if (existsSync3(join4(current, PROJECT_DIR))) {
652
+ return current;
653
+ }
654
+ return null;
655
+ }
656
+ var DEFAULT_IGNORE_PATTERNS = [
657
+ ".waniwani",
658
+ ".git",
659
+ "node_modules",
660
+ ".DS_Store",
661
+ "*.log",
662
+ ".cache",
663
+ "dist",
664
+ "coverage",
665
+ ".turbo",
666
+ ".next",
667
+ ".nuxt",
668
+ ".vercel"
669
+ ];
670
+ var DEFAULT_ENV_IGNORE_PATTERNS = [".env", ".env.*"];
671
+ async function loadIgnorePatterns(projectRoot, options = {}) {
672
+ const { includeEnvFiles = false } = options;
673
+ const ig = ignore();
674
+ ig.add(DEFAULT_IGNORE_PATTERNS);
675
+ if (!includeEnvFiles) {
676
+ ig.add(DEFAULT_ENV_IGNORE_PATTERNS);
677
+ }
678
+ const gitignorePath = join4(projectRoot, ".gitignore");
679
+ if (existsSync3(gitignorePath)) {
680
+ try {
681
+ const content = await readFile2(gitignorePath, "utf-8");
682
+ ig.add(content);
683
+ } catch {
684
+ }
685
+ }
686
+ if (includeEnvFiles) {
687
+ ig.add(["!.env", "!.env.*"]);
688
+ }
689
+ return ig;
690
+ }
691
+ async function collectFiles(projectRoot, options = {}) {
692
+ const ig = await loadIgnorePatterns(projectRoot, options);
693
+ const files = [];
694
+ async function walk(dir) {
695
+ const entries = await readdir(dir, { withFileTypes: true });
696
+ for (const entry of entries) {
697
+ const fullPath = join4(dir, entry.name);
698
+ const relativePath = relative(projectRoot, fullPath);
699
+ if (ig.ignores(relativePath)) {
700
+ continue;
701
+ }
702
+ if (entry.isDirectory()) {
703
+ await walk(fullPath);
704
+ } else if (entry.isFile()) {
705
+ try {
706
+ const content = await readFile2(fullPath);
707
+ const isBinary = isBinaryPath(fullPath) || detectBinary(content);
708
+ files.push({
709
+ path: relativePath,
710
+ content: isBinary ? content.toString("base64") : content.toString("utf8"),
711
+ encoding: isBinary ? "base64" : "utf8"
712
+ });
713
+ } catch {
714
+ }
715
+ }
716
+ }
717
+ }
718
+ await walk(projectRoot);
719
+ return files;
720
+ }
721
+ async function pullFilesFromGithub(mcpId, targetDir) {
722
+ const result = await api.get(
723
+ `/api/mcp/repositories/${mcpId}/files`
724
+ );
725
+ const writtenFiles = [];
726
+ for (const file of result.files) {
727
+ const localPath = join4(targetDir, file.path);
728
+ const dir = dirname(localPath);
729
+ await mkdir2(dir, { recursive: true });
730
+ if (file.encoding === "base64") {
731
+ await writeFile2(localPath, Buffer.from(file.content, "base64"));
732
+ } else {
733
+ await writeFile2(localPath, file.content, "utf8");
734
+ }
735
+ writtenFiles.push(file.path);
736
+ }
737
+ return { count: writtenFiles.length, files: writtenFiles };
738
+ }
739
+ async function collectSingleFile(projectRoot, filePath) {
740
+ const fullPath = join4(projectRoot, filePath);
741
+ const relativePath = relative(projectRoot, fullPath);
742
+ if (!existsSync3(fullPath)) {
743
+ return null;
744
+ }
745
+ try {
746
+ const fileStat = await stat(fullPath);
747
+ if (!fileStat.isFile()) {
748
+ return null;
749
+ }
750
+ const content = await readFile2(fullPath);
751
+ const isBinary = isBinaryPath(fullPath) || detectBinary(content);
752
+ return {
753
+ path: relativePath,
754
+ content: isBinary ? content.toString("base64") : content.toString("utf8"),
755
+ encoding: isBinary ? "base64" : "utf8"
756
+ };
757
+ } catch {
758
+ return null;
759
+ }
760
+ }
761
+
762
+ // src/commands/git-credential-helper.ts
763
+ function parseCredentialInput(input) {
764
+ const fields = {};
765
+ for (const line of input.split("\n")) {
766
+ const trimmed = line.trim();
767
+ if (!trimmed) continue;
768
+ const eqIdx = trimmed.indexOf("=");
769
+ if (eqIdx > 0) {
770
+ fields[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
771
+ }
772
+ }
773
+ return fields;
774
+ }
775
+ var gitCredentialHelperCommand = new Command3("git-credential-helper").description("Git credential helper (used by git, not called directly)").argument("<operation>", "get, store, or erase").action(async (operation) => {
776
+ if (operation !== "get") {
777
+ process.exit(0);
778
+ }
779
+ try {
780
+ const input = readFileSync(0, "utf-8");
781
+ const fields = parseCredentialInput(input);
782
+ if (fields.protocol && fields.protocol !== "https") {
783
+ process.exit(0);
784
+ }
785
+ const projectRoot = await findProjectRoot(process.cwd());
786
+ if (!projectRoot) {
787
+ process.stderr.write(
788
+ "waniwani: not in a WaniWani project (no .waniwani/ found)\n"
789
+ );
790
+ process.exit(1);
791
+ }
792
+ const configPath = join5(projectRoot, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
793
+ const configData = JSON.parse(readFileSync(configPath, "utf-8"));
794
+ const mcpId = configData.mcpId;
795
+ if (!mcpId) {
796
+ process.stderr.write("waniwani: no mcpId in config\n");
797
+ process.exit(1);
798
+ }
799
+ const gitAuth = await getGitAuthContext(mcpId);
800
+ if (!gitAuth.credentials) {
801
+ process.stderr.write("waniwani: no credentials returned from API\n");
802
+ process.exit(1);
803
+ }
804
+ process.stdout.write(
805
+ `username=${gitAuth.credentials.username}
806
+ password=${gitAuth.credentials.password}
807
+ `
808
+ );
809
+ } catch (error) {
810
+ const message = error instanceof Error ? error.message : String(error);
811
+ process.stderr.write(`waniwani credential helper error: ${message}
812
+ `);
813
+ process.exit(1);
814
+ }
815
+ });
816
+
817
+ // src/commands/login.ts
818
+ import { spawn } from "child_process";
819
+ import { createServer } from "http";
820
+ import chalk3 from "chalk";
821
+ import { Command as Command4 } from "commander";
822
+ import ora from "ora";
823
+ var LOGO_LINES = [
824
+ "\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
825
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551",
826
+ "\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551",
827
+ "\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551",
828
+ "\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551",
829
+ " \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D"
830
+ ];
831
+ var LOGO_COLORS = [
832
+ "\x1B[38;5;223m",
833
+ // light peach
834
+ "\x1B[38;5;216m",
835
+ // peach
836
+ "\x1B[38;5;209m",
837
+ // salmon
838
+ "\x1B[38;5;203m",
839
+ // coral
840
+ "\x1B[38;5;167m",
841
+ // warm red
842
+ "\x1B[38;5;131m"
843
+ // deep terracotta
844
+ ];
845
+ var RESET = "\x1B[0m";
846
+ function showLogo() {
847
+ console.log();
848
+ for (let i = 0; i < LOGO_LINES.length; i++) {
849
+ console.log(`${LOGO_COLORS[i]}${LOGO_LINES[i]}${RESET}`);
850
+ }
851
+ console.log();
852
+ }
853
+ var CALLBACK_PORT = 54321;
854
+ var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
855
+ var CLIENT_NAME = "waniwani-cli";
856
+ function generateCodeVerifier() {
857
+ const array = new Uint8Array(32);
858
+ crypto.getRandomValues(array);
859
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
860
+ }
861
+ async function generateCodeChallenge(verifier) {
862
+ const encoder = new TextEncoder();
863
+ const data = encoder.encode(verifier);
864
+ const hash = await crypto.subtle.digest("SHA-256", data);
865
+ return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
866
+ }
867
+ function generateState() {
868
+ const array = new Uint8Array(16);
869
+ crypto.getRandomValues(array);
870
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
871
+ }
872
+ async function registerClient() {
873
+ const apiUrl = await config.getApiUrl();
874
+ const response = await fetch(`${apiUrl}/api/auth/oauth2/register`, {
875
+ method: "POST",
876
+ headers: {
877
+ "Content-Type": "application/json"
878
+ },
879
+ body: JSON.stringify({
880
+ client_name: CLIENT_NAME,
881
+ redirect_uris: [CALLBACK_URL],
882
+ grant_types: ["authorization_code", "refresh_token"],
883
+ response_types: ["code"],
884
+ token_endpoint_auth_method: "none"
885
+ })
886
+ });
887
+ if (!response.ok) {
888
+ const error = await response.json().catch(() => ({}));
889
+ throw new CLIError(
890
+ error.error_description || "Failed to register OAuth client",
891
+ "CLIENT_REGISTRATION_FAILED"
892
+ );
893
+ }
894
+ return response.json();
895
+ }
896
+ async function openBrowser(url) {
897
+ const [cmd, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", url] : ["xdg-open", url];
898
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
899
+ }
900
+ async function waitForCallback(expectedState, timeoutMs = 3e5) {
901
+ return new Promise((resolve, reject) => {
902
+ let server = null;
903
+ const sockets = /* @__PURE__ */ new Set();
904
+ const timeout = setTimeout(() => {
905
+ cleanup();
906
+ reject(new CLIError("Login timed out", "LOGIN_TIMEOUT"));
907
+ }, timeoutMs);
908
+ const cleanup = () => {
909
+ clearTimeout(timeout);
910
+ for (const socket of sockets) {
911
+ socket.destroy();
912
+ }
913
+ sockets.clear();
914
+ server?.close();
915
+ };
916
+ const htmlResponse = (title, message, isSuccess) => `<!DOCTYPE html>
917
+ <html>
918
+ <head>
919
+ <meta charset="UTF-8">
920
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
921
+ <title>${title} - WaniWani</title>
922
+ <style>
923
+ * { margin: 0; padding: 0; box-sizing: border-box; }
924
+ body {
925
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
926
+ min-height: 100vh;
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: center;
930
+ background: #fafafa;
931
+ position: relative;
932
+ overflow: hidden;
933
+ }
934
+ .blob {
935
+ position: absolute;
936
+ border-radius: 50%;
937
+ filter: blur(60px);
938
+ pointer-events: none;
939
+ }
940
+ .blob-1 {
471
941
  top: 0;
472
942
  left: 25%;
473
943
  width: 24rem;
@@ -655,7 +1125,7 @@ async function exchangeCodeForToken(code, codeVerifier, clientId, resource) {
655
1125
  }
656
1126
  return response.json();
657
1127
  }
658
- var loginCommand = new Command3("login").description("Log in to WaniWani").option("--no-browser", "Don't open the browser automatically").action(async (options, command) => {
1128
+ var loginCommand = new Command4("login").description("Log in to WaniWani").option("--no-browser", "Don't open the browser automatically").action(async (options, command) => {
659
1129
  const globalOptions = command.optsWithGlobals();
660
1130
  const json = globalOptions.json ?? false;
661
1131
  try {
@@ -734,288 +1204,90 @@ var loginCommand = new Command3("login").description("Log in to WaniWani").optio
734
1204
  await config.ensureConfigDir();
735
1205
  await auth.setTokens(
736
1206
  tokenResponse.access_token,
737
- tokenResponse.refresh_token,
738
- tokenResponse.expires_in,
739
- clientId
740
- );
741
- spinner.succeed("Logged in successfully!");
742
- if (json) {
743
- formatOutput({ success: true, loggedIn: true }, true);
744
- } else {
745
- console.log();
746
- formatSuccess("You're now logged in to WaniWani!", false);
747
- console.log();
748
- console.log("Get started:");
749
- console.log(
750
- " waniwani mcp create my-server Create a new MCP sandbox"
751
- );
752
- console.log(' waniwani task "Add a tool" Send tasks to Claude');
753
- console.log(
754
- " waniwani org list View your organizations"
755
- );
756
- }
757
- } catch (error) {
758
- handleError(error, json);
759
- process.exit(1);
760
- }
761
- });
762
-
763
- // src/commands/logout.ts
764
- import { Command as Command4 } from "commander";
765
- var logoutCommand = new Command4("logout").description("Log out from WaniWani").action(async (_options, command) => {
766
- const globalOptions = command.optsWithGlobals();
767
- const json = globalOptions.json ?? false;
768
- try {
769
- if (!await auth.isLoggedIn()) {
770
- if (json) {
771
- formatOutput({ alreadyLoggedOut: true }, true);
772
- } else {
773
- console.log("Not currently logged in.");
774
- }
775
- return;
776
- }
777
- await auth.clear();
778
- if (json) {
779
- formatOutput({ success: true }, true);
780
- } else {
781
- formatSuccess("You have been logged out.", false);
782
- }
783
- } catch (error) {
784
- handleError(error, json);
785
- process.exit(1);
786
- }
787
- });
788
-
789
- // src/commands/mcp/index.ts
790
- import { Command as Command21 } from "commander";
791
-
792
- // src/commands/mcp/clone.ts
793
- import { execFileSync as execFileSync2, execSync } from "child_process";
794
- import { existsSync as existsSync3 } from "fs";
795
- import { readFile as readFile2 } from "fs/promises";
796
- import { join as join4 } from "path";
797
- import { Command as Command5 } from "commander";
798
- import ora2 from "ora";
799
-
800
- // src/lib/api.ts
801
- var ApiError = class extends CLIError {
802
- constructor(message, code, statusCode, details) {
803
- super(message, code, details);
804
- this.statusCode = statusCode;
805
- this.name = "ApiError";
806
- }
807
- };
808
- async function request(method, path, options) {
809
- const {
810
- body,
811
- requireAuth = true,
812
- headers: extraHeaders = {}
813
- } = options || {};
814
- const headers = {
815
- "Content-Type": "application/json",
816
- ...extraHeaders
817
- };
818
- if (requireAuth) {
819
- const token = await auth.getAccessToken();
820
- if (!token) {
821
- throw new AuthError(
822
- "Not logged in. Run 'waniwani login' to authenticate."
823
- );
824
- }
825
- headers.Authorization = `Bearer ${token}`;
826
- }
827
- const baseUrl = await config.getApiUrl();
828
- const url = `${baseUrl}${path}`;
829
- const response = await fetch(url, {
830
- method,
831
- headers,
832
- body: body ? JSON.stringify(body) : void 0
833
- });
834
- if (response.status === 204) {
835
- return void 0;
836
- }
837
- let data;
838
- let rawBody;
839
- try {
840
- rawBody = await response.text();
841
- data = JSON.parse(rawBody);
842
- } catch {
843
- throw new ApiError(
844
- rawBody || `Request failed with status ${response.status}`,
845
- "API_ERROR",
846
- response.status,
847
- { statusText: response.statusText }
848
- );
849
- }
850
- if (!response.ok || data.error) {
851
- const errorMessage = data.error?.message || data.message || data.error || rawBody || `Request failed with status ${response.status}`;
852
- const errorCode = data.error?.code || data.code || "API_ERROR";
853
- const errorDetails = {
854
- ...data.error?.details,
855
- statusText: response.statusText,
856
- ...data.error ? {} : { rawResponse: data }
857
- };
858
- const error = {
859
- code: errorCode,
860
- message: errorMessage,
861
- details: errorDetails
862
- };
863
- if (response.status === 401) {
864
- const refreshed = await auth.tryRefreshToken();
865
- if (refreshed) {
866
- return request(method, path, options);
867
- }
868
- throw new AuthError(
869
- "Session expired. Run 'waniwani login' to re-authenticate."
1207
+ tokenResponse.refresh_token,
1208
+ tokenResponse.expires_in,
1209
+ clientId
1210
+ );
1211
+ spinner.succeed("Logged in successfully!");
1212
+ if (json) {
1213
+ formatOutput({ success: true, loggedIn: true }, true);
1214
+ } else {
1215
+ console.log();
1216
+ formatSuccess("You're now logged in to WaniWani!", false);
1217
+ console.log();
1218
+ console.log("Get started:");
1219
+ console.log(
1220
+ " waniwani mcp create my-server Create a new MCP sandbox"
1221
+ );
1222
+ console.log(' waniwani task "Add a tool" Send tasks to Claude');
1223
+ console.log(
1224
+ " waniwani org list View your organizations"
870
1225
  );
871
1226
  }
872
- throw new ApiError(
873
- error.message,
874
- error.code,
875
- response.status,
876
- error.details
877
- );
1227
+ } catch (error) {
1228
+ handleError(error, json);
1229
+ process.exit(1);
878
1230
  }
879
- return data.data;
880
- }
881
- var api = {
882
- get: (path, options) => request("GET", path, options),
883
- post: (path, body, options) => request("POST", path, { body, ...options }),
884
- delete: (path, options) => request("DELETE", path, options),
885
- getBaseUrl: () => config.getApiUrl()
886
- };
1231
+ });
887
1232
 
888
- // src/lib/git-auth.ts
889
- import { execFileSync } from "child_process";
890
- import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "fs";
891
- import { tmpdir } from "os";
892
- import { join as join3 } from "path";
893
- function getGitHubApiBaseUrl(remoteUrl) {
894
- try {
895
- const parsed = new URL(remoteUrl);
896
- return parsed.hostname === "github.com" || parsed.hostname === "www.github.com" ? "https://api.github.com" : `${parsed.protocol}//${parsed.host}/api/v3`;
897
- } catch {
898
- return null;
899
- }
900
- }
901
- function parseCloneUrlAuth(cloneUrl) {
1233
+ // src/commands/logout.ts
1234
+ import { Command as Command5 } from "commander";
1235
+ var logoutCommand = new Command5("logout").description("Log out from WaniWani").action(async (_options, command) => {
1236
+ const globalOptions = command.optsWithGlobals();
1237
+ const json = globalOptions.json ?? false;
902
1238
  try {
903
- const parsed = new URL(cloneUrl);
904
- const username = decodeURIComponent(parsed.username);
905
- const password = decodeURIComponent(parsed.password);
906
- if (username && password) {
907
- parsed.username = "";
908
- parsed.password = "";
909
- const remoteUrl = parsed.toString();
910
- return {
911
- cloneUrl,
912
- remoteUrl,
913
- credentials: { username, password },
914
- githubApiBaseUrl: getGitHubApiBaseUrl(remoteUrl)
915
- };
1239
+ if (!await auth.isLoggedIn()) {
1240
+ if (json) {
1241
+ formatOutput({ alreadyLoggedOut: true }, true);
1242
+ } else {
1243
+ console.log("Not currently logged in.");
1244
+ }
1245
+ return;
916
1246
  }
917
- } catch {
918
- }
919
- return {
920
- cloneUrl,
921
- remoteUrl: cloneUrl,
922
- credentials: null,
923
- githubApiBaseUrl: null
924
- };
925
- }
926
- async function getGitAuthContext(mcpId) {
927
- try {
928
- const gitAuth = await api.get(
929
- `/api/mcp/repositories/${mcpId}/git-auth`
930
- );
931
- const parsedRemote = new URL(gitAuth.remoteUrl);
932
- parsedRemote.username = gitAuth.username;
933
- parsedRemote.password = gitAuth.token;
934
- const cloneUrl = parsedRemote.toString();
935
- return {
936
- cloneUrl,
937
- remoteUrl: gitAuth.remoteUrl,
938
- credentials: {
939
- username: gitAuth.username,
940
- password: gitAuth.token
941
- },
942
- githubApiBaseUrl: getGitHubApiBaseUrl(gitAuth.remoteUrl)
943
- };
944
- } catch (error) {
945
- if (error instanceof ApiError && error.statusCode !== 404) {
946
- throw error;
1247
+ await auth.clear();
1248
+ if (json) {
1249
+ formatOutput({ success: true }, true);
1250
+ } else {
1251
+ formatSuccess("You have been logged out.", false);
947
1252
  }
948
- const { cloneUrl } = await api.get(
949
- `/api/mcp/repositories/${mcpId}/clone-url`
950
- );
951
- return parseCloneUrlAuth(cloneUrl);
952
- }
953
- }
954
- function runGitWithCredentials(args, options) {
955
- const { cwd, stdio = "ignore", credentials = null } = options ?? {};
956
- if (!credentials) {
957
- execFileSync("git", args, { cwd, stdio });
958
- return;
959
- }
960
- const dir = mkdtempSync(join3(tmpdir(), "waniwani-askpass-"));
961
- const askpassPath = join3(dir, "askpass.sh");
962
- try {
963
- writeFileSync(
964
- askpassPath,
965
- `#!/bin/sh
966
- case "$1" in
967
- *sername*) printf '%s\\n' "$WANIWANI_GIT_USERNAME" ;;
968
- *assword*) printf '%s\\n' "$WANIWANI_GIT_PASSWORD" ;;
969
- *) printf '\\n' ;;
970
- esac
971
- `,
972
- "utf-8"
973
- );
974
- chmodSync(askpassPath, 448);
975
- execFileSync("git", ["-c", "credential.helper=", ...args], {
976
- cwd,
977
- stdio,
978
- env: {
979
- ...process.env,
980
- GIT_ASKPASS: askpassPath,
981
- GIT_TERMINAL_PROMPT: "0",
982
- WANIWANI_GIT_USERNAME: credentials.username,
983
- WANIWANI_GIT_PASSWORD: credentials.password
984
- }
985
- });
986
- } finally {
987
- rmSync(dir, { recursive: true, force: true });
988
- }
989
- }
990
- var REVOKE_TIMEOUT_MS = 5e3;
991
- async function revokeGitHubInstallationToken(auth2) {
992
- if (!auth2.credentials?.password || !auth2.githubApiBaseUrl) return;
993
- const controller = new AbortController();
994
- const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
995
- try {
996
- await fetch(`${auth2.githubApiBaseUrl}/installation/token`, {
997
- method: "DELETE",
998
- headers: {
999
- Accept: "application/vnd.github+json",
1000
- Authorization: `Bearer ${auth2.credentials.password}`,
1001
- "X-GitHub-Api-Version": "2022-11-28"
1002
- },
1003
- signal: controller.signal
1004
- });
1005
- } catch {
1006
- } finally {
1007
- clearTimeout(timeout);
1253
+ } catch (error) {
1254
+ handleError(error, json);
1255
+ process.exit(1);
1008
1256
  }
1257
+ });
1258
+
1259
+ // src/commands/mcp/index.ts
1260
+ import { Command as Command21 } from "commander";
1261
+
1262
+ // src/commands/mcp/clone.ts
1263
+ import { execFileSync as execFileSync3, execSync } from "child_process";
1264
+ import { existsSync as existsSync4 } from "fs";
1265
+ import { readFile as readFile3 } from "fs/promises";
1266
+ import { join as join6 } from "path";
1267
+ import { Command as Command6 } from "commander";
1268
+ import ora2 from "ora";
1269
+
1270
+ // src/lib/credential-helper-setup.ts
1271
+ import { execFileSync as execFileSync2 } from "child_process";
1272
+ import { realpathSync } from "fs";
1273
+ function setupGitCredentialHelper(repoDir) {
1274
+ const binaryPath = realpathSync(process.argv[1]);
1275
+ const helperCommand = `!${process.execPath} ${binaryPath} git-credential-helper`;
1276
+ execFileSync2(
1277
+ "git",
1278
+ ["config", "--local", "credential.helper", helperCommand],
1279
+ { cwd: repoDir, stdio: "ignore" }
1280
+ );
1009
1281
  }
1010
1282
 
1011
1283
  // src/commands/mcp/clone.ts
1012
1284
  async function loadParentConfig(cwd) {
1013
- const parentConfigPath = join4(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1014
- if (!existsSync3(parentConfigPath)) {
1285
+ const parentConfigPath = join6(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1286
+ if (!existsSync4(parentConfigPath)) {
1015
1287
  return null;
1016
1288
  }
1017
1289
  try {
1018
- const content = await readFile2(parentConfigPath, "utf-8");
1290
+ const content = await readFile3(parentConfigPath, "utf-8");
1019
1291
  const config2 = JSON.parse(content);
1020
1292
  const { mcpId: _, sessionId: __, ...rest } = config2;
1021
1293
  return rest;
@@ -1033,15 +1305,15 @@ function checkGitInstalled() {
1033
1305
  );
1034
1306
  }
1035
1307
  }
1036
- var cloneCommand = new Command5("clone").description("Clone an existing MCP project to a local directory").argument("<name>", "Name of the MCP to clone").argument("[directory]", "Directory to clone into (defaults to MCP name)").action(
1308
+ var cloneCommand = new Command6("clone").description("Clone an existing MCP project to a local directory").argument("<name>", "Name of the MCP to clone").argument("[directory]", "Directory to clone into (defaults to MCP name)").action(
1037
1309
  async (name, directory, _options, command) => {
1038
1310
  const globalOptions = command.optsWithGlobals();
1039
1311
  const json = globalOptions.json ?? false;
1040
1312
  try {
1041
1313
  const cwd = process.cwd();
1042
1314
  const dirName = directory ?? name;
1043
- const projectDir = join4(cwd, dirName);
1044
- if (existsSync3(projectDir)) {
1315
+ const projectDir = join6(cwd, dirName);
1316
+ if (existsSync4(projectDir)) {
1045
1317
  if (json) {
1046
1318
  formatOutput(
1047
1319
  {
@@ -1083,7 +1355,7 @@ var cloneCommand = new Command5("clone").description("Clone an existing MCP proj
1083
1355
  } finally {
1084
1356
  await revokeGitHubInstallationToken(gitAuth);
1085
1357
  }
1086
- execFileSync2(
1358
+ execFileSync3(
1087
1359
  "git",
1088
1360
  ["remote", "set-url", "origin", mcp.githubCloneUrl],
1089
1361
  {
@@ -1096,6 +1368,7 @@ var cloneCommand = new Command5("clone").description("Clone an existing MCP proj
1096
1368
  ...parentConfig,
1097
1369
  mcpId: mcp.id
1098
1370
  });
1371
+ setupGitCredentialHelper(projectDir);
1099
1372
  spinner.succeed("Repository cloned");
1100
1373
  if (json) {
1101
1374
  formatOutput(
@@ -1113,6 +1386,7 @@ var cloneCommand = new Command5("clone").description("Clone an existing MCP proj
1113
1386
  console.log("Next steps:");
1114
1387
  console.log(` cd ${dirName}`);
1115
1388
  console.log(" waniwani mcp preview # Start developing");
1389
+ console.log(" git push origin main # Deploy");
1116
1390
  }
1117
1391
  } catch (error) {
1118
1392
  handleError(error, json);
@@ -1122,19 +1396,19 @@ var cloneCommand = new Command5("clone").description("Clone an existing MCP proj
1122
1396
  );
1123
1397
 
1124
1398
  // src/commands/mcp/create.ts
1125
- import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
1126
- import { existsSync as existsSync4 } from "fs";
1127
- import { readFile as readFile3 } from "fs/promises";
1128
- import { join as join5 } from "path";
1129
- import { Command as Command6 } from "commander";
1399
+ import { execFileSync as execFileSync4, execSync as execSync2 } from "child_process";
1400
+ import { existsSync as existsSync5 } from "fs";
1401
+ import { readFile as readFile4 } from "fs/promises";
1402
+ import { join as join7 } from "path";
1403
+ import { Command as Command7 } from "commander";
1130
1404
  import ora3 from "ora";
1131
1405
  async function loadParentConfig2(cwd) {
1132
- const parentConfigPath = join5(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1133
- if (!existsSync4(parentConfigPath)) {
1406
+ const parentConfigPath = join7(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1407
+ if (!existsSync5(parentConfigPath)) {
1134
1408
  return null;
1135
1409
  }
1136
1410
  try {
1137
- const content = await readFile3(parentConfigPath, "utf-8");
1411
+ const content = await readFile4(parentConfigPath, "utf-8");
1138
1412
  const config2 = JSON.parse(content);
1139
1413
  const { mcpId: _, sessionId: __, ...rest } = config2;
1140
1414
  return rest;
@@ -1152,13 +1426,13 @@ function checkGitInstalled2() {
1152
1426
  );
1153
1427
  }
1154
1428
  }
1155
- var createCommand = new Command6("create").description("Create a new MCP project").argument("<name>", "Name for the MCP project").action(async (name, _options, command) => {
1429
+ var createCommand = new Command7("create").description("Create a new MCP project").argument("<name>", "Name for the MCP project").action(async (name, _options, command) => {
1156
1430
  const globalOptions = command.optsWithGlobals();
1157
1431
  const json = globalOptions.json ?? false;
1158
1432
  try {
1159
1433
  const cwd = process.cwd();
1160
- const projectDir = join5(cwd, name);
1161
- if (existsSync4(projectDir)) {
1434
+ const projectDir = join7(cwd, name);
1435
+ if (existsSync5(projectDir)) {
1162
1436
  if (json) {
1163
1437
  formatOutput(
1164
1438
  {
@@ -1193,7 +1467,7 @@ var createCommand = new Command6("create").description("Create a new MCP project
1193
1467
  } finally {
1194
1468
  await revokeGitHubInstallationToken(gitAuth);
1195
1469
  }
1196
- execFileSync3(
1470
+ execFileSync4(
1197
1471
  "git",
1198
1472
  ["remote", "set-url", "origin", result.githubCloneUrl],
1199
1473
  {
@@ -1206,6 +1480,7 @@ var createCommand = new Command6("create").description("Create a new MCP project
1206
1480
  ...parentConfig,
1207
1481
  mcpId: result.id
1208
1482
  });
1483
+ setupGitCredentialHelper(projectDir);
1209
1484
  spinner.succeed("MCP project created");
1210
1485
  if (json) {
1211
1486
  formatOutput(
@@ -1223,6 +1498,7 @@ var createCommand = new Command6("create").description("Create a new MCP project
1223
1498
  console.log("Next steps:");
1224
1499
  console.log(` cd ${name}`);
1225
1500
  console.log(" waniwani mcp preview # Start developing");
1501
+ console.log(" git push origin main # Deploy");
1226
1502
  }
1227
1503
  } catch (error) {
1228
1504
  handleError(error, json);
@@ -1233,68 +1509,9 @@ var createCommand = new Command6("create").description("Create a new MCP project
1233
1509
  // src/commands/mcp/delete.ts
1234
1510
  import { confirm } from "@inquirer/prompts";
1235
1511
  import chalk4 from "chalk";
1236
- import { Command as Command7 } from "commander";
1237
- import ora4 from "ora";
1238
-
1239
- // src/lib/utils.ts
1240
- async function requireMcpId(mcpId) {
1241
- if (mcpId) return mcpId;
1242
- const configMcpId = await config.getMcpId();
1243
- if (!configMcpId) {
1244
- throw new McpError(
1245
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1246
- );
1247
- }
1248
- return configMcpId;
1249
- }
1250
- async function requireSessionId() {
1251
- const sessionId = await config.getSessionId();
1252
- if (!sessionId) {
1253
- throw new McpError(
1254
- "No active session. Run 'waniwani mcp preview' to start development."
1255
- );
1256
- }
1257
- return sessionId;
1258
- }
1259
- var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1260
- ".png",
1261
- ".jpg",
1262
- ".jpeg",
1263
- ".gif",
1264
- ".ico",
1265
- ".webp",
1266
- ".svg",
1267
- ".woff",
1268
- ".woff2",
1269
- ".ttf",
1270
- ".eot",
1271
- ".otf",
1272
- ".zip",
1273
- ".tar",
1274
- ".gz",
1275
- ".pdf",
1276
- ".exe",
1277
- ".dll",
1278
- ".so",
1279
- ".dylib",
1280
- ".bin",
1281
- ".mp3",
1282
- ".mp4",
1283
- ".wav",
1284
- ".ogg",
1285
- ".webm"
1286
- ]);
1287
- function isBinaryPath(filePath) {
1288
- const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1289
- return BINARY_EXTENSIONS.has(ext);
1290
- }
1291
- function detectBinary(buffer) {
1292
- const sample = buffer.subarray(0, 8192);
1293
- return sample.includes(0);
1294
- }
1295
-
1296
- // src/commands/mcp/delete.ts
1297
- var deleteCommand = new Command7("delete").description("Delete the MCP (includes all associated resources)").option("--mcp-id <id>", "Specific MCP ID").option("--force", "Skip confirmation prompt").action(async (options, command) => {
1512
+ import { Command as Command8 } from "commander";
1513
+ import ora4 from "ora";
1514
+ var deleteCommand = new Command8("delete").description("Delete the MCP (includes all associated resources)").option("--mcp-id <id>", "Specific MCP ID").option("--force", "Skip confirmation prompt").action(async (options, command) => {
1298
1515
  const globalOptions = command.optsWithGlobals();
1299
1516
  const json = globalOptions.json ?? false;
1300
1517
  try {
@@ -1338,13 +1555,13 @@ var deleteCommand = new Command7("delete").description("Delete the MCP (includes
1338
1555
  });
1339
1556
 
1340
1557
  // src/commands/mcp/file/index.ts
1341
- import { Command as Command11 } from "commander";
1558
+ import { Command as Command12 } from "commander";
1342
1559
 
1343
1560
  // src/commands/mcp/file/list.ts
1344
1561
  import chalk5 from "chalk";
1345
- import { Command as Command8 } from "commander";
1562
+ import { Command as Command9 } from "commander";
1346
1563
  import ora5 from "ora";
1347
- var listCommand = new Command8("list").description("List files in the MCP sandbox").argument("[path]", "Directory path (defaults to /app)", "/app").option("--mcp-id <id>", "Specific MCP ID").action(async (path, options, command) => {
1564
+ var listCommand = new Command9("list").description("List files in the MCP sandbox").argument("[path]", "Directory path (defaults to /app)", "/app").option("--mcp-id <id>", "Specific MCP ID").action(async (path, options, command) => {
1348
1565
  const globalOptions = command.optsWithGlobals();
1349
1566
  const json = globalOptions.json ?? false;
1350
1567
  try {
@@ -1386,10 +1603,10 @@ function formatSize(bytes) {
1386
1603
  }
1387
1604
 
1388
1605
  // src/commands/mcp/file/read.ts
1389
- import { writeFile as writeFile2 } from "fs/promises";
1390
- import { Command as Command9 } from "commander";
1606
+ import { writeFile as writeFile3 } from "fs/promises";
1607
+ import { Command as Command10 } from "commander";
1391
1608
  import ora6 from "ora";
1392
- var readCommand = new Command9("read").description("Read a file from the MCP sandbox").argument("<path>", "Path in sandbox (e.g., /app/src/index.ts)").option("--mcp-id <id>", "Specific MCP ID").option("--output <file>", "Write to local file instead of stdout").option("--base64", "Output as base64 (for binary files)").action(async (path, options, command) => {
1609
+ var readCommand = new Command10("read").description("Read a file from the MCP sandbox").argument("<path>", "Path in sandbox (e.g., /app/src/index.ts)").option("--mcp-id <id>", "Specific MCP ID").option("--output <file>", "Write to local file instead of stdout").option("--base64", "Output as base64 (for binary files)").action(async (path, options, command) => {
1393
1610
  const globalOptions = command.optsWithGlobals();
1394
1611
  const json = globalOptions.json ?? false;
1395
1612
  try {
@@ -1406,7 +1623,7 @@ var readCommand = new Command9("read").description("Read a file from the MCP san
1406
1623
  }
1407
1624
  if (options.output) {
1408
1625
  const buffer = result.encoding === "base64" ? Buffer.from(result.content, "base64") : Buffer.from(result.content, "utf8");
1409
- await writeFile2(options.output, buffer);
1626
+ await writeFile3(options.output, buffer);
1410
1627
  if (json) {
1411
1628
  formatOutput({ path, savedTo: options.output }, true);
1412
1629
  } else {
@@ -1427,10 +1644,10 @@ var readCommand = new Command9("read").description("Read a file from the MCP san
1427
1644
  });
1428
1645
 
1429
1646
  // src/commands/mcp/file/write.ts
1430
- import { readFile as readFile4 } from "fs/promises";
1431
- import { Command as Command10 } from "commander";
1647
+ import { readFile as readFile5 } from "fs/promises";
1648
+ import { Command as Command11 } from "commander";
1432
1649
  import ora7 from "ora";
1433
- var writeCommand = new Command10("write").description("Write a file to the MCP sandbox").argument("<path>", "Path in sandbox (e.g., /app/src/index.ts)").option("--mcp-id <id>", "Specific MCP ID").option("--content <content>", "Content to write").option("--file <localFile>", "Local file to upload").option("--base64", "Treat content as base64 encoded").action(async (path, options, command) => {
1650
+ var writeCommand = new Command11("write").description("Write a file to the MCP sandbox").argument("<path>", "Path in sandbox (e.g., /app/src/index.ts)").option("--mcp-id <id>", "Specific MCP ID").option("--content <content>", "Content to write").option("--file <localFile>", "Local file to upload").option("--base64", "Treat content as base64 encoded").action(async (path, options, command) => {
1434
1651
  const globalOptions = command.optsWithGlobals();
1435
1652
  const json = globalOptions.json ?? false;
1436
1653
  try {
@@ -1444,7 +1661,7 @@ var writeCommand = new Command10("write").description("Write a file to the MCP s
1444
1661
  encoding = "base64";
1445
1662
  }
1446
1663
  } else if (options.file) {
1447
- const fileBuffer = await readFile4(options.file);
1664
+ const fileBuffer = await readFile5(options.file);
1448
1665
  if (options.base64) {
1449
1666
  content = fileBuffer.toString("base64");
1450
1667
  encoding = "base64";
@@ -1477,13 +1694,13 @@ var writeCommand = new Command10("write").description("Write a file to the MCP s
1477
1694
  });
1478
1695
 
1479
1696
  // src/commands/mcp/file/index.ts
1480
- var fileCommand = new Command11("file").description("File operations in MCP sandbox").addCommand(readCommand).addCommand(writeCommand).addCommand(listCommand);
1697
+ var fileCommand = new Command12("file").description("File operations in MCP sandbox").addCommand(readCommand).addCommand(writeCommand).addCommand(listCommand);
1481
1698
 
1482
1699
  // src/commands/mcp/list.ts
1483
1700
  import chalk6 from "chalk";
1484
- import { Command as Command12 } from "commander";
1701
+ import { Command as Command13 } from "commander";
1485
1702
  import ora8 from "ora";
1486
- var listCommand2 = new Command12("list").description("List all MCPs in your organization").action(async (_options, command) => {
1703
+ var listCommand2 = new Command13("list").description("List all MCPs in your organization").action(async (_options, command) => {
1487
1704
  const globalOptions = command.optsWithGlobals();
1488
1705
  const json = globalOptions.json ?? false;
1489
1706
  try {
@@ -1541,9 +1758,9 @@ var listCommand2 = new Command12("list").description("List all MCPs in your orga
1541
1758
 
1542
1759
  // src/commands/mcp/logs.ts
1543
1760
  import chalk7 from "chalk";
1544
- import { Command as Command13 } from "commander";
1761
+ import { Command as Command14 } from "commander";
1545
1762
  import ora9 from "ora";
1546
- var logsCommand = new Command13("logs").description("Stream logs from the MCP server").argument("[cmdId]", "Command ID (defaults to running server)").option("--mcp-id <id>", "Specific MCP ID").option("-f, --follow", "Keep streaming logs (default)", true).option("--no-follow", "Fetch logs and exit").action(async (cmdIdArg, options, command) => {
1763
+ var logsCommand = new Command14("logs").description("Stream logs from the MCP server").argument("[cmdId]", "Command ID (defaults to running server)").option("--mcp-id <id>", "Specific MCP ID").option("-f, --follow", "Keep streaming logs (default)", true).option("--no-follow", "Fetch logs and exit").action(async (cmdIdArg, options, command) => {
1547
1764
  const globalOptions = command.optsWithGlobals();
1548
1765
  const json = globalOptions.json ?? false;
1549
1766
  let reader;
@@ -1683,8 +1900,10 @@ Error: ${event.error}`));
1683
1900
  });
1684
1901
 
1685
1902
  // src/commands/mcp/preview.ts
1903
+ import { existsSync as existsSync6 } from "fs";
1904
+ import { join as join8 } from "path";
1686
1905
  import { watch } from "chokidar";
1687
- import { Command as Command14 } from "commander";
1906
+ import { Command as Command15, InvalidArgumentError } from "commander";
1688
1907
  import ora10 from "ora";
1689
1908
 
1690
1909
  // src/lib/async.ts
@@ -1707,134 +1926,172 @@ async function withTimeout(promise, timeoutMs, options) {
1707
1926
  }
1708
1927
  }
1709
1928
 
1710
- // src/lib/sync.ts
1711
- import { existsSync as existsSync5 } from "fs";
1712
- import { mkdir as mkdir2, readdir, readFile as readFile5, stat, writeFile as writeFile3 } from "fs/promises";
1713
- import { dirname, join as join6, relative } from "path";
1714
- import ignore from "ignore";
1715
- var PROJECT_DIR = ".waniwani";
1716
- async function findProjectRoot(startDir) {
1717
- let current = startDir;
1718
- const root = dirname(current);
1719
- while (current !== root) {
1720
- if (existsSync5(join6(current, PROJECT_DIR))) {
1721
- return current;
1722
- }
1723
- const parent = dirname(current);
1724
- if (parent === current) break;
1725
- current = parent;
1929
+ // src/commands/mcp/preview.ts
1930
+ var SHUTDOWN_MAX_WAIT_MS = 3e3;
1931
+ var SHUTDOWN_STEP_TIMEOUT_MS = 1200;
1932
+ var DEV_SERVER_VERIFY_ATTEMPTS = 4;
1933
+ var DEV_SERVER_VERIFY_INTERVAL_MS = 750;
1934
+ var DEFAULT_DEV_SERVER_MONITOR_INTERVAL_MS = 6e4;
1935
+ var MAX_INSTALL_TIMEOUT_MS = 3e5;
1936
+ function resolveSessionInfo(response) {
1937
+ const maybeLegacy = response;
1938
+ if (maybeLegacy?.sandbox?.id && maybeLegacy?.previewUrl) {
1939
+ return {
1940
+ id: maybeLegacy.sandbox.id,
1941
+ previewUrl: maybeLegacy.previewUrl,
1942
+ sandboxId: maybeLegacy.sandbox.sandboxId
1943
+ };
1726
1944
  }
1727
- if (existsSync5(join6(current, PROJECT_DIR))) {
1728
- return current;
1945
+ const maybeSession = response;
1946
+ if (maybeSession?.id && maybeSession?.previewUrl) {
1947
+ return {
1948
+ id: maybeSession.id,
1949
+ previewUrl: maybeSession.previewUrl,
1950
+ sandboxId: maybeSession.sandboxId
1951
+ };
1729
1952
  }
1730
- return null;
1953
+ throw new CLIError("Invalid session response from API", "SESSION_ERROR");
1731
1954
  }
1732
- var DEFAULT_IGNORE_PATTERNS = [
1733
- ".waniwani",
1734
- ".git",
1735
- "node_modules",
1736
- ".env",
1737
- ".env.*",
1738
- ".DS_Store",
1739
- "*.log",
1740
- ".cache",
1741
- "dist",
1742
- "coverage",
1743
- ".turbo",
1744
- ".next",
1745
- ".nuxt",
1746
- ".vercel"
1747
- ];
1748
- async function loadIgnorePatterns(projectRoot) {
1749
- const ig = ignore();
1750
- ig.add(DEFAULT_IGNORE_PATTERNS);
1751
- const gitignorePath = join6(projectRoot, ".gitignore");
1752
- if (existsSync5(gitignorePath)) {
1753
- try {
1754
- const content = await readFile5(gitignorePath, "utf-8");
1755
- ig.add(content);
1756
- } catch {
1757
- }
1955
+ function detectPackageManager(projectRoot) {
1956
+ if (existsSync6(join8(projectRoot, "bun.lock")) || existsSync6(join8(projectRoot, "bun.lockb"))) {
1957
+ return "bun";
1758
1958
  }
1759
- return ig;
1760
- }
1761
- async function collectFiles(projectRoot) {
1762
- const ig = await loadIgnorePatterns(projectRoot);
1763
- const files = [];
1764
- async function walk(dir) {
1765
- const entries = await readdir(dir, { withFileTypes: true });
1766
- for (const entry of entries) {
1767
- const fullPath = join6(dir, entry.name);
1768
- const relativePath = relative(projectRoot, fullPath);
1769
- if (ig.ignores(relativePath)) {
1770
- continue;
1771
- }
1772
- if (entry.isDirectory()) {
1773
- await walk(fullPath);
1774
- } else if (entry.isFile()) {
1775
- try {
1776
- const content = await readFile5(fullPath);
1777
- const isBinary = isBinaryPath(fullPath) || detectBinary(content);
1778
- files.push({
1779
- path: relativePath,
1780
- content: isBinary ? content.toString("base64") : content.toString("utf8"),
1781
- encoding: isBinary ? "base64" : "utf8"
1782
- });
1783
- } catch {
1784
- }
1785
- }
1786
- }
1959
+ if (existsSync6(join8(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
1960
+ if (existsSync6(join8(projectRoot, "yarn.lock"))) return "yarn";
1961
+ if (existsSync6(join8(projectRoot, "package-lock.json")) || existsSync6(join8(projectRoot, "npm-shrinkwrap.json"))) {
1962
+ return "npm-ci";
1787
1963
  }
1788
- await walk(projectRoot);
1789
- return files;
1964
+ return "npm";
1790
1965
  }
1791
- async function pullFilesFromGithub(mcpId, targetDir) {
1792
- const result = await api.get(
1793
- `/api/mcp/repositories/${mcpId}/files`
1794
- );
1795
- const writtenFiles = [];
1796
- for (const file of result.files) {
1797
- const localPath = join6(targetDir, file.path);
1798
- const dir = dirname(localPath);
1799
- await mkdir2(dir, { recursive: true });
1800
- if (file.encoding === "base64") {
1801
- await writeFile3(localPath, Buffer.from(file.content, "base64"));
1802
- } else {
1803
- await writeFile3(localPath, file.content, "utf8");
1804
- }
1805
- writtenFiles.push(file.path);
1966
+ function getInstallCommand(pm) {
1967
+ switch (pm) {
1968
+ case "bun":
1969
+ return { command: "bun", args: ["install", "--frozen-lockfile"] };
1970
+ case "pnpm":
1971
+ return {
1972
+ command: "pnpm",
1973
+ args: ["install", "--frozen-lockfile", "--prefer-offline"]
1974
+ };
1975
+ case "yarn":
1976
+ return {
1977
+ command: "yarn",
1978
+ args: ["install", "--frozen-lockfile", "--prefer-offline"]
1979
+ };
1980
+ case "npm-ci":
1981
+ return {
1982
+ command: "npm",
1983
+ args: ["ci", "--no-audit", "--no-fund", "--prefer-offline"]
1984
+ };
1985
+ default:
1986
+ return {
1987
+ command: "npm",
1988
+ args: ["install", "--no-audit", "--no-fund"]
1989
+ };
1806
1990
  }
1807
- return { count: writtenFiles.length, files: writtenFiles };
1808
1991
  }
1809
- async function collectSingleFile(projectRoot, filePath) {
1810
- const fullPath = join6(projectRoot, filePath);
1811
- const relativePath = relative(projectRoot, fullPath);
1812
- if (!existsSync5(fullPath)) {
1813
- return null;
1992
+ function getNonFrozenInstallCommand(pm) {
1993
+ switch (pm) {
1994
+ case "bun":
1995
+ return { command: "bun", args: ["install"] };
1996
+ case "pnpm":
1997
+ return { command: "pnpm", args: ["install", "--prefer-offline"] };
1998
+ case "yarn":
1999
+ return { command: "yarn", args: ["install", "--prefer-offline"] };
2000
+ case "npm-ci":
2001
+ return { command: "npm", args: ["install", "--no-audit", "--no-fund"] };
2002
+ default:
2003
+ return null;
1814
2004
  }
2005
+ }
2006
+ function shouldRetryWithoutFrozenLockfile(result) {
2007
+ const output = `${result.stderr}
2008
+ ${result.stdout}`.toLowerCase();
2009
+ const npmLockMismatchDetected = output.includes("update your lock file") && output.includes("npm install") || output.includes("package-lock.json") && output.includes("not in sync") || output.includes("package-lock.json") && output.includes("are not in sync") || output.includes("npm ci") && output.includes("can only install packages") && output.includes("package-lock.json");
2010
+ return output.includes("frozen-lockfile") || output.includes("lockfile had changes") || output.includes("lockfile is frozen") || output.includes("lockfile") && output.includes("out of date") || npmLockMismatchDetected;
2011
+ }
2012
+ function isAlreadyRunningServerError(error) {
2013
+ const normalized = getNormalizedError(error);
2014
+ return normalized.includes("already_running") || normalized.includes("already running");
2015
+ }
2016
+ function isServerNotRunningError(error) {
2017
+ const normalized = getNormalizedError(error);
2018
+ return normalized.includes("server_not_running") || normalized.includes("server not running");
2019
+ }
2020
+ function getNormalizedError(error) {
2021
+ if (!(error instanceof CLIError)) return "";
2022
+ const details = JSON.stringify(error.details ?? {}).toLowerCase();
2023
+ return `${error.code} ${error.message} ${details}`.toLowerCase();
2024
+ }
2025
+ async function getServerStatusOrNull(sessionId) {
1815
2026
  try {
1816
- const fileStat = await stat(fullPath);
1817
- if (!fileStat.isFile()) {
2027
+ return await api.get(
2028
+ `/api/mcp/sessions/${sessionId}/server`
2029
+ );
2030
+ } catch (error) {
2031
+ if (isServerNotRunningError(error)) {
1818
2032
  return null;
1819
2033
  }
1820
- const content = await readFile5(fullPath);
1821
- const isBinary = isBinaryPath(fullPath) || detectBinary(content);
1822
- return {
1823
- path: relativePath,
1824
- content: isBinary ? content.toString("base64") : content.toString("utf8"),
1825
- encoding: isBinary ? "base64" : "utf8"
1826
- };
2034
+ throw error;
2035
+ }
2036
+ }
2037
+ function getDevCommand(pm) {
2038
+ switch (pm) {
2039
+ case "bun":
2040
+ return "bun run dev";
2041
+ case "pnpm":
2042
+ return "pnpm run dev";
2043
+ case "yarn":
2044
+ return "yarn run dev";
2045
+ default:
2046
+ return "npm run dev";
2047
+ }
2048
+ }
2049
+ function sleep(ms) {
2050
+ return new Promise((resolve) => {
2051
+ setTimeout(resolve, ms);
2052
+ });
2053
+ }
2054
+ function parseStatusPollIntervalMs(value) {
2055
+ const parsed = Number(value);
2056
+ if (!Number.isInteger(parsed) || parsed < 500) {
2057
+ throw new InvalidArgumentError(
2058
+ "Status poll interval must be an integer >= 500 milliseconds."
2059
+ );
2060
+ }
2061
+ return parsed;
2062
+ }
2063
+ async function getCommandOutputOrNull(sessionId, cmdId) {
2064
+ try {
2065
+ return await api.get(
2066
+ `/api/mcp/sessions/${sessionId}/commands/${cmdId}`
2067
+ );
1827
2068
  } catch {
1828
2069
  return null;
1829
2070
  }
1830
2071
  }
1831
-
1832
- // src/commands/mcp/preview.ts
1833
- var SHUTDOWN_MAX_WAIT_MS = 3e3;
1834
- var SHUTDOWN_STEP_TIMEOUT_MS = 1200;
1835
- var previewCommand = new Command14("preview").description("Start live development with sandbox and file watching").option("--mcp-id <id>", "Specific MCP ID").option("--no-watch", "Skip file watching").option("--no-logs", "Don't stream logs to terminal").action(async (options, command) => {
2072
+ async function cleanupPreviewSession(sessionId) {
2073
+ await withTimeout(
2074
+ api.post(`/api/mcp/sessions/${sessionId}/server`, { action: "stop" }).catch(() => void 0),
2075
+ SHUTDOWN_STEP_TIMEOUT_MS
2076
+ );
2077
+ await withTimeout(
2078
+ api.delete(`/api/mcp/sessions/${sessionId}`).catch(() => void 0),
2079
+ SHUTDOWN_STEP_TIMEOUT_MS
2080
+ );
2081
+ await withTimeout(
2082
+ config.setSessionId(null).catch(() => void 0),
2083
+ SHUTDOWN_STEP_TIMEOUT_MS
2084
+ );
2085
+ }
2086
+ var previewCommand = new Command15("preview").description("Start live development with sandbox and file watching").option("--mcp-id <id>", "Specific MCP ID").option("--no-watch", "Skip file watching").option("--no-logs", "Don't stream logs to terminal").option(
2087
+ "--status-poll-interval-ms <ms>",
2088
+ "Watch-mode server status polling interval in milliseconds (default: 60000)",
2089
+ parseStatusPollIntervalMs,
2090
+ DEFAULT_DEV_SERVER_MONITOR_INTERVAL_MS
2091
+ ).action(async (options, command) => {
1836
2092
  const globalOptions = command.optsWithGlobals();
1837
2093
  const json = globalOptions.json ?? false;
2094
+ const statusPollIntervalMs = options.statusPollIntervalMs ?? DEFAULT_DEV_SERVER_MONITOR_INTERVAL_MS;
1838
2095
  try {
1839
2096
  const projectRoot = await findProjectRoot(process.cwd());
1840
2097
  if (!projectRoot) {
@@ -1857,13 +2114,16 @@ var previewCommand = new Command14("preview").description("Start live developmen
1857
2114
  spinner.text = "Starting session...";
1858
2115
  let sessionId;
1859
2116
  let previewUrl;
2117
+ let sandboxId;
1860
2118
  try {
1861
2119
  const sessionResponse = await api.post(
1862
2120
  `/api/mcp/repositories/${mcpId}/session`,
1863
2121
  {}
1864
2122
  );
1865
- sessionId = sessionResponse.sandbox.id;
1866
- previewUrl = sessionResponse.previewUrl;
2123
+ const sessionInfo = resolveSessionInfo(sessionResponse);
2124
+ sessionId = sessionInfo.id;
2125
+ previewUrl = sessionInfo.previewUrl;
2126
+ sandboxId = sessionInfo.sandboxId;
1867
2127
  } catch {
1868
2128
  const existing = await api.get(
1869
2129
  `/api/mcp/repositories/${mcpId}/session`
@@ -1871,33 +2131,156 @@ var previewCommand = new Command14("preview").description("Start live developmen
1871
2131
  if (!existing) {
1872
2132
  throw new CLIError("Failed to start session", "SESSION_ERROR");
1873
2133
  }
1874
- sessionId = existing.id;
1875
- previewUrl = existing.previewUrl;
2134
+ const sessionInfo = resolveSessionInfo(existing);
2135
+ sessionId = sessionInfo.id;
2136
+ previewUrl = sessionInfo.previewUrl;
2137
+ sandboxId = sessionInfo.sandboxId;
1876
2138
  }
1877
2139
  await config.setSessionId(sessionId);
1878
2140
  spinner.text = "Syncing files to sandbox...";
1879
- const files = await collectFiles(projectRoot);
2141
+ const files = await collectFiles(projectRoot, { includeEnvFiles: true });
1880
2142
  if (files.length > 0) {
1881
2143
  await api.post(
1882
2144
  `/api/mcp/sessions/${sessionId}/files`,
1883
2145
  { files }
1884
2146
  );
1885
2147
  }
2148
+ const packageManager = detectPackageManager(projectRoot);
2149
+ let installCommand = getInstallCommand(packageManager);
2150
+ spinner.text = `Installing dependencies (${installCommand.command})...`;
2151
+ let installResult = await api.post(
2152
+ `/api/mcp/sessions/${sessionId}/commands`,
2153
+ {
2154
+ command: installCommand.command,
2155
+ args: installCommand.args,
2156
+ timeout: MAX_INSTALL_TIMEOUT_MS
2157
+ }
2158
+ );
2159
+ if (installResult.exitCode !== 0) {
2160
+ const nonFrozenInstallCommand = getNonFrozenInstallCommand(packageManager);
2161
+ if (nonFrozenInstallCommand && shouldRetryWithoutFrozenLockfile(installResult)) {
2162
+ installCommand = nonFrozenInstallCommand;
2163
+ spinner.text = `Retrying install without frozen lockfile (${installCommand.command})...`;
2164
+ installResult = await api.post(
2165
+ `/api/mcp/sessions/${sessionId}/commands`,
2166
+ {
2167
+ command: installCommand.command,
2168
+ args: installCommand.args,
2169
+ timeout: MAX_INSTALL_TIMEOUT_MS
2170
+ }
2171
+ );
2172
+ }
2173
+ }
2174
+ if (installResult.exitCode !== 0) {
2175
+ throw new CLIError(
2176
+ installResult.stderr || `Dependency install failed with exit code ${installResult.exitCode}`,
2177
+ "SANDBOX_HYDRATION_FAILED",
2178
+ {
2179
+ command: [installCommand.command, ...installCommand.args].join(" "),
2180
+ exitCode: installResult.exitCode
2181
+ }
2182
+ );
2183
+ }
2184
+ const devCommand = getDevCommand(packageManager);
2185
+ let serverCmdId;
2186
+ let didStartServer = false;
2187
+ const serverStatus = await getServerStatusOrNull(sessionId);
2188
+ const serverStatusPreviewUrl = serverStatus?.previewUrl;
2189
+ if (serverStatusPreviewUrl) {
2190
+ previewUrl = serverStatusPreviewUrl;
2191
+ }
2192
+ if (serverStatus?.running) {
2193
+ spinner.text = "Server already running, attaching...";
2194
+ serverCmdId = serverStatus.cmdId;
2195
+ } else {
2196
+ spinner.text = `Starting server (${devCommand})...`;
2197
+ try {
2198
+ const serverStart = await api.post(
2199
+ `/api/mcp/sessions/${sessionId}/server`,
2200
+ {
2201
+ action: "start",
2202
+ command: devCommand
2203
+ }
2204
+ );
2205
+ const serverStartPreviewUrl = serverStart.previewUrl;
2206
+ if (serverStartPreviewUrl) {
2207
+ previewUrl = serverStartPreviewUrl;
2208
+ }
2209
+ serverCmdId = serverStart.cmdId;
2210
+ didStartServer = true;
2211
+ } catch (error) {
2212
+ if (!isAlreadyRunningServerError(error)) {
2213
+ throw error;
2214
+ }
2215
+ const refreshedStatus = await getServerStatusOrNull(sessionId);
2216
+ if (!refreshedStatus?.running) {
2217
+ throw error;
2218
+ }
2219
+ const refreshedPreviewUrl = refreshedStatus.previewUrl;
2220
+ if (refreshedPreviewUrl) {
2221
+ previewUrl = refreshedPreviewUrl;
2222
+ }
2223
+ serverCmdId = refreshedStatus.cmdId;
2224
+ spinner.text = "Server already running, attaching...";
2225
+ }
2226
+ }
2227
+ if (didStartServer && !serverCmdId) {
2228
+ await cleanupPreviewSession(sessionId);
2229
+ throw new CLIError(
2230
+ "Server start command ID was not returned by the API.",
2231
+ "SERVER_START_FAILED",
2232
+ { command: devCommand }
2233
+ );
2234
+ }
2235
+ if (didStartServer && serverCmdId) {
2236
+ spinner.text = "Verifying server startup...";
2237
+ for (let attempt = 0; attempt < DEV_SERVER_VERIFY_ATTEMPTS; attempt++) {
2238
+ if (attempt > 0) {
2239
+ await sleep(DEV_SERVER_VERIFY_INTERVAL_MS);
2240
+ }
2241
+ const commandStatus = await api.get(
2242
+ `/api/mcp/sessions/${sessionId}/commands/${serverCmdId}?statusOnly=true`
2243
+ );
2244
+ if (commandStatus.exitCode === null) {
2245
+ continue;
2246
+ }
2247
+ const commandOutput = await api.get(
2248
+ `/api/mcp/sessions/${sessionId}/commands/${serverCmdId}`
2249
+ ).catch(() => null);
2250
+ await cleanupPreviewSession(sessionId);
2251
+ throw new CLIError(
2252
+ `Dev server command exited during startup with code ${commandStatus.exitCode}.`,
2253
+ "SERVER_START_FAILED",
2254
+ {
2255
+ command: devCommand,
2256
+ cmdId: serverCmdId,
2257
+ exitCode: commandStatus.exitCode,
2258
+ stdout: commandOutput?.stdout ?? "",
2259
+ stderr: commandOutput?.stderr ?? ""
2260
+ }
2261
+ );
2262
+ }
2263
+ }
1886
2264
  spinner.succeed("Development environment ready");
1887
2265
  console.log();
1888
2266
  formatSuccess("Live preview started!", false);
1889
2267
  console.log();
2268
+ if (sandboxId) {
2269
+ console.log(` Sandbox ID: ${sandboxId}`);
2270
+ }
1890
2271
  console.log(` Preview URL: ${previewUrl}`);
1891
2272
  console.log();
1892
2273
  console.log(` MCP Inspector:`);
1893
2274
  console.log(
1894
- ` npx @anthropic-ai/mcp-inspector@latest "${previewUrl}/mcp"`
2275
+ ` npx @modelcontextprotocol/inspector@latest --transport http --server-url "${previewUrl}/mcp"`
1895
2276
  );
1896
2277
  console.log();
1897
2278
  if (options.watch !== false) {
1898
2279
  console.log("Watching for file changes... (Ctrl+C to stop)");
1899
2280
  console.log();
1900
- const ig = await loadIgnorePatterns(projectRoot);
2281
+ const ig = await loadIgnorePatterns(projectRoot, {
2282
+ includeEnvFiles: true
2283
+ });
1901
2284
  const watcher = watch(projectRoot, {
1902
2285
  ignored: (path) => {
1903
2286
  const relative2 = path.replace(`${projectRoot}/`, "");
@@ -1932,24 +2315,15 @@ var previewCommand = new Command14("preview").description("Start live developmen
1932
2315
  const relativePath = filePath.replace(`${projectRoot}/`, "");
1933
2316
  console.log(` Deleted: ${relativePath}`);
1934
2317
  });
1935
- const stopSessionBestEffort = async () => {
1936
- await withTimeout(
1937
- api.post(`/api/mcp/sessions/${sessionId}/server`, { action: "stop" }).catch(() => void 0),
1938
- SHUTDOWN_STEP_TIMEOUT_MS
1939
- );
1940
- await withTimeout(
1941
- api.delete(`/api/mcp/sessions/${sessionId}`).catch(() => void 0),
1942
- SHUTDOWN_STEP_TIMEOUT_MS
1943
- );
1944
- await withTimeout(
1945
- config.setSessionId(null).catch(() => void 0),
1946
- SHUTDOWN_STEP_TIMEOUT_MS
1947
- );
1948
- };
1949
2318
  let shuttingDown = false;
1950
- const gracefulShutdown = async () => {
2319
+ let serverMonitorInterval = null;
2320
+ const gracefulShutdown = async (exitCode = 0) => {
1951
2321
  if (shuttingDown) return;
1952
2322
  shuttingDown = true;
2323
+ if (serverMonitorInterval) {
2324
+ clearInterval(serverMonitorInterval);
2325
+ serverMonitorInterval = null;
2326
+ }
1953
2327
  console.log();
1954
2328
  console.log("Stopping development environment...");
1955
2329
  try {
@@ -1959,7 +2333,7 @@ var previewCommand = new Command14("preview").description("Start live developmen
1959
2333
  watcher.close().catch(() => void 0),
1960
2334
  SHUTDOWN_STEP_TIMEOUT_MS
1961
2335
  );
1962
- await stopSessionBestEffort();
2336
+ await cleanupPreviewSession(sessionId);
1963
2337
  })(),
1964
2338
  SHUTDOWN_MAX_WAIT_MS,
1965
2339
  {
@@ -1969,9 +2343,49 @@ var previewCommand = new Command14("preview").description("Start live developmen
1969
2343
  }
1970
2344
  );
1971
2345
  } finally {
1972
- process.exit(0);
2346
+ process.exit(exitCode);
1973
2347
  }
1974
2348
  };
2349
+ if (serverCmdId) {
2350
+ serverMonitorInterval = setInterval(() => {
2351
+ if (shuttingDown) return;
2352
+ void (async () => {
2353
+ try {
2354
+ const commandStatus = await api.get(
2355
+ `/api/mcp/sessions/${sessionId}/commands/${serverCmdId}?statusOnly=true`
2356
+ );
2357
+ if (commandStatus.exitCode === null) {
2358
+ return;
2359
+ }
2360
+ if (commandStatus.exitCode === 0) {
2361
+ console.log("Dev server exited (code 0).");
2362
+ await gracefulShutdown(0);
2363
+ return;
2364
+ }
2365
+ const commandOutput = await getCommandOutputOrNull(
2366
+ sessionId,
2367
+ serverCmdId
2368
+ );
2369
+ handleError(
2370
+ new CLIError(
2371
+ `Dev server exited with code ${commandStatus.exitCode}.`,
2372
+ "SERVER_EXITED",
2373
+ {
2374
+ command: devCommand,
2375
+ cmdId: serverCmdId,
2376
+ exitCode: commandStatus.exitCode,
2377
+ stdout: commandOutput?.stdout ?? "",
2378
+ stderr: commandOutput?.stderr ?? ""
2379
+ }
2380
+ ),
2381
+ json
2382
+ );
2383
+ await gracefulShutdown(1);
2384
+ } catch {
2385
+ }
2386
+ })();
2387
+ }, statusPollIntervalMs);
2388
+ }
1975
2389
  process.once("SIGINT", () => {
1976
2390
  void gracefulShutdown();
1977
2391
  });
@@ -1987,94 +2401,10 @@ var previewCommand = new Command14("preview").description("Start live developmen
1987
2401
  }
1988
2402
  });
1989
2403
 
1990
- // src/commands/mcp/publish.ts
1991
- import { execFileSync as execFileSync4 } from "child_process";
1992
- import { input } from "@inquirer/prompts";
1993
- import { Command as Command15 } from "commander";
1994
- import ora11 from "ora";
1995
- var publishCommand = new Command15("publish").description("Push local files to GitHub and trigger deployment").option("-m, --message <msg>", "Commit message").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1996
- const globalOptions = command.optsWithGlobals();
1997
- const json = globalOptions.json ?? false;
1998
- try {
1999
- const mcpId = await requireMcpId(options.mcpId);
2000
- const projectRoot = await findProjectRoot(process.cwd());
2001
- if (!projectRoot) {
2002
- throw new CLIError(
2003
- "Not in a WaniWani project. Run 'waniwani mcp create <name>' first.",
2004
- "NOT_IN_PROJECT"
2005
- );
2006
- }
2007
- try {
2008
- execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], {
2009
- cwd: projectRoot,
2010
- stdio: "ignore"
2011
- });
2012
- } catch {
2013
- throw new CLIError(
2014
- "Not a git repository. Run 'waniwani mcp create <name>' or 'waniwani mcp clone <name>' to set up properly.",
2015
- "NOT_GIT_REPO"
2016
- );
2017
- }
2018
- const status = execFileSync4("git", ["status", "--porcelain"], {
2019
- cwd: projectRoot,
2020
- encoding: "utf-8"
2021
- }).trim();
2022
- if (!status) {
2023
- if (json) {
2024
- formatOutput({ success: true, message: "Nothing to publish" }, true);
2025
- } else {
2026
- console.log("Nothing to publish \u2014 no changes detected.");
2027
- }
2028
- return;
2029
- }
2030
- let message = options.message;
2031
- if (!message) {
2032
- message = await input({
2033
- message: "Commit message:",
2034
- validate: (value) => value.trim() ? true : "Commit message is required"
2035
- });
2036
- }
2037
- const spinner = ora11("Publishing...").start();
2038
- const gitAuth = await getGitAuthContext(mcpId);
2039
- spinner.text = "Committing changes...";
2040
- execFileSync4("git", ["add", "-A"], { cwd: projectRoot, stdio: "ignore" });
2041
- execFileSync4("git", ["commit", "-m", message], {
2042
- cwd: projectRoot,
2043
- stdio: "ignore"
2044
- });
2045
- spinner.text = "Pushing to GitHub...";
2046
- try {
2047
- runGitWithCredentials(["push", gitAuth.remoteUrl, "HEAD"], {
2048
- cwd: projectRoot,
2049
- stdio: "ignore",
2050
- credentials: gitAuth.credentials
2051
- });
2052
- } finally {
2053
- await revokeGitHubInstallationToken(gitAuth);
2054
- }
2055
- const commitSha = execFileSync4("git", ["rev-parse", "HEAD"], {
2056
- cwd: projectRoot,
2057
- encoding: "utf-8"
2058
- }).trim();
2059
- spinner.succeed(`Pushed to GitHub (${commitSha.slice(0, 7)})`);
2060
- if (json) {
2061
- formatOutput({ commitSha, message }, true);
2062
- } else {
2063
- console.log();
2064
- formatSuccess("Files pushed to GitHub!", false);
2065
- console.log();
2066
- console.log("Deployment will start automatically via webhook.");
2067
- }
2068
- } catch (error) {
2069
- handleError(error, json);
2070
- process.exit(1);
2071
- }
2072
- });
2073
-
2074
2404
  // src/commands/mcp/run-command.ts
2075
2405
  import chalk8 from "chalk";
2076
2406
  import { Command as Command16 } from "commander";
2077
- import ora12 from "ora";
2407
+ import ora11 from "ora";
2078
2408
  var runCommandCommand = new Command16("run-command").description("Run a command in the MCP sandbox").argument("<command>", "Command to run").argument("[args...]", "Command arguments").option("--mcp-id <id>", "Specific MCP ID").option("--cwd <path>", "Working directory").option(
2079
2409
  "--timeout <ms>",
2080
2410
  "Command timeout in milliseconds (default: 30000, max: 300000)"
@@ -2085,7 +2415,7 @@ var runCommandCommand = new Command16("run-command").description("Run a command
2085
2415
  await requireMcpId(options.mcpId);
2086
2416
  const sessionId = await requireSessionId();
2087
2417
  const timeout = options.timeout ? Number.parseInt(options.timeout, 10) : void 0;
2088
- const spinner = ora12(`Running: ${cmd} ${args.join(" ")}`.trim()).start();
2418
+ const spinner = ora11(`Running: ${cmd} ${args.join(" ")}`.trim()).start();
2089
2419
  const result = await api.post(
2090
2420
  `/api/mcp/sessions/${sessionId}/commands`,
2091
2421
  {
@@ -2133,13 +2463,13 @@ var runCommandCommand = new Command16("run-command").description("Run a command
2133
2463
  // src/commands/mcp/status.ts
2134
2464
  import chalk9 from "chalk";
2135
2465
  import { Command as Command17 } from "commander";
2136
- import ora13 from "ora";
2466
+ import ora12 from "ora";
2137
2467
  var statusCommand = new Command17("status").description("Show current MCP status").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
2138
2468
  const globalOptions = command.optsWithGlobals();
2139
2469
  const json = globalOptions.json ?? false;
2140
2470
  try {
2141
2471
  const mcpId = await requireMcpId(options.mcpId);
2142
- const spinner = ora13("Fetching MCP status...").start();
2472
+ const spinner = ora12("Fetching MCP status...").start();
2143
2473
  const result = await api.get(
2144
2474
  `/api/mcp/repositories/${mcpId}`
2145
2475
  );
@@ -2209,14 +2539,14 @@ var statusCommand = new Command17("status").description("Show current MCP status
2209
2539
 
2210
2540
  // src/commands/mcp/stop.ts
2211
2541
  import { Command as Command18 } from "commander";
2212
- import ora14 from "ora";
2542
+ import ora13 from "ora";
2213
2543
  var stopCommand = new Command18("stop").description("Stop the development environment (sandbox + server)").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
2214
2544
  const globalOptions = command.optsWithGlobals();
2215
2545
  const json = globalOptions.json ?? false;
2216
2546
  try {
2217
2547
  await requireMcpId(options.mcpId);
2218
2548
  const sessionId = await requireSessionId();
2219
- const spinner = ora14("Stopping development environment...").start();
2549
+ const spinner = ora13("Stopping development environment...").start();
2220
2550
  try {
2221
2551
  await api.post(`/api/mcp/sessions/${sessionId}/server`, {
2222
2552
  action: "stop"
@@ -2241,7 +2571,7 @@ var stopCommand = new Command18("stop").description("Stop the development enviro
2241
2571
 
2242
2572
  // src/commands/mcp/sync.ts
2243
2573
  import { Command as Command19 } from "commander";
2244
- import ora15 from "ora";
2574
+ import ora14 from "ora";
2245
2575
  var syncCommand = new Command19("sync").description("Pull template files to local project").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
2246
2576
  const globalOptions = command.optsWithGlobals();
2247
2577
  const json = globalOptions.json ?? false;
@@ -2254,7 +2584,7 @@ var syncCommand = new Command19("sync").description("Pull template files to loca
2254
2584
  "NOT_IN_PROJECT"
2255
2585
  );
2256
2586
  }
2257
- const spinner = ora15("Pulling files...").start();
2587
+ const spinner = ora14("Pulling files...").start();
2258
2588
  const result = await pullFilesFromGithub(mcpId, projectRoot);
2259
2589
  spinner.succeed(`Pulled ${result.count} files`);
2260
2590
  if (json) {
@@ -2277,12 +2607,12 @@ var syncCommand = new Command19("sync").description("Pull template files to loca
2277
2607
 
2278
2608
  // src/commands/mcp/use.ts
2279
2609
  import { Command as Command20 } from "commander";
2280
- import ora16 from "ora";
2610
+ import ora15 from "ora";
2281
2611
  var useCommand = new Command20("use").description("Select an MCP to use for subsequent commands").argument("<name>", "Name of the MCP to use").action(async (name, _options, command) => {
2282
2612
  const globalOptions = command.optsWithGlobals();
2283
2613
  const json = globalOptions.json ?? false;
2284
2614
  try {
2285
- const spinner = ora16("Fetching MCPs...").start();
2615
+ const spinner = ora15("Fetching MCPs...").start();
2286
2616
  const mcps = await api.get(
2287
2617
  "/api/mcp/repositories"
2288
2618
  );
@@ -2313,7 +2643,7 @@ var useCommand = new Command20("use").description("Select an MCP to use for subs
2313
2643
  });
2314
2644
 
2315
2645
  // src/commands/mcp/index.ts
2316
- var mcpCommand = new Command21("mcp").description("MCP management commands").addCommand(createCommand).addCommand(cloneCommand).addCommand(listCommand2).addCommand(useCommand).addCommand(statusCommand).addCommand(previewCommand).addCommand(stopCommand).addCommand(logsCommand).addCommand(syncCommand).addCommand(publishCommand).addCommand(deleteCommand).addCommand(fileCommand).addCommand(runCommandCommand);
2646
+ var mcpCommand = new Command21("mcp").description("MCP management commands").addCommand(createCommand).addCommand(cloneCommand).addCommand(listCommand2).addCommand(useCommand).addCommand(statusCommand).addCommand(previewCommand).addCommand(stopCommand).addCommand(logsCommand).addCommand(syncCommand).addCommand(deleteCommand).addCommand(fileCommand).addCommand(runCommandCommand);
2317
2647
 
2318
2648
  // src/commands/org/index.ts
2319
2649
  import { Command as Command24 } from "commander";
@@ -2321,12 +2651,12 @@ import { Command as Command24 } from "commander";
2321
2651
  // src/commands/org/list.ts
2322
2652
  import chalk10 from "chalk";
2323
2653
  import { Command as Command22 } from "commander";
2324
- import ora17 from "ora";
2654
+ import ora16 from "ora";
2325
2655
  var listCommand3 = new Command22("list").description("List your organizations").action(async (_options, command) => {
2326
2656
  const globalOptions = command.optsWithGlobals();
2327
2657
  const json = globalOptions.json ?? false;
2328
2658
  try {
2329
- const spinner = ora17("Fetching organizations...").start();
2659
+ const spinner = ora16("Fetching organizations...").start();
2330
2660
  const result = await api.get("/api/oauth/orgs");
2331
2661
  spinner.stop();
2332
2662
  const { orgs, activeOrgId } = result;
@@ -2373,12 +2703,12 @@ var listCommand3 = new Command22("list").description("List your organizations").
2373
2703
 
2374
2704
  // src/commands/org/switch.ts
2375
2705
  import { Command as Command23 } from "commander";
2376
- import ora18 from "ora";
2706
+ import ora17 from "ora";
2377
2707
  var switchCommand = new Command23("switch").description("Switch to a different organization").argument("<name>", "Name or slug of the organization to switch to").action(async (name, _options, command) => {
2378
2708
  const globalOptions = command.optsWithGlobals();
2379
2709
  const json = globalOptions.json ?? false;
2380
2710
  try {
2381
- const spinner = ora18("Fetching organizations...").start();
2711
+ const spinner = ora17("Fetching organizations...").start();
2382
2712
  const { orgs } = await api.get("/api/oauth/orgs");
2383
2713
  const org = orgs.find((o) => o.name === name || o.slug === name);
2384
2714
  if (!org) {
@@ -2422,6 +2752,7 @@ program.addCommand(logoutCommand);
2422
2752
  program.addCommand(mcpCommand);
2423
2753
  program.addCommand(orgCommand);
2424
2754
  program.addCommand(configCommand);
2755
+ program.addCommand(gitCredentialHelperCommand);
2425
2756
 
2426
2757
  // src/index.ts
2427
2758
  program.parse(process.argv);