@waniwani/cli 0.0.33 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command as Command25 } from "commander";
4
+ import { Command as Command24 } from "commander";
5
5
 
6
6
  // src/commands/config/index.ts
7
7
  import { Command as Command2 } from "commander";
@@ -25,8 +25,11 @@ var GLOBAL_DIR = join(homedir(), LOCAL_CONFIG_DIR);
25
25
  var GLOBAL_FILE = join(GLOBAL_DIR, CONFIG_FILE_NAME);
26
26
  var DEFAULT_API_URL = "https://app.waniwani.ai";
27
27
  var ConfigSchema = z.object({
28
- mcpId: z.string().nullable().default(null),
29
- apiUrl: z.string().nullable().default(null)
28
+ mcp: z.object({
29
+ id: z.string().nullable().default(null),
30
+ name: z.string().nullable().default(null)
31
+ }),
32
+ apiUrl: z.string().default(DEFAULT_API_URL)
30
33
  });
31
34
  var Config = class {
32
35
  dir;
@@ -57,21 +60,16 @@ var Config = class {
57
60
  await writeFile(this.file, JSON.stringify(data, null, " "));
58
61
  }
59
62
  async getMcpId() {
60
- return (await this.load()).mcpId;
63
+ return (await this.load()).mcp.id;
61
64
  }
62
65
  async setMcpId(id) {
63
66
  const data = await this.load();
64
- data.mcpId = id;
67
+ data.mcp.id = id;
65
68
  await this.save(data);
66
69
  }
67
70
  async getApiUrl() {
68
71
  if (process.env.WANIWANI_API_URL) return process.env.WANIWANI_API_URL;
69
- return (await this.load()).apiUrl || DEFAULT_API_URL;
70
- }
71
- async setApiUrl(url) {
72
- const data = await this.load();
73
- data.apiUrl = url;
74
- await this.save(data);
72
+ return (await this.load()).apiUrl;
75
73
  }
76
74
  async clear() {
77
75
  await this.save(ConfigSchema.parse({}));
@@ -235,10 +233,10 @@ var configInitCommand = new Command("init").description("Initialize .waniwani co
235
233
  // src/commands/config/index.ts
236
234
  var configCommand = new Command2("config").description("Manage WaniWani configuration").addCommand(configInitCommand);
237
235
 
238
- // src/commands/dev.ts
239
- import { relative as relative2 } from "path";
236
+ // src/commands/login.ts
237
+ import { spawn } from "child_process";
238
+ import { createServer } from "http";
240
239
  import chalk3 from "chalk";
241
- import chokidar from "chokidar";
242
240
  import { Command as Command3 } from "commander";
243
241
  import ora from "ora";
244
242
 
@@ -350,545 +348,66 @@ var AuthManager = class {
350
348
  };
351
349
  var auth = new AuthManager();
352
350
 
353
- // src/lib/api.ts
354
- var ApiError = class extends CLIError {
355
- constructor(message, code, statusCode, details) {
356
- super(message, code, details);
357
- this.statusCode = statusCode;
358
- this.name = "ApiError";
359
- }
360
- };
361
- async function request(method, path, options) {
362
- const {
363
- body,
364
- requireAuth = true,
365
- headers: extraHeaders = {}
366
- } = options || {};
367
- const headers = {
368
- "Content-Type": "application/json",
369
- ...extraHeaders
370
- };
371
- if (requireAuth) {
372
- const token = await auth.getAccessToken();
373
- if (!token) {
374
- throw new AuthError(
375
- "Not logged in. Run 'waniwani login' to authenticate."
376
- );
377
- }
378
- headers.Authorization = `Bearer ${token}`;
379
- }
380
- const baseUrl = await config.getApiUrl();
381
- const url = `${baseUrl}${path}`;
382
- const response = await fetch(url, {
383
- method,
384
- headers,
385
- body: body ? JSON.stringify(body) : void 0
386
- });
387
- if (response.status === 204) {
388
- return void 0;
389
- }
390
- let data;
391
- let rawBody;
392
- try {
393
- rawBody = await response.text();
394
- data = JSON.parse(rawBody);
395
- } catch {
396
- throw new ApiError(
397
- rawBody || `Request failed with status ${response.status}`,
398
- "API_ERROR",
399
- response.status,
400
- { statusText: response.statusText }
401
- );
402
- }
403
- if (!response.ok || data.error) {
404
- const errorMessage = data.error?.message || data.message || data.error || rawBody || `Request failed with status ${response.status}`;
405
- const errorCode = data.error?.code || data.code || "API_ERROR";
406
- const errorDetails = {
407
- ...data.error?.details,
408
- statusText: response.statusText,
409
- ...data.error ? {} : { rawResponse: data }
410
- };
411
- const error = {
412
- code: errorCode,
413
- message: errorMessage,
414
- details: errorDetails
415
- };
416
- if (response.status === 401) {
417
- const refreshed = await auth.tryRefreshToken();
418
- if (refreshed) {
419
- return request(method, path, options);
420
- }
421
- throw new AuthError(
422
- "Session expired. Run 'waniwani login' to re-authenticate."
423
- );
424
- }
425
- throw new ApiError(
426
- error.message,
427
- error.code,
428
- response.status,
429
- error.details
430
- );
431
- }
432
- return data.data;
433
- }
434
- var api = {
435
- get: (path, options) => request("GET", path, options),
436
- post: (path, body, options) => request("POST", path, { body, ...options }),
437
- delete: (path, options) => request("DELETE", path, options),
438
- getBaseUrl: () => config.getApiUrl()
439
- };
440
-
441
- // src/lib/sync.ts
442
- import { existsSync as existsSync3 } from "fs";
443
- import { mkdir as mkdir3, readdir, readFile as readFile3, stat, writeFile as writeFile3 } from "fs/promises";
444
- import { dirname, join as join4, relative } from "path";
445
- import ignore from "ignore";
446
-
447
- // src/lib/utils.ts
448
- function debounce(fn, delay) {
449
- let timeoutId;
450
- return (...args) => {
451
- clearTimeout(timeoutId);
452
- timeoutId = setTimeout(() => fn(...args), delay);
453
- };
454
- }
455
- var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
456
- ".png",
457
- ".jpg",
458
- ".jpeg",
459
- ".gif",
460
- ".ico",
461
- ".webp",
462
- ".svg",
463
- ".woff",
464
- ".woff2",
465
- ".ttf",
466
- ".eot",
467
- ".otf",
468
- ".zip",
469
- ".tar",
470
- ".gz",
471
- ".pdf",
472
- ".exe",
473
- ".dll",
474
- ".so",
475
- ".dylib",
476
- ".bin",
477
- ".mp3",
478
- ".mp4",
479
- ".wav",
480
- ".ogg",
481
- ".webm"
482
- ]);
483
- function isBinaryPath(filePath) {
484
- const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
485
- return BINARY_EXTENSIONS.has(ext);
351
+ // src/commands/login.ts
352
+ var CALLBACK_PORT = 54321;
353
+ var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
354
+ var CLIENT_NAME = "waniwani-cli";
355
+ function generateCodeVerifier() {
356
+ const array = new Uint8Array(32);
357
+ crypto.getRandomValues(array);
358
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
486
359
  }
487
- function detectBinary(buffer) {
488
- const sample = buffer.subarray(0, 8192);
489
- return sample.includes(0);
360
+ async function generateCodeChallenge(verifier) {
361
+ const encoder = new TextEncoder();
362
+ const data = encoder.encode(verifier);
363
+ const hash = await crypto.subtle.digest("SHA-256", data);
364
+ return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
490
365
  }
491
-
492
- // src/lib/sync.ts
493
- var PROJECT_DIR = ".waniwani";
494
- var SETTINGS_FILE = "settings.json";
495
- async function findProjectRoot(startDir) {
496
- let current = startDir;
497
- const root = dirname(current);
498
- while (current !== root) {
499
- if (existsSync3(join4(current, PROJECT_DIR))) {
500
- return current;
501
- }
502
- const parent = dirname(current);
503
- if (parent === current) break;
504
- current = parent;
505
- }
506
- if (existsSync3(join4(current, PROJECT_DIR))) {
507
- return current;
508
- }
509
- return null;
366
+ function generateState() {
367
+ const array = new Uint8Array(16);
368
+ crypto.getRandomValues(array);
369
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
510
370
  }
511
- async function loadProjectMcpId(projectRoot) {
512
- const settingsPath = join4(projectRoot, PROJECT_DIR, SETTINGS_FILE);
513
- try {
514
- const content = await readFile3(settingsPath, "utf-8");
515
- const settings = JSON.parse(content);
516
- return settings.mcpId ?? null;
517
- } catch {
518
- return null;
371
+ async function registerClient() {
372
+ const apiUrl = await config.getApiUrl();
373
+ const response = await fetch(`${apiUrl}/api/auth/oauth2/register`, {
374
+ method: "POST",
375
+ headers: {
376
+ "Content-Type": "application/json"
377
+ },
378
+ body: JSON.stringify({
379
+ client_name: CLIENT_NAME,
380
+ redirect_uris: [CALLBACK_URL],
381
+ grant_types: ["authorization_code", "refresh_token"],
382
+ response_types: ["code"],
383
+ token_endpoint_auth_method: "none"
384
+ })
385
+ });
386
+ if (!response.ok) {
387
+ const error = await response.json().catch(() => ({}));
388
+ throw new CLIError(
389
+ error.error_description || "Failed to register OAuth client",
390
+ "CLIENT_REGISTRATION_FAILED"
391
+ );
519
392
  }
393
+ return response.json();
520
394
  }
521
- var DEFAULT_IGNORE_PATTERNS = [
522
- ".waniwani",
523
- ".git",
524
- "node_modules",
525
- ".env",
526
- ".env.*",
527
- ".DS_Store",
528
- "*.log",
529
- ".cache",
530
- "dist",
531
- "coverage",
532
- ".turbo",
533
- ".next",
534
- ".nuxt",
535
- ".vercel"
536
- ];
537
- async function loadIgnorePatterns(projectRoot) {
538
- const ig = ignore();
539
- ig.add(DEFAULT_IGNORE_PATTERNS);
540
- const gitignorePath = join4(projectRoot, ".gitignore");
541
- if (existsSync3(gitignorePath)) {
542
- try {
543
- const content = await readFile3(gitignorePath, "utf-8");
544
- ig.add(content);
545
- } catch {
546
- }
547
- }
548
- return ig;
395
+ async function openBrowser(url) {
396
+ const [cmd, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", url] : ["xdg-open", url];
397
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
549
398
  }
550
- async function collectFiles(projectRoot) {
551
- const ig = await loadIgnorePatterns(projectRoot);
552
- const files = [];
553
- async function walk(dir) {
554
- const entries = await readdir(dir, { withFileTypes: true });
555
- for (const entry of entries) {
556
- const fullPath = join4(dir, entry.name);
557
- const relativePath = relative(projectRoot, fullPath);
558
- if (ig.ignores(relativePath)) {
559
- continue;
560
- }
561
- if (entry.isDirectory()) {
562
- await walk(fullPath);
563
- } else if (entry.isFile()) {
564
- try {
565
- const content = await readFile3(fullPath);
566
- const isBinary = isBinaryPath(fullPath) || detectBinary(content);
567
- files.push({
568
- path: relativePath,
569
- content: isBinary ? content.toString("base64") : content.toString("utf8"),
570
- encoding: isBinary ? "base64" : "utf8"
571
- });
572
- } catch {
573
- }
574
- }
575
- }
576
- }
577
- await walk(projectRoot);
578
- return files;
579
- }
580
- async function pullFilesFromSandbox(mcpId, targetDir) {
581
- const result = await api.get(
582
- `/api/mcp/sandboxes/${mcpId}/files/pull`
583
- );
584
- const writtenFiles = [];
585
- for (const file of result.files) {
586
- const localPath = join4(targetDir, file.path);
587
- const dir = dirname(localPath);
588
- await mkdir3(dir, { recursive: true });
589
- if (file.encoding === "base64") {
590
- await writeFile3(localPath, Buffer.from(file.content, "base64"));
591
- } else {
592
- await writeFile3(localPath, file.content, "utf8");
593
- }
594
- writtenFiles.push(file.path);
595
- }
596
- return { count: writtenFiles.length, files: writtenFiles };
597
- }
598
- async function collectSingleFile(projectRoot, filePath) {
599
- const fullPath = join4(projectRoot, filePath);
600
- const relativePath = relative(projectRoot, fullPath);
601
- if (!existsSync3(fullPath)) {
602
- return null;
603
- }
604
- try {
605
- const fileStat = await stat(fullPath);
606
- if (!fileStat.isFile()) {
607
- return null;
608
- }
609
- const content = await readFile3(fullPath);
610
- const isBinary = isBinaryPath(fullPath) || detectBinary(content);
611
- return {
612
- path: relativePath,
613
- content: isBinary ? content.toString("base64") : content.toString("utf8"),
614
- encoding: isBinary ? "base64" : "utf8"
615
- };
616
- } catch {
617
- return null;
618
- }
619
- }
620
-
621
- // src/commands/dev.ts
622
- var BATCH_SIZE = 50;
623
- var DEFAULT_DEBOUNCE_MS = 300;
624
- var devCommand = new Command3("dev").description("Watch and sync files to MCP sandbox").option("--no-initial-sync", "Skip initial sync of all files").option(
625
- "--debounce <ms>",
626
- "Debounce delay in milliseconds",
627
- String(DEFAULT_DEBOUNCE_MS)
628
- ).action(async (options, command) => {
629
- const globalOptions = command.optsWithGlobals();
630
- const json = globalOptions.json ?? false;
631
- try {
632
- const cwd = process.cwd();
633
- const projectRoot = await findProjectRoot(cwd);
634
- if (!projectRoot) {
635
- throw new CLIError(
636
- "Not in a WaniWani project. Run 'waniwani init <name>' first.",
637
- "NOT_IN_PROJECT"
638
- );
639
- }
640
- const mcpId = await loadProjectMcpId(projectRoot);
641
- if (!mcpId) {
642
- throw new CLIError(
643
- "No MCP ID found in project config. Run 'waniwani init <name>' first.",
644
- "NO_MCP_ID"
645
- );
646
- }
647
- if (options.initialSync !== false) {
648
- const spinner = ora("Initial sync...").start();
649
- const files = await collectFiles(projectRoot);
650
- if (files.length > 0) {
651
- const totalBatches = Math.ceil(files.length / BATCH_SIZE);
652
- let synced = 0;
653
- for (let i = 0; i < totalBatches; i++) {
654
- const batch = files.slice(i * BATCH_SIZE, (i + 1) * BATCH_SIZE);
655
- spinner.text = `Syncing (${i + 1}/${totalBatches})...`;
656
- const result = await api.post(
657
- `/api/mcp/sandboxes/${mcpId}/files`,
658
- {
659
- files: batch.map((f) => ({
660
- path: f.path,
661
- content: f.content,
662
- encoding: f.encoding
663
- }))
664
- }
665
- );
666
- synced += result.written.length;
667
- }
668
- spinner.succeed(`Initial sync complete (${synced} files)`);
669
- } else {
670
- spinner.info("No files to sync");
671
- }
672
- }
673
- const ig = await loadIgnorePatterns(projectRoot);
674
- const debounceMs = Number.parseInt(options.debounce, 10) || DEFAULT_DEBOUNCE_MS;
675
- const syncFile = debounce(async (filePath) => {
676
- const relativePath = relative2(projectRoot, filePath);
677
- if (ig.ignores(relativePath)) {
678
- return;
679
- }
680
- const file = await collectSingleFile(projectRoot, relativePath);
681
- if (!file) {
682
- console.log(chalk3.yellow("Skipped:"), relativePath);
683
- return;
684
- }
685
- try {
686
- await api.post(
687
- `/api/mcp/sandboxes/${mcpId}/files`,
688
- {
689
- files: [
690
- {
691
- path: file.path,
692
- content: file.content,
693
- encoding: file.encoding
694
- }
695
- ]
696
- }
697
- );
698
- console.log(chalk3.green("Synced:"), relativePath);
699
- } catch (error) {
700
- console.log(chalk3.red("Failed:"), relativePath);
701
- if (globalOptions.verbose) {
702
- console.error(error);
703
- }
704
- }
705
- }, debounceMs);
706
- console.log();
707
- console.log(chalk3.bold("Watching for changes..."));
708
- console.log(chalk3.dim("Press Ctrl+C to stop"));
709
- console.log();
710
- const watcher = chokidar.watch(projectRoot, {
711
- ignored: (path) => {
712
- const relativePath = relative2(projectRoot, path);
713
- return ig.ignores(relativePath);
714
- },
715
- persistent: true,
716
- ignoreInitial: true,
717
- awaitWriteFinish: {
718
- stabilityThreshold: 100,
719
- pollInterval: 100
720
- }
721
- });
722
- watcher.on("add", (path) => syncFile(path)).on("change", (path) => syncFile(path)).on("unlink", (path) => {
723
- const relativePath = relative2(projectRoot, path);
724
- console.log(chalk3.yellow("Deleted (local only):"), relativePath);
725
- });
726
- const cleanup = () => {
727
- console.log();
728
- console.log(chalk3.dim("Stopping watcher..."));
729
- watcher.close().then(() => {
730
- process.exit(0);
731
- });
732
- };
733
- process.on("SIGINT", cleanup);
734
- process.on("SIGTERM", cleanup);
735
- } catch (error) {
736
- handleError(error, json);
737
- process.exit(1);
738
- }
739
- });
740
-
741
- // src/commands/init.ts
742
- import { existsSync as existsSync4 } from "fs";
743
- import { mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
744
- import { join as join5 } from "path";
745
- import { Command as Command4 } from "commander";
746
- import ora2 from "ora";
747
- async function loadParentConfig(cwd) {
748
- const parentConfigPath = join5(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
749
- if (!existsSync4(parentConfigPath)) {
750
- return null;
751
- }
752
- try {
753
- const content = await readFile4(parentConfigPath, "utf-8");
754
- const config2 = JSON.parse(content);
755
- const { mcpId: _, ...rest } = config2;
756
- return rest;
757
- } catch {
758
- return null;
759
- }
760
- }
761
- var initCommand = new Command4("init").description("Create a new MCP project from template").argument("<name>", "Name for the MCP project").action(async (name, _, command) => {
762
- const globalOptions = command.optsWithGlobals();
763
- const json = globalOptions.json ?? false;
764
- try {
765
- const cwd = process.cwd();
766
- const projectDir = join5(cwd, name);
767
- if (existsSync4(projectDir)) {
768
- if (json) {
769
- formatOutput(
770
- {
771
- success: false,
772
- error: `Directory "${name}" already exists`
773
- },
774
- true
775
- );
776
- } else {
777
- console.error(`Error: Directory "${name}" already exists`);
778
- }
779
- process.exit(1);
780
- }
781
- const spinner = ora2("Creating MCP sandbox...").start();
782
- const result = await api.post("/api/mcp/sandboxes", {
783
- name
784
- });
785
- spinner.text = "Downloading template files...";
786
- await mkdir4(projectDir, { recursive: true });
787
- await pullFilesFromSandbox(result.id, projectDir);
788
- spinner.text = "Setting up project config...";
789
- const parentConfig = await loadParentConfig(cwd);
790
- await initConfigAt(projectDir, {
791
- ...parentConfig,
792
- mcpId: result.id
793
- // Always use the new sandbox's mcpId
794
- });
795
- spinner.succeed("MCP project created");
796
- if (json) {
797
- formatOutput(
798
- {
799
- success: true,
800
- projectDir,
801
- mcpId: result.id,
802
- sandboxId: result.sandboxId,
803
- previewUrl: result.previewUrl
804
- },
805
- true
806
- );
807
- } else {
808
- console.log();
809
- formatSuccess(`MCP project "${name}" created!`, false);
810
- console.log();
811
- console.log(` Project: ${projectDir}`);
812
- console.log(` MCP ID: ${result.id}`);
813
- console.log(` Preview URL: ${result.previewUrl}`);
814
- console.log();
815
- console.log("Next steps:");
816
- console.log(` cd ${name}`);
817
- console.log(" waniwani push # Sync files to sandbox");
818
- console.log(" waniwani dev # Watch mode with auto-sync");
819
- console.log(' waniwani task "..." # Send tasks to Claude');
820
- }
821
- } catch (error) {
822
- handleError(error, json);
823
- process.exit(1);
824
- }
825
- });
826
-
827
- // src/commands/login.ts
828
- import { spawn } from "child_process";
829
- import { createServer } from "http";
830
- import chalk4 from "chalk";
831
- import { Command as Command5 } from "commander";
832
- import ora3 from "ora";
833
- var CALLBACK_PORT = 54321;
834
- var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
835
- var CLIENT_NAME = "waniwani-cli";
836
- function generateCodeVerifier() {
837
- const array = new Uint8Array(32);
838
- crypto.getRandomValues(array);
839
- return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
840
- }
841
- async function generateCodeChallenge(verifier) {
842
- const encoder = new TextEncoder();
843
- const data = encoder.encode(verifier);
844
- const hash = await crypto.subtle.digest("SHA-256", data);
845
- return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
846
- }
847
- function generateState() {
848
- const array = new Uint8Array(16);
849
- crypto.getRandomValues(array);
850
- return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
851
- }
852
- async function registerClient() {
853
- const apiUrl = await config.getApiUrl();
854
- const response = await fetch(`${apiUrl}/api/auth/oauth2/register`, {
855
- method: "POST",
856
- headers: {
857
- "Content-Type": "application/json"
858
- },
859
- body: JSON.stringify({
860
- client_name: CLIENT_NAME,
861
- redirect_uris: [CALLBACK_URL],
862
- grant_types: ["authorization_code", "refresh_token"],
863
- response_types: ["code"],
864
- token_endpoint_auth_method: "none"
865
- })
866
- });
867
- if (!response.ok) {
868
- const error = await response.json().catch(() => ({}));
869
- throw new CLIError(
870
- error.error_description || "Failed to register OAuth client",
871
- "CLIENT_REGISTRATION_FAILED"
872
- );
873
- }
874
- return response.json();
875
- }
876
- async function openBrowser(url) {
877
- const [cmd, ...args] = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", url] : ["xdg-open", url];
878
- spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
879
- }
880
- async function waitForCallback(expectedState, timeoutMs = 3e5) {
881
- return new Promise((resolve, reject) => {
882
- let server = null;
883
- const sockets = /* @__PURE__ */ new Set();
884
- const timeout = setTimeout(() => {
885
- cleanup();
886
- reject(new CLIError("Login timed out", "LOGIN_TIMEOUT"));
887
- }, timeoutMs);
888
- const cleanup = () => {
889
- clearTimeout(timeout);
890
- for (const socket of sockets) {
891
- socket.destroy();
399
+ async function waitForCallback(expectedState, timeoutMs = 3e5) {
400
+ return new Promise((resolve, reject) => {
401
+ let server = null;
402
+ const sockets = /* @__PURE__ */ new Set();
403
+ const timeout = setTimeout(() => {
404
+ cleanup();
405
+ reject(new CLIError("Login timed out", "LOGIN_TIMEOUT"));
406
+ }, timeoutMs);
407
+ const cleanup = () => {
408
+ clearTimeout(timeout);
409
+ for (const socket of sockets) {
410
+ socket.destroy();
892
411
  }
893
412
  sockets.clear();
894
413
  server?.close();
@@ -1104,7 +623,7 @@ async function exchangeCodeForToken(code, codeVerifier, clientId, resource) {
1104
623
  }
1105
624
  return response.json();
1106
625
  }
1107
- var loginCommand = new Command5("login").description("Log in to WaniWani").option("--no-browser", "Don't open the browser automatically").action(async (options, command) => {
626
+ var loginCommand = new Command3("login").description("Log in to WaniWani").option("--no-browser", "Don't open the browser automatically").action(async (options, command) => {
1108
627
  const globalOptions = command.optsWithGlobals();
1109
628
  const json = globalOptions.json ?? false;
1110
629
  try {
@@ -1116,14 +635,14 @@ var loginCommand = new Command5("login").description("Log in to WaniWani").optio
1116
635
  formatOutput({ alreadyLoggedIn: true, refreshed: true }, true);
1117
636
  } else {
1118
637
  console.log(
1119
- chalk4.green("Session refreshed. You're still logged in.")
638
+ chalk3.green("Session refreshed. You're still logged in.")
1120
639
  );
1121
640
  }
1122
641
  return;
1123
642
  }
1124
643
  if (!json) {
1125
644
  console.log(
1126
- chalk4.yellow("Session expired. Starting new login flow...")
645
+ chalk3.yellow("Session expired. Starting new login flow...")
1127
646
  );
1128
647
  }
1129
648
  await auth.clear();
@@ -1132,7 +651,7 @@ var loginCommand = new Command5("login").description("Log in to WaniWani").optio
1132
651
  formatOutput({ alreadyLoggedIn: true }, true);
1133
652
  } else {
1134
653
  console.log(
1135
- chalk4.yellow(
654
+ chalk3.yellow(
1136
655
  "Already logged in. Use 'waniwani logout' to log out first."
1137
656
  )
1138
657
  );
@@ -1141,9 +660,9 @@ var loginCommand = new Command5("login").description("Log in to WaniWani").optio
1141
660
  }
1142
661
  }
1143
662
  if (!json) {
1144
- console.log(chalk4.bold("\nWaniWani CLI Login\n"));
663
+ console.log(chalk3.bold("\nWaniWani CLI Login\n"));
1145
664
  }
1146
- const spinner = ora3("Registering client...").start();
665
+ const spinner = ora("Registering client...").start();
1147
666
  const { client_id: clientId } = await registerClient();
1148
667
  spinner.text = "Preparing authentication...";
1149
668
  const codeVerifier = generateCodeVerifier();
@@ -1163,7 +682,7 @@ var loginCommand = new Command5("login").description("Log in to WaniWani").optio
1163
682
  console.log("Opening browser for authentication...\n");
1164
683
  console.log(`If the browser doesn't open, visit:
1165
684
  `);
1166
- console.log(chalk4.cyan(` ${authUrl.toString()}`));
685
+ console.log(chalk3.cyan(` ${authUrl.toString()}`));
1167
686
  console.log();
1168
687
  }
1169
688
  const callbackPromise = waitForCallback(state);
@@ -1209,8 +728,8 @@ var loginCommand = new Command5("login").description("Log in to WaniWani").optio
1209
728
  });
1210
729
 
1211
730
  // src/commands/logout.ts
1212
- import { Command as Command6 } from "commander";
1213
- var logoutCommand = new Command6("logout").description("Log out from WaniWani").action(async (_, command) => {
731
+ import { Command as Command4 } from "commander";
732
+ var logoutCommand = new Command4("logout").description("Log out from WaniWani").action(async (_, command) => {
1214
733
  const globalOptions = command.optsWithGlobals();
1215
734
  const json = globalOptions.json ?? false;
1216
735
  try {
@@ -1238,76 +757,501 @@ var logoutCommand = new Command6("logout").description("Log out from WaniWani").
1238
757
  import { Command as Command20 } from "commander";
1239
758
 
1240
759
  // src/commands/mcp/delete.ts
1241
- import { Command as Command7 } from "commander";
1242
- import ora4 from "ora";
1243
- var deleteCommand = new Command7("delete").description("Delete the MCP sandbox").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1244
- const globalOptions = command.optsWithGlobals();
1245
- const json = globalOptions.json ?? false;
1246
- try {
1247
- let mcpId = options.mcpId;
1248
- if (!mcpId) {
1249
- mcpId = await config.getMcpId();
1250
- if (!mcpId) {
1251
- throw new McpError("No active MCP. Use --mcp-id to specify one.");
1252
- }
1253
- }
1254
- const spinner = ora4("Deleting MCP sandbox...").start();
1255
- await api.delete(`/api/mcp/sandboxes/${mcpId}`);
1256
- spinner.succeed("MCP sandbox deleted");
1257
- if (await config.getMcpId() === mcpId) {
1258
- await config.setMcpId(null);
1259
- }
1260
- if (json) {
1261
- formatOutput({ deleted: mcpId }, true);
1262
- } else {
1263
- formatSuccess("MCP sandbox deleted and cleaned up.", false);
1264
- }
1265
- } catch (error) {
1266
- handleError(error, json);
1267
- process.exit(1);
1268
- }
1269
- });
760
+ import { confirm } from "@inquirer/prompts";
761
+ import chalk4 from "chalk";
762
+ import { Command as Command5 } from "commander";
763
+ import ora2 from "ora";
1270
764
 
1271
- // src/commands/mcp/deploy.ts
1272
- import { Command as Command8 } from "commander";
1273
- import ora5 from "ora";
1274
- var deployCommand = new Command8("deploy").description("Deploy MCP server to GitHub + Vercel from sandbox").option("--repo <name>", "GitHub repository name").option("--org <name>", "GitHub organization").option("--private", "Create private repository").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
765
+ // src/lib/api.ts
766
+ var ApiError = class extends CLIError {
767
+ constructor(message, code, statusCode, details) {
768
+ super(message, code, details);
769
+ this.statusCode = statusCode;
770
+ this.name = "ApiError";
771
+ }
772
+ };
773
+ async function request(method, path, options) {
774
+ const {
775
+ body,
776
+ requireAuth = true,
777
+ headers: extraHeaders = {}
778
+ } = options || {};
779
+ const headers = {
780
+ "Content-Type": "application/json",
781
+ ...extraHeaders
782
+ };
783
+ if (requireAuth) {
784
+ const token = await auth.getAccessToken();
785
+ if (!token) {
786
+ throw new AuthError(
787
+ "Not logged in. Run 'waniwani login' to authenticate."
788
+ );
789
+ }
790
+ headers.Authorization = `Bearer ${token}`;
791
+ }
792
+ const baseUrl = await config.getApiUrl();
793
+ const url = `${baseUrl}${path}`;
794
+ const response = await fetch(url, {
795
+ method,
796
+ headers,
797
+ body: body ? JSON.stringify(body) : void 0
798
+ });
799
+ if (response.status === 204) {
800
+ return void 0;
801
+ }
802
+ let data;
803
+ let rawBody;
804
+ try {
805
+ rawBody = await response.text();
806
+ data = JSON.parse(rawBody);
807
+ } catch {
808
+ throw new ApiError(
809
+ rawBody || `Request failed with status ${response.status}`,
810
+ "API_ERROR",
811
+ response.status,
812
+ { statusText: response.statusText }
813
+ );
814
+ }
815
+ if (!response.ok || data.error) {
816
+ const errorMessage = data.error?.message || data.message || data.error || rawBody || `Request failed with status ${response.status}`;
817
+ const errorCode = data.error?.code || data.code || "API_ERROR";
818
+ const errorDetails = {
819
+ ...data.error?.details,
820
+ statusText: response.statusText,
821
+ ...data.error ? {} : { rawResponse: data }
822
+ };
823
+ const error = {
824
+ code: errorCode,
825
+ message: errorMessage,
826
+ details: errorDetails
827
+ };
828
+ if (response.status === 401) {
829
+ const refreshed = await auth.tryRefreshToken();
830
+ if (refreshed) {
831
+ return request(method, path, options);
832
+ }
833
+ throw new AuthError(
834
+ "Session expired. Run 'waniwani login' to re-authenticate."
835
+ );
836
+ }
837
+ throw new ApiError(
838
+ error.message,
839
+ error.code,
840
+ response.status,
841
+ error.details
842
+ );
843
+ }
844
+ return data.data;
845
+ }
846
+ var api = {
847
+ get: (path, options) => request("GET", path, options),
848
+ post: (path, body, options) => request("POST", path, { body, ...options }),
849
+ delete: (path, options) => request("DELETE", path, options),
850
+ getBaseUrl: () => config.getApiUrl()
851
+ };
852
+
853
+ // src/lib/utils.ts
854
+ async function requireMcpId(mcpId) {
855
+ if (mcpId) return mcpId;
856
+ const configMcpId = await config.getMcpId();
857
+ if (!configMcpId) {
858
+ throw new McpError(
859
+ "No active MCP. Run 'waniwani mcp init <name>' or 'waniwani mcp use <name>'."
860
+ );
861
+ }
862
+ return configMcpId;
863
+ }
864
+ async function requireSessionId(mcpId) {
865
+ const repository = await api.get(
866
+ `/api/mcp/repositories/${mcpId}`
867
+ );
868
+ if (!repository.activeSandbox) {
869
+ throw new McpError(
870
+ "No active session. Run 'waniwani mcp dev' to start development."
871
+ );
872
+ }
873
+ return repository.activeSandbox.id;
874
+ }
875
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
876
+ ".png",
877
+ ".jpg",
878
+ ".jpeg",
879
+ ".gif",
880
+ ".ico",
881
+ ".webp",
882
+ ".svg",
883
+ ".woff",
884
+ ".woff2",
885
+ ".ttf",
886
+ ".eot",
887
+ ".otf",
888
+ ".zip",
889
+ ".tar",
890
+ ".gz",
891
+ ".pdf",
892
+ ".exe",
893
+ ".dll",
894
+ ".so",
895
+ ".dylib",
896
+ ".bin",
897
+ ".mp3",
898
+ ".mp4",
899
+ ".wav",
900
+ ".ogg",
901
+ ".webm"
902
+ ]);
903
+ function isBinaryPath(filePath) {
904
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
905
+ return BINARY_EXTENSIONS.has(ext);
906
+ }
907
+ function detectBinary(buffer) {
908
+ const sample = buffer.subarray(0, 8192);
909
+ return sample.includes(0);
910
+ }
911
+
912
+ // src/commands/mcp/delete.ts
913
+ var deleteCommand = new Command5("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) => {
1275
914
  const globalOptions = command.optsWithGlobals();
1276
915
  const json = globalOptions.json ?? false;
1277
916
  try {
1278
- let mcpId = options.mcpId;
1279
- if (!mcpId) {
1280
- mcpId = await config.getMcpId();
1281
- if (!mcpId) {
1282
- throw new McpError(
1283
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1284
- );
917
+ const mcpId = await requireMcpId(options.mcpId);
918
+ const mcp = await api.get(
919
+ `/api/mcp/repositories/${mcpId}`
920
+ );
921
+ if (!options.force && !json) {
922
+ console.log();
923
+ console.log(chalk4.yellow("This will permanently delete:"));
924
+ console.log(` - MCP: ${mcp.name}`);
925
+ if (mcp.activeSandbox) {
926
+ console.log(" - Active sandbox");
927
+ }
928
+ console.log();
929
+ const confirmed = await confirm({
930
+ message: `Delete "${mcp.name}"?`,
931
+ default: false
932
+ });
933
+ if (!confirmed) {
934
+ console.log("Cancelled.");
935
+ return;
1285
936
  }
1286
937
  }
1287
- const spinner = ora5("Deploying to GitHub...").start();
1288
- const result = await api.post(
1289
- `/api/mcp/sandboxes/${mcpId}/deploy`,
1290
- {
1291
- repoName: options.repo,
1292
- org: options.org,
1293
- private: options.private ?? false
938
+ const spinner = ora2("Deleting MCP...").start();
939
+ await api.delete(`/api/mcp/repositories/${mcpId}`);
940
+ spinner.succeed("MCP deleted");
941
+ if (await config.getMcpId() === mcpId) {
942
+ await config.setMcpId(null);
943
+ }
944
+ if (json) {
945
+ formatOutput({ deleted: mcpId }, true);
946
+ } else {
947
+ formatSuccess("MCP deleted.", false);
948
+ }
949
+ } catch (error) {
950
+ handleError(error, json);
951
+ process.exit(1);
952
+ }
953
+ });
954
+
955
+ // src/commands/mcp/deploy.ts
956
+ import { input } from "@inquirer/prompts";
957
+ import { Command as Command6 } from "commander";
958
+ import ora3 from "ora";
959
+
960
+ // src/lib/sync.ts
961
+ import { existsSync as existsSync3 } from "fs";
962
+ import { mkdir as mkdir3, readdir, readFile as readFile3, stat, writeFile as writeFile3 } from "fs/promises";
963
+ import { dirname, join as join4, relative } from "path";
964
+ import ignore from "ignore";
965
+ var PROJECT_DIR = ".waniwani";
966
+ var SETTINGS_FILE = "settings.json";
967
+ async function findProjectRoot(startDir) {
968
+ let current = startDir;
969
+ const root = dirname(current);
970
+ while (current !== root) {
971
+ if (existsSync3(join4(current, PROJECT_DIR))) {
972
+ return current;
973
+ }
974
+ const parent = dirname(current);
975
+ if (parent === current) break;
976
+ current = parent;
977
+ }
978
+ if (existsSync3(join4(current, PROJECT_DIR))) {
979
+ return current;
980
+ }
981
+ return null;
982
+ }
983
+ async function loadProjectMcpId(projectRoot) {
984
+ const settingsPath = join4(projectRoot, PROJECT_DIR, SETTINGS_FILE);
985
+ try {
986
+ const content = await readFile3(settingsPath, "utf-8");
987
+ const settings = JSON.parse(content);
988
+ return settings.mcp?.id ?? null;
989
+ } catch {
990
+ return null;
991
+ }
992
+ }
993
+ var DEFAULT_IGNORE_PATTERNS = [
994
+ ".waniwani",
995
+ ".git",
996
+ "node_modules",
997
+ ".env",
998
+ ".env.*",
999
+ ".DS_Store",
1000
+ "*.log",
1001
+ ".cache",
1002
+ "dist",
1003
+ "coverage",
1004
+ ".turbo",
1005
+ ".next",
1006
+ ".nuxt",
1007
+ ".vercel"
1008
+ ];
1009
+ async function loadIgnorePatterns(projectRoot) {
1010
+ const ig = ignore();
1011
+ ig.add(DEFAULT_IGNORE_PATTERNS);
1012
+ const gitignorePath = join4(projectRoot, ".gitignore");
1013
+ if (existsSync3(gitignorePath)) {
1014
+ try {
1015
+ const content = await readFile3(gitignorePath, "utf-8");
1016
+ ig.add(content);
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ return ig;
1021
+ }
1022
+ async function collectFiles(projectRoot) {
1023
+ const ig = await loadIgnorePatterns(projectRoot);
1024
+ const files = [];
1025
+ async function walk(dir) {
1026
+ const entries = await readdir(dir, { withFileTypes: true });
1027
+ for (const entry of entries) {
1028
+ const fullPath = join4(dir, entry.name);
1029
+ const relativePath = relative(projectRoot, fullPath);
1030
+ if (ig.ignores(relativePath)) {
1031
+ continue;
1032
+ }
1033
+ if (entry.isDirectory()) {
1034
+ await walk(fullPath);
1035
+ } else if (entry.isFile()) {
1036
+ try {
1037
+ const content = await readFile3(fullPath);
1038
+ const isBinary = isBinaryPath(fullPath) || detectBinary(content);
1039
+ files.push({
1040
+ path: relativePath,
1041
+ content: isBinary ? content.toString("base64") : content.toString("utf8"),
1042
+ encoding: isBinary ? "base64" : "utf8"
1043
+ });
1044
+ } catch {
1045
+ }
1294
1046
  }
1047
+ }
1048
+ }
1049
+ await walk(projectRoot);
1050
+ return files;
1051
+ }
1052
+ async function pullFilesFromGithub(mcpId, targetDir) {
1053
+ const result = await api.get(
1054
+ `/api/mcp/repositories/${mcpId}/files`
1055
+ );
1056
+ const writtenFiles = [];
1057
+ for (const file of result.files) {
1058
+ const localPath = join4(targetDir, file.path);
1059
+ const dir = dirname(localPath);
1060
+ await mkdir3(dir, { recursive: true });
1061
+ if (file.encoding === "base64") {
1062
+ await writeFile3(localPath, Buffer.from(file.content, "base64"));
1063
+ } else {
1064
+ await writeFile3(localPath, file.content, "utf8");
1065
+ }
1066
+ writtenFiles.push(file.path);
1067
+ }
1068
+ return { count: writtenFiles.length, files: writtenFiles };
1069
+ }
1070
+ async function collectSingleFile(projectRoot, filePath) {
1071
+ const fullPath = join4(projectRoot, filePath);
1072
+ const relativePath = relative(projectRoot, fullPath);
1073
+ if (!existsSync3(fullPath)) {
1074
+ return null;
1075
+ }
1076
+ try {
1077
+ const fileStat = await stat(fullPath);
1078
+ if (!fileStat.isFile()) {
1079
+ return null;
1080
+ }
1081
+ const content = await readFile3(fullPath);
1082
+ const isBinary = isBinaryPath(fullPath) || detectBinary(content);
1083
+ return {
1084
+ path: relativePath,
1085
+ content: isBinary ? content.toString("base64") : content.toString("utf8"),
1086
+ encoding: isBinary ? "base64" : "utf8"
1087
+ };
1088
+ } catch {
1089
+ return null;
1090
+ }
1091
+ }
1092
+
1093
+ // src/commands/mcp/deploy.ts
1094
+ var deployCommand = new Command6("deploy").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) => {
1095
+ const globalOptions = command.optsWithGlobals();
1096
+ const json = globalOptions.json ?? false;
1097
+ try {
1098
+ const mcpId = await requireMcpId(options.mcpId);
1099
+ const projectRoot = await findProjectRoot(process.cwd());
1100
+ if (!projectRoot) {
1101
+ throw new CLIError(
1102
+ "Not in a WaniWani project. Run 'waniwani mcp init <name>' first.",
1103
+ "NOT_IN_PROJECT"
1104
+ );
1105
+ }
1106
+ let message = options.message;
1107
+ if (!message) {
1108
+ message = await input({
1109
+ message: "Commit message:",
1110
+ validate: (value) => value.trim() ? true : "Commit message is required"
1111
+ });
1112
+ }
1113
+ const spinner = ora3("Collecting files...").start();
1114
+ const files = await collectFiles(projectRoot);
1115
+ if (files.length === 0) {
1116
+ spinner.fail("No files to deploy");
1117
+ return;
1118
+ }
1119
+ spinner.text = `Pushing ${files.length} files to GitHub...`;
1120
+ const result = await api.post(
1121
+ `/api/mcp/repositories/${mcpId}/deploy`,
1122
+ { files, message }
1295
1123
  );
1296
- spinner.succeed("Deployment complete!");
1124
+ spinner.succeed(`Pushed to GitHub (${result.commitSha.slice(0, 7)})`);
1297
1125
  if (json) {
1298
1126
  formatOutput(result, true);
1299
1127
  } else {
1300
1128
  console.log();
1301
- formatSuccess("MCP server deployed!", false);
1129
+ formatSuccess("Files pushed to GitHub!", false);
1302
1130
  console.log();
1303
- console.log(` Repository: ${result.repository.url}`);
1304
- if (result.deployment.url) {
1305
- console.log(` Deployment: ${result.deployment.url}`);
1131
+ console.log("Deployment will start automatically via webhook.");
1132
+ }
1133
+ } catch (error) {
1134
+ handleError(error, json);
1135
+ process.exit(1);
1136
+ }
1137
+ });
1138
+
1139
+ // src/commands/mcp/dev.ts
1140
+ import { watch } from "chokidar";
1141
+ import { Command as Command7 } from "commander";
1142
+ import ora4 from "ora";
1143
+ var devCommand = new Command7("dev").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) => {
1144
+ const globalOptions = command.optsWithGlobals();
1145
+ const json = globalOptions.json ?? false;
1146
+ try {
1147
+ const projectRoot = await findProjectRoot(process.cwd());
1148
+ if (!projectRoot) {
1149
+ throw new CLIError(
1150
+ "Not in a WaniWani project. Run 'waniwani mcp init <name>' first.",
1151
+ "NOT_IN_PROJECT"
1152
+ );
1153
+ }
1154
+ let mcpId = options.mcpId;
1155
+ if (!mcpId) {
1156
+ mcpId = await loadProjectMcpId(projectRoot);
1157
+ }
1158
+ if (!mcpId) {
1159
+ mcpId = await config.getMcpId();
1160
+ }
1161
+ if (!mcpId) {
1162
+ throw new CLIError(
1163
+ "No MCP found. Run 'waniwani mcp init <name>' or use --mcp-id.",
1164
+ "NO_MCP"
1165
+ );
1166
+ }
1167
+ const spinner = ora4("Starting development environment...").start();
1168
+ spinner.text = "Starting session...";
1169
+ let sessionId;
1170
+ try {
1171
+ const sessionResponse = await api.post(
1172
+ `/api/mcp/repositories/${mcpId}/session`,
1173
+ {}
1174
+ );
1175
+ sessionId = sessionResponse.sandbox.id;
1176
+ } catch {
1177
+ const existing = await api.get(
1178
+ `/api/mcp/repositories/${mcpId}/session`
1179
+ );
1180
+ if (!existing) {
1181
+ throw new CLIError("Failed to start session", "SESSION_ERROR");
1306
1182
  }
1183
+ sessionId = existing.id;
1184
+ }
1185
+ spinner.text = "Syncing files to sandbox...";
1186
+ const files = await collectFiles(projectRoot);
1187
+ if (files.length > 0) {
1188
+ await api.post(
1189
+ `/api/mcp/sessions/${sessionId}/files`,
1190
+ { files }
1191
+ );
1192
+ }
1193
+ spinner.text = "Starting MCP server...";
1194
+ const serverResult = await api.post(
1195
+ `/api/mcp/sessions/${sessionId}/server`,
1196
+ { action: "start" }
1197
+ );
1198
+ spinner.succeed("Development environment ready");
1199
+ console.log();
1200
+ formatSuccess("Live preview started!", false);
1201
+ console.log();
1202
+ console.log(` Preview URL: ${serverResult.previewUrl}`);
1203
+ console.log();
1204
+ console.log(` MCP Inspector:`);
1205
+ console.log(
1206
+ ` npx @anthropic-ai/mcp-inspector@latest "${serverResult.previewUrl}/mcp"`
1207
+ );
1208
+ console.log();
1209
+ if (options.watch !== false) {
1210
+ console.log("Watching for file changes... (Ctrl+C to stop)");
1307
1211
  console.log();
1308
- if (result.deployment.note) {
1309
- console.log(`Note: ${result.deployment.note}`);
1310
- }
1212
+ const ig = await loadIgnorePatterns(projectRoot);
1213
+ const watcher = watch(projectRoot, {
1214
+ ignored: (path) => {
1215
+ const relative2 = path.replace(`${projectRoot}/`, "");
1216
+ if (relative2 === path) return false;
1217
+ return ig.ignores(relative2);
1218
+ },
1219
+ persistent: true,
1220
+ ignoreInitial: true,
1221
+ awaitWriteFinish: {
1222
+ stabilityThreshold: 100,
1223
+ pollInterval: 50
1224
+ }
1225
+ });
1226
+ const syncFile = async (filePath) => {
1227
+ const relativePath = filePath.replace(`${projectRoot}/`, "");
1228
+ const file = await collectSingleFile(projectRoot, relativePath);
1229
+ if (file) {
1230
+ try {
1231
+ await api.post(
1232
+ `/api/mcp/sessions/${sessionId}/files`,
1233
+ { files: [file] }
1234
+ );
1235
+ console.log(` Synced: ${relativePath}`);
1236
+ } catch {
1237
+ console.error(` Failed to sync: ${relativePath}`);
1238
+ }
1239
+ }
1240
+ };
1241
+ watcher.on("add", syncFile);
1242
+ watcher.on("change", syncFile);
1243
+ watcher.on("unlink", (filePath) => {
1244
+ const relativePath = filePath.replace(`${projectRoot}/`, "");
1245
+ console.log(` Deleted: ${relativePath}`);
1246
+ });
1247
+ process.on("SIGINT", async () => {
1248
+ console.log();
1249
+ console.log("Stopping development environment...");
1250
+ await watcher.close();
1251
+ process.exit(0);
1252
+ });
1253
+ await new Promise(() => {
1254
+ });
1311
1255
  }
1312
1256
  } catch (error) {
1313
1257
  handleError(error, json);
@@ -1316,28 +1260,21 @@ var deployCommand = new Command8("deploy").description("Deploy MCP server to Git
1316
1260
  });
1317
1261
 
1318
1262
  // src/commands/mcp/file/index.ts
1319
- import { Command as Command12 } from "commander";
1263
+ import { Command as Command11 } from "commander";
1320
1264
 
1321
1265
  // src/commands/mcp/file/list.ts
1322
1266
  import chalk5 from "chalk";
1323
- import { Command as Command9 } from "commander";
1324
- import ora6 from "ora";
1325
- 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) => {
1267
+ import { Command as Command8 } from "commander";
1268
+ import ora5 from "ora";
1269
+ 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) => {
1326
1270
  const globalOptions = command.optsWithGlobals();
1327
1271
  const json = globalOptions.json ?? false;
1328
1272
  try {
1329
- let mcpId = options.mcpId;
1330
- if (!mcpId) {
1331
- mcpId = await config.getMcpId();
1332
- if (!mcpId) {
1333
- throw new McpError(
1334
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1335
- );
1336
- }
1337
- }
1338
- const spinner = ora6(`Listing ${path}...`).start();
1273
+ const mcpId = await requireMcpId(options.mcpId);
1274
+ const sessionId = await requireSessionId(mcpId);
1275
+ const spinner = ora5(`Listing ${path}...`).start();
1339
1276
  const result = await api.get(
1340
- `/api/mcp/sandboxes/${mcpId}/files/list?path=${encodeURIComponent(path)}`
1277
+ `/api/mcp/sessions/${sessionId}/files/list?path=${encodeURIComponent(path)}`
1341
1278
  );
1342
1279
  spinner.stop();
1343
1280
  if (json) {
@@ -1372,25 +1309,18 @@ function formatSize(bytes) {
1372
1309
 
1373
1310
  // src/commands/mcp/file/read.ts
1374
1311
  import { writeFile as writeFile4 } from "fs/promises";
1375
- import { Command as Command10 } from "commander";
1376
- import ora7 from "ora";
1377
- 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) => {
1312
+ import { Command as Command9 } from "commander";
1313
+ import ora6 from "ora";
1314
+ 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) => {
1378
1315
  const globalOptions = command.optsWithGlobals();
1379
1316
  const json = globalOptions.json ?? false;
1380
1317
  try {
1381
- let mcpId = options.mcpId;
1382
- if (!mcpId) {
1383
- mcpId = await config.getMcpId();
1384
- if (!mcpId) {
1385
- throw new McpError(
1386
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1387
- );
1388
- }
1389
- }
1318
+ const mcpId = await requireMcpId(options.mcpId);
1319
+ const sessionId = await requireSessionId(mcpId);
1390
1320
  const encoding = options.base64 ? "base64" : "utf8";
1391
- const spinner = ora7(`Reading ${path}...`).start();
1321
+ const spinner = ora6(`Reading ${path}...`).start();
1392
1322
  const result = await api.get(
1393
- `/api/mcp/sandboxes/${mcpId}/files?path=${encodeURIComponent(path)}&encoding=${encoding}`
1323
+ `/api/mcp/sessions/${sessionId}/files?path=${encodeURIComponent(path)}&encoding=${encoding}`
1394
1324
  );
1395
1325
  spinner.stop();
1396
1326
  if (!result.exists) {
@@ -1419,22 +1349,15 @@ var readCommand = new Command10("read").description("Read a file from the MCP sa
1419
1349
  });
1420
1350
 
1421
1351
  // src/commands/mcp/file/write.ts
1422
- import { readFile as readFile5 } from "fs/promises";
1423
- import { Command as Command11 } from "commander";
1424
- import ora8 from "ora";
1425
- 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) => {
1352
+ import { readFile as readFile4 } from "fs/promises";
1353
+ import { Command as Command10 } from "commander";
1354
+ import ora7 from "ora";
1355
+ 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) => {
1426
1356
  const globalOptions = command.optsWithGlobals();
1427
1357
  const json = globalOptions.json ?? false;
1428
1358
  try {
1429
- let mcpId = options.mcpId;
1430
- if (!mcpId) {
1431
- mcpId = await config.getMcpId();
1432
- if (!mcpId) {
1433
- throw new McpError(
1434
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1435
- );
1436
- }
1437
- }
1359
+ const mcpId = await requireMcpId(options.mcpId);
1360
+ const sessionId = await requireSessionId(mcpId);
1438
1361
  let content;
1439
1362
  let encoding = "utf8";
1440
1363
  if (options.content) {
@@ -1443,7 +1366,7 @@ var writeCommand = new Command11("write").description("Write a file to the MCP s
1443
1366
  encoding = "base64";
1444
1367
  }
1445
1368
  } else if (options.file) {
1446
- const fileBuffer = await readFile5(options.file);
1369
+ const fileBuffer = await readFile4(options.file);
1447
1370
  if (options.base64) {
1448
1371
  content = fileBuffer.toString("base64");
1449
1372
  encoding = "base64";
@@ -1456,9 +1379,9 @@ var writeCommand = new Command11("write").description("Write a file to the MCP s
1456
1379
  "MISSING_CONTENT"
1457
1380
  );
1458
1381
  }
1459
- const spinner = ora8(`Writing ${path}...`).start();
1382
+ const spinner = ora7(`Writing ${path}...`).start();
1460
1383
  const result = await api.post(
1461
- `/api/mcp/sandboxes/${mcpId}/files`,
1384
+ `/api/mcp/sessions/${sessionId}/files`,
1462
1385
  {
1463
1386
  files: [{ path, content, encoding }]
1464
1387
  }
@@ -1476,19 +1399,119 @@ var writeCommand = new Command11("write").description("Write a file to the MCP s
1476
1399
  });
1477
1400
 
1478
1401
  // src/commands/mcp/file/index.ts
1479
- var fileCommand = new Command12("file").description("File operations in MCP sandbox").addCommand(readCommand).addCommand(writeCommand).addCommand(listCommand);
1402
+ var fileCommand = new Command11("file").description("File operations in MCP sandbox").addCommand(readCommand).addCommand(writeCommand).addCommand(listCommand);
1403
+
1404
+ // src/commands/mcp/init.ts
1405
+ import { existsSync as existsSync4 } from "fs";
1406
+ import { readFile as readFile5 } from "fs/promises";
1407
+ import { join as join5 } from "path";
1408
+ import { Command as Command12 } from "commander";
1409
+ import { execa } from "execa";
1410
+ import ora8 from "ora";
1411
+ async function loadParentConfig(cwd) {
1412
+ const parentConfigPath = join5(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1413
+ if (!existsSync4(parentConfigPath)) {
1414
+ return null;
1415
+ }
1416
+ try {
1417
+ const content = await readFile5(parentConfigPath, "utf-8");
1418
+ const config2 = JSON.parse(content);
1419
+ const { mcp: _, ...rest } = config2;
1420
+ return rest;
1421
+ } catch {
1422
+ return null;
1423
+ }
1424
+ }
1425
+ var initCommand = new Command12("init").description("Create a new MCP project").argument("<name>", "Name for the MCP project").option("--no-clone", "Skip automatic git clone (just output the command)").action(async (name, options, command) => {
1426
+ const globalOptions = command.optsWithGlobals();
1427
+ const json = globalOptions.json ?? false;
1428
+ try {
1429
+ const cwd = process.cwd();
1430
+ const projectDir = join5(cwd, name);
1431
+ if (existsSync4(projectDir)) {
1432
+ if (json) {
1433
+ formatOutput(
1434
+ {
1435
+ success: false,
1436
+ error: `Directory "${name}" already exists`
1437
+ },
1438
+ true
1439
+ );
1440
+ } else {
1441
+ console.error(`Error: Directory "${name}" already exists`);
1442
+ }
1443
+ process.exit(1);
1444
+ }
1445
+ const spinner = ora8("Creating MCP...").start();
1446
+ const result = await api.post(
1447
+ "/api/mcp/repositories",
1448
+ { name }
1449
+ );
1450
+ if (options.clone !== false) {
1451
+ spinner.text = "Cloning repository...";
1452
+ await execa("git", ["clone", result.cloneUrl, name], { cwd });
1453
+ spinner.text = "Setting up project config...";
1454
+ const parentConfig = await loadParentConfig(cwd);
1455
+ await initConfigAt(projectDir, {
1456
+ ...parentConfig,
1457
+ mcp: {
1458
+ id: result.repository.id,
1459
+ name: result.repository.name
1460
+ }
1461
+ });
1462
+ spinner.succeed("MCP project created");
1463
+ } else {
1464
+ spinner.succeed("MCP created");
1465
+ }
1466
+ if (json) {
1467
+ formatOutput(
1468
+ {
1469
+ success: true,
1470
+ projectDir: options.clone !== false ? projectDir : null,
1471
+ mcpId: result.repository.id
1472
+ },
1473
+ true
1474
+ );
1475
+ } else {
1476
+ console.log();
1477
+ formatSuccess(`MCP "${name}" created!`, false);
1478
+ console.log();
1479
+ if (options.clone !== false) {
1480
+ console.log("Next steps:");
1481
+ console.log(` cd ${name}`);
1482
+ console.log(
1483
+ " waniwani mcp dev # Start live preview with file watching"
1484
+ );
1485
+ console.log(" waniwani mcp push # Deploy to production");
1486
+ } else {
1487
+ console.log("Clone your repository:");
1488
+ console.log(` ${result.cloneCommand}`);
1489
+ console.log(` cd ${name}`);
1490
+ console.log();
1491
+ console.log("Then start developing:");
1492
+ console.log(
1493
+ " waniwani mcp dev # Start live preview with file watching"
1494
+ );
1495
+ console.log(" waniwani mcp push # Deploy to production");
1496
+ }
1497
+ }
1498
+ } catch (error) {
1499
+ handleError(error, json);
1500
+ process.exit(1);
1501
+ }
1502
+ });
1480
1503
 
1481
1504
  // src/commands/mcp/list.ts
1482
1505
  import chalk6 from "chalk";
1483
1506
  import { Command as Command13 } from "commander";
1484
1507
  import ora9 from "ora";
1485
- var listCommand2 = new Command13("list").description("List all MCPs in your organization").option("--all", "Include stopped/expired MCPs").action(async (options, command) => {
1508
+ var listCommand2 = new Command13("list").description("List all MCPs in your organization").action(async (_, command) => {
1486
1509
  const globalOptions = command.optsWithGlobals();
1487
1510
  const json = globalOptions.json ?? false;
1488
1511
  try {
1489
1512
  const spinner = ora9("Fetching MCPs...").start();
1490
1513
  const mcps = await api.get(
1491
- `/api/mcp/sandboxes${options.all ? "?all=true" : ""}`
1514
+ "/api/mcp/repositories"
1492
1515
  );
1493
1516
  spinner.stop();
1494
1517
  const activeMcpId = await config.getMcpId();
@@ -1506,29 +1529,29 @@ var listCommand2 = new Command13("list").description("List all MCPs in your orga
1506
1529
  } else {
1507
1530
  if (mcps.length === 0) {
1508
1531
  console.log("No MCPs found.");
1509
- console.log("\nCreate a new MCP sandbox: waniwani mcp create <name>");
1532
+ console.log("\nCreate a new MCP: waniwani mcp init <name>");
1510
1533
  return;
1511
1534
  }
1512
1535
  console.log(chalk6.bold("\nMCPs:\n"));
1513
1536
  const rows = mcps.map((m) => {
1514
1537
  const isActive = m.id === activeMcpId;
1515
- const statusColor = m.status === "active" ? chalk6.green : m.status === "stopped" ? chalk6.red : chalk6.yellow;
1538
+ const deployStatus = m.deployedAt ? chalk6.green("Deployed") : chalk6.yellow("Pending");
1539
+ const sandboxStatus = m.activeSandbox ? chalk6.green("Active") : chalk6.gray("None");
1540
+ const lastDeploy = m.deployedAt ? new Date(m.deployedAt).toLocaleDateString() : chalk6.gray("Never");
1516
1541
  return [
1517
- isActive ? chalk6.cyan(`* ${m.id.slice(0, 8)}`) : ` ${m.id.slice(0, 8)}`,
1518
- m.name,
1519
- statusColor(m.status),
1520
- m.previewUrl,
1521
- m.createdAt ? new Date(m.createdAt).toLocaleString() : "N/A"
1542
+ isActive ? chalk6.cyan(`* ${m.name}`) : ` ${m.name}`,
1543
+ deployStatus,
1544
+ sandboxStatus,
1545
+ lastDeploy
1522
1546
  ];
1523
1547
  });
1524
- formatTable(
1525
- ["ID", "Name", "Status", "Preview URL", "Created"],
1526
- rows,
1527
- false
1528
- );
1548
+ formatTable(["Name", "Status", "Sandbox", "Last Deploy"], rows, false);
1529
1549
  console.log();
1530
1550
  if (activeMcpId) {
1531
- console.log(`Active MCP: ${chalk6.cyan(activeMcpId.slice(0, 8))}`);
1551
+ const activeMcp = mcps.find((m) => m.id === activeMcpId);
1552
+ if (activeMcp) {
1553
+ console.log(`Active MCP: ${chalk6.cyan(activeMcp.name)}`);
1554
+ }
1532
1555
  }
1533
1556
  console.log("\nSelect an MCP: waniwani mcp use <name>");
1534
1557
  }
@@ -1556,15 +1579,8 @@ var logsCommand = new Command14("logs").description("Stream logs from the MCP se
1556
1579
  process.on("SIGINT", cleanup);
1557
1580
  process.on("SIGTERM", cleanup);
1558
1581
  try {
1559
- let mcpId = options.mcpId;
1560
- if (!mcpId) {
1561
- mcpId = await config.getMcpId();
1562
- if (!mcpId) {
1563
- throw new McpError(
1564
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1565
- );
1566
- }
1567
- }
1582
+ const mcpId = await requireMcpId(options.mcpId);
1583
+ const sessionId = await requireSessionId(mcpId);
1568
1584
  const token = await auth.getAccessToken();
1569
1585
  if (!token) {
1570
1586
  throw new AuthError(
@@ -1575,20 +1591,20 @@ var logsCommand = new Command14("logs").description("Stream logs from the MCP se
1575
1591
  if (!cmdId) {
1576
1592
  const spinner = ora10("Getting server status...").start();
1577
1593
  const status = await api.post(
1578
- `/api/mcp/sandboxes/${mcpId}/server`,
1594
+ `/api/mcp/sessions/${sessionId}/server`,
1579
1595
  { action: "status" }
1580
1596
  );
1581
1597
  spinner.stop();
1582
1598
  if (!status.running || !status.cmdId) {
1583
1599
  throw new McpError(
1584
- "No server is running. Run 'waniwani mcp start' first."
1600
+ "No server is running. Run 'waniwani mcp dev' first."
1585
1601
  );
1586
1602
  }
1587
1603
  cmdId = status.cmdId;
1588
1604
  }
1589
1605
  const baseUrl = await api.getBaseUrl();
1590
1606
  const streamParam = options.follow ? "?stream=true" : "";
1591
- const url = `${baseUrl}/api/mcp/sandboxes/${mcpId}/commands/${cmdId}${streamParam}`;
1607
+ const url = `${baseUrl}/api/mcp/sessions/${sessionId}/commands/${cmdId}${streamParam}`;
1592
1608
  if (!json) {
1593
1609
  console.log(chalk7.gray(`Streaming logs for command ${cmdId}...`));
1594
1610
  console.log(chalk7.gray("Press Ctrl+C to stop\n"));
@@ -1699,19 +1715,12 @@ var runCommandCommand = new Command15("run-command").description("Run a command
1699
1715
  const globalOptions = command.optsWithGlobals();
1700
1716
  const json = globalOptions.json ?? false;
1701
1717
  try {
1702
- let mcpId = options.mcpId;
1703
- if (!mcpId) {
1704
- mcpId = await config.getMcpId();
1705
- if (!mcpId) {
1706
- throw new McpError(
1707
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1708
- );
1709
- }
1710
- }
1718
+ const mcpId = await requireMcpId(options.mcpId);
1719
+ const sessionId = await requireSessionId(mcpId);
1711
1720
  const timeout = options.timeout ? Number.parseInt(options.timeout, 10) : void 0;
1712
1721
  const spinner = ora11(`Running: ${cmd} ${args.join(" ")}`.trim()).start();
1713
1722
  const result = await api.post(
1714
- `/api/mcp/sandboxes/${mcpId}/commands`,
1723
+ `/api/mcp/sessions/${sessionId}/commands`,
1715
1724
  {
1716
1725
  command: cmd,
1717
1726
  args: args.length > 0 ? args : void 0,
@@ -1754,49 +1763,76 @@ var runCommandCommand = new Command15("run-command").description("Run a command
1754
1763
  }
1755
1764
  });
1756
1765
 
1757
- // src/commands/mcp/start.ts
1766
+ // src/commands/mcp/status.ts
1758
1767
  import chalk9 from "chalk";
1759
1768
  import { Command as Command16 } from "commander";
1760
1769
  import ora12 from "ora";
1761
- var startCommand = new Command16("start").description("Start the MCP server (npm run dev)").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1770
+ var statusCommand = new Command16("status").description("Show current MCP status").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1762
1771
  const globalOptions = command.optsWithGlobals();
1763
1772
  const json = globalOptions.json ?? false;
1764
1773
  try {
1765
- let mcpId = options.mcpId;
1766
- if (!mcpId) {
1767
- mcpId = await config.getMcpId();
1768
- if (!mcpId) {
1769
- throw new McpError(
1770
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1771
- );
1772
- }
1773
- }
1774
- const spinner = ora12("Starting MCP server...").start();
1775
- const result = await api.post(
1776
- `/api/mcp/sandboxes/${mcpId}/server`,
1777
- { action: "start" }
1774
+ const mcpId = await requireMcpId(options.mcpId);
1775
+ const spinner = ora12("Fetching MCP status...").start();
1776
+ const result = await api.get(
1777
+ `/api/mcp/repositories/${mcpId}`
1778
1778
  );
1779
- spinner.succeed("MCP server started");
1779
+ let serverStatus = null;
1780
+ if (result.activeSandbox) {
1781
+ serverStatus = await api.post(
1782
+ `/api/mcp/sessions/${result.activeSandbox.id}/server`,
1783
+ { action: "status" }
1784
+ ).catch(() => null);
1785
+ }
1786
+ spinner.stop();
1780
1787
  if (json) {
1781
- formatOutput(result, true);
1788
+ formatOutput({ ...result, server: serverStatus }, true);
1782
1789
  } else {
1790
+ const deployStatus = result.deployedAt ? chalk9.green("Deployed") : chalk9.yellow("Pending");
1791
+ const items = [
1792
+ { label: "Name", value: result.name },
1793
+ { label: "MCP ID", value: result.id },
1794
+ { label: "Status", value: deployStatus },
1795
+ {
1796
+ label: "Last Deploy",
1797
+ value: result.deployedAt ? new Date(result.deployedAt).toLocaleString() : chalk9.gray("Never")
1798
+ },
1799
+ {
1800
+ label: "Created",
1801
+ value: new Date(result.createdAt).toLocaleString()
1802
+ }
1803
+ ];
1804
+ if (result.activeSandbox) {
1805
+ const sandbox = result.activeSandbox;
1806
+ const serverRunning = serverStatus?.running ?? sandbox.serverRunning;
1807
+ const serverStatusColor = serverRunning ? chalk9.green : chalk9.yellow;
1808
+ items.push(
1809
+ { label: "", value: "" },
1810
+ // Separator
1811
+ { label: "Sandbox", value: chalk9.green("Active") },
1812
+ { label: "Preview URL", value: sandbox.previewUrl },
1813
+ {
1814
+ label: "Server",
1815
+ value: serverStatusColor(serverRunning ? "Running" : "Stopped")
1816
+ },
1817
+ {
1818
+ label: "Expires",
1819
+ value: sandbox.expiresAt ? new Date(sandbox.expiresAt).toLocaleString() : chalk9.gray("N/A")
1820
+ }
1821
+ );
1822
+ } else {
1823
+ items.push(
1824
+ { label: "", value: "" },
1825
+ // Separator
1826
+ { label: "Sandbox", value: chalk9.gray("None") }
1827
+ );
1828
+ }
1829
+ formatList(items, false);
1783
1830
  console.log();
1784
- formatList(
1785
- [
1786
- { label: "Command ID", value: result.cmdId },
1787
- { label: "Preview URL", value: chalk9.cyan(result.previewUrl) }
1788
- ],
1789
- false
1790
- );
1791
- console.log();
1792
- console.log(chalk9.bold("Test with MCP Inspector:"));
1793
- console.log(
1794
- ` npx @modelcontextprotocol/inspector --url ${result.previewUrl}/mcp`
1795
- );
1796
- console.log();
1797
- console.log(
1798
- chalk9.gray("Run 'waniwani mcp logs' to stream server output")
1799
- );
1831
+ if (!result.activeSandbox) {
1832
+ console.log("Start development: waniwani mcp dev");
1833
+ } else if (!serverStatus?.running) {
1834
+ console.log("View logs: waniwani mcp logs");
1835
+ }
1800
1836
  }
1801
1837
  } catch (error) {
1802
1838
  handleError(error, json);
@@ -1804,58 +1840,30 @@ var startCommand = new Command16("start").description("Start the MCP server (npm
1804
1840
  }
1805
1841
  });
1806
1842
 
1807
- // src/commands/mcp/status.ts
1808
- import chalk10 from "chalk";
1843
+ // src/commands/mcp/stop.ts
1809
1844
  import { Command as Command17 } from "commander";
1810
1845
  import ora13 from "ora";
1811
- var statusCommand = new Command17("status").description("Show current MCP sandbox status").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1846
+ var stopCommand = new Command17("stop").description("Stop the development environment (sandbox + server)").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1812
1847
  const globalOptions = command.optsWithGlobals();
1813
1848
  const json = globalOptions.json ?? false;
1814
1849
  try {
1815
- let mcpId = options.mcpId;
1816
- if (!mcpId) {
1817
- mcpId = await config.getMcpId();
1818
- if (!mcpId) {
1819
- throw new McpError(
1820
- "No active MCP. Run 'waniwani mcp create <name>' to create one or 'waniwani mcp use <name>' to select one."
1821
- );
1822
- }
1850
+ const mcpId = await requireMcpId(options.mcpId);
1851
+ const sessionId = await requireSessionId(mcpId);
1852
+ const spinner = ora13("Stopping development environment...").start();
1853
+ try {
1854
+ await api.post(`/api/mcp/sessions/${sessionId}/server`, {
1855
+ action: "stop"
1856
+ });
1857
+ } catch {
1823
1858
  }
1824
- const spinner = ora13("Fetching MCP status...").start();
1825
- const [result, serverStatus] = await Promise.all([
1826
- api.get(`/api/mcp/sandboxes/${mcpId}`),
1827
- api.post(`/api/mcp/sandboxes/${mcpId}/server`, {
1828
- action: "status"
1829
- }).catch(() => ({
1830
- running: false,
1831
- cmdId: void 0,
1832
- previewUrl: void 0
1833
- }))
1834
- ]);
1835
- spinner.stop();
1859
+ await api.delete(`/api/mcp/sessions/${sessionId}`);
1860
+ spinner.succeed("Development environment stopped");
1836
1861
  if (json) {
1837
- formatOutput({ ...result, server: serverStatus }, true);
1862
+ formatOutput({ stopped: true }, true);
1838
1863
  } else {
1839
- const statusColor = result.status === "active" ? chalk10.green : chalk10.red;
1840
- const serverRunning = serverStatus.running;
1841
- const serverStatusColor = serverRunning ? chalk10.green : chalk10.yellow;
1842
- formatList(
1843
- [
1844
- { label: "MCP ID", value: result.id },
1845
- { label: "Name", value: result.name },
1846
- { label: "Status", value: statusColor(result.status) },
1847
- { label: "Sandbox ID", value: result.sandboxId },
1848
- { label: "Preview URL", value: result.previewUrl },
1849
- {
1850
- label: "Server",
1851
- value: serverStatusColor(serverRunning ? "Running" : "Stopped")
1852
- },
1853
- ...serverStatus.cmdId ? [{ label: "Server Cmd ID", value: serverStatus.cmdId }] : [],
1854
- { label: "Created", value: result.createdAt },
1855
- { label: "Expires", value: result.expiresAt ?? "N/A" }
1856
- ],
1857
- false
1858
- );
1864
+ formatSuccess("Sandbox stopped.", false);
1865
+ console.log();
1866
+ console.log("Start again: waniwani mcp dev");
1859
1867
  }
1860
1868
  } catch (error) {
1861
1869
  handleError(error, json);
@@ -1863,36 +1871,35 @@ var statusCommand = new Command17("status").description("Show current MCP sandbo
1863
1871
  }
1864
1872
  });
1865
1873
 
1866
- // src/commands/mcp/stop.ts
1874
+ // src/commands/mcp/sync.ts
1867
1875
  import { Command as Command18 } from "commander";
1868
1876
  import ora14 from "ora";
1869
- var stopCommand = new Command18("stop").description("Stop the MCP server process").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1877
+ var syncCommand = new Command18("sync").description("Pull files from GitHub to local project").option("--mcp-id <id>", "Specific MCP ID").action(async (options, command) => {
1870
1878
  const globalOptions = command.optsWithGlobals();
1871
1879
  const json = globalOptions.json ?? false;
1872
1880
  try {
1873
- let mcpId = options.mcpId;
1874
- if (!mcpId) {
1875
- mcpId = await config.getMcpId();
1876
- if (!mcpId) {
1877
- throw new McpError(
1878
- "No active MCP. Run 'waniwani mcp create <name>' or 'waniwani mcp use <name>'."
1879
- );
1880
- }
1881
- }
1882
- const spinner = ora14("Stopping MCP server...").start();
1883
- const result = await api.post(
1884
- `/api/mcp/sandboxes/${mcpId}/server`,
1885
- { action: "stop" }
1886
- );
1887
- if (result.stopped) {
1888
- spinner.succeed("MCP server stopped");
1889
- } else {
1890
- spinner.warn("Server was not running");
1881
+ const mcpId = await requireMcpId(options.mcpId);
1882
+ const projectRoot = await findProjectRoot(process.cwd());
1883
+ if (!projectRoot) {
1884
+ throw new CLIError(
1885
+ "Not in a WaniWani project. Run 'waniwani mcp init <name>' first.",
1886
+ "NOT_IN_PROJECT"
1887
+ );
1891
1888
  }
1889
+ const spinner = ora14("Pulling files from GitHub...").start();
1890
+ const result = await pullFilesFromGithub(mcpId, projectRoot);
1891
+ spinner.succeed(`Pulled ${result.count} files from GitHub`);
1892
1892
  if (json) {
1893
- formatOutput(result, true);
1893
+ formatOutput({ files: result.files }, true);
1894
1894
  } else {
1895
- formatSuccess("MCP server stopped.", false);
1895
+ console.log();
1896
+ formatSuccess("Files synced from GitHub!", false);
1897
+ if (result.files.length > 0 && result.files.length <= 10) {
1898
+ console.log();
1899
+ for (const file of result.files) {
1900
+ console.log(` ${file}`);
1901
+ }
1902
+ }
1896
1903
  }
1897
1904
  } catch (error) {
1898
1905
  handleError(error, json);
@@ -1908,7 +1915,9 @@ var useCommand = new Command19("use").description("Select an MCP to use for subs
1908
1915
  const json = globalOptions.json ?? false;
1909
1916
  try {
1910
1917
  const spinner = ora15("Fetching MCPs...").start();
1911
- const mcps = await api.get("/api/mcp/sandboxes");
1918
+ const mcps = await api.get(
1919
+ "/api/mcp/repositories"
1920
+ );
1912
1921
  spinner.stop();
1913
1922
  const mcp = mcps.find((m) => m.name === name);
1914
1923
  if (!mcp) {
@@ -1916,11 +1925,6 @@ var useCommand = new Command19("use").description("Select an MCP to use for subs
1916
1925
  `MCP "${name}" not found. Run 'waniwani mcp list' to see available MCPs.`
1917
1926
  );
1918
1927
  }
1919
- if (mcp.status !== "active") {
1920
- throw new McpError(
1921
- `MCP "${name}" is ${mcp.status}. Only active MCPs can be used.`
1922
- );
1923
- }
1924
1928
  const cfg = options.global ? globalConfig : config;
1925
1929
  await cfg.setMcpId(mcp.id);
1926
1930
  if (json) {
@@ -1928,13 +1932,11 @@ var useCommand = new Command19("use").description("Select an MCP to use for subs
1928
1932
  } else {
1929
1933
  formatSuccess(`Now using MCP "${name}" (${cfg.scope})`, false);
1930
1934
  console.log();
1931
- console.log(` MCP ID: ${mcp.id}`);
1932
- console.log(` Preview URL: ${mcp.previewUrl}`);
1935
+ console.log(` MCP ID: ${mcp.id}`);
1933
1936
  console.log();
1934
1937
  console.log("Next steps:");
1935
- console.log(' waniwani task "Add a tool"');
1936
- console.log(" waniwani mcp test");
1937
- console.log(" waniwani mcp status");
1938
+ console.log(" waniwani mcp dev # Start live preview");
1939
+ console.log(" waniwani mcp status # Check status");
1938
1940
  }
1939
1941
  } catch (error) {
1940
1942
  handleError(error, json);
@@ -1943,13 +1945,13 @@ var useCommand = new Command19("use").description("Select an MCP to use for subs
1943
1945
  });
1944
1946
 
1945
1947
  // src/commands/mcp/index.ts
1946
- var mcpCommand = new Command20("mcp").description("MCP sandbox management commands").addCommand(listCommand2).addCommand(useCommand).addCommand(statusCommand).addCommand(startCommand).addCommand(stopCommand).addCommand(logsCommand).addCommand(deleteCommand).addCommand(deployCommand).addCommand(fileCommand).addCommand(runCommandCommand);
1948
+ var mcpCommand = new Command20("mcp").description("MCP management commands").addCommand(initCommand).addCommand(listCommand2).addCommand(useCommand).addCommand(statusCommand).addCommand(devCommand).addCommand(stopCommand).addCommand(logsCommand).addCommand(syncCommand).addCommand(deployCommand).addCommand(deleteCommand).addCommand(fileCommand).addCommand(runCommandCommand);
1947
1949
 
1948
1950
  // src/commands/org/index.ts
1949
1951
  import { Command as Command23 } from "commander";
1950
1952
 
1951
1953
  // src/commands/org/list.ts
1952
- import chalk11 from "chalk";
1954
+ import chalk10 from "chalk";
1953
1955
  import { Command as Command21 } from "commander";
1954
1956
  import ora16 from "ora";
1955
1957
  var listCommand3 = new Command21("list").description("List your organizations").action(async (_, command) => {
@@ -1976,11 +1978,11 @@ var listCommand3 = new Command21("list").description("List your organizations").
1976
1978
  console.log("No organizations found.");
1977
1979
  return;
1978
1980
  }
1979
- console.log(chalk11.bold("\nOrganizations:\n"));
1981
+ console.log(chalk10.bold("\nOrganizations:\n"));
1980
1982
  const rows = orgs.map((o) => {
1981
1983
  const isActive = o.id === activeOrgId;
1982
1984
  return [
1983
- isActive ? chalk11.cyan(`* ${o.name}`) : ` ${o.name}`,
1985
+ isActive ? chalk10.cyan(`* ${o.name}`) : ` ${o.name}`,
1984
1986
  o.slug,
1985
1987
  o.role
1986
1988
  ];
@@ -1990,7 +1992,7 @@ var listCommand3 = new Command21("list").description("List your organizations").
1990
1992
  if (activeOrgId) {
1991
1993
  const activeOrg = orgs.find((o) => o.id === activeOrgId);
1992
1994
  if (activeOrg) {
1993
- console.log(`Active organization: ${chalk11.cyan(activeOrg.name)}`);
1995
+ console.log(`Active organization: ${chalk10.cyan(activeOrg.name)}`);
1994
1996
  }
1995
1997
  }
1996
1998
  console.log("\nSwitch organization: waniwani org switch <name>");
@@ -2043,95 +2045,11 @@ var switchCommand = new Command22("switch").description("Switch to a different o
2043
2045
  // src/commands/org/index.ts
2044
2046
  var orgCommand = new Command23("org").description("Organization management commands").addCommand(listCommand3).addCommand(switchCommand);
2045
2047
 
2046
- // src/commands/push.ts
2047
- import chalk12 from "chalk";
2048
- import { Command as Command24 } from "commander";
2049
- import ora18 from "ora";
2050
- var BATCH_SIZE2 = 50;
2051
- var pushCommand = new Command24("push").description("Sync local files to MCP sandbox").option("--dry-run", "Show what would be synced without uploading").action(async (options, command) => {
2052
- const globalOptions = command.optsWithGlobals();
2053
- const json = globalOptions.json ?? false;
2054
- try {
2055
- const cwd = process.cwd();
2056
- const projectRoot = await findProjectRoot(cwd);
2057
- if (!projectRoot) {
2058
- throw new CLIError(
2059
- "Not in a WaniWani project. Run 'waniwani init <name>' first.",
2060
- "NOT_IN_PROJECT"
2061
- );
2062
- }
2063
- const mcpId = await loadProjectMcpId(projectRoot);
2064
- if (!mcpId) {
2065
- throw new CLIError(
2066
- "No MCP ID found in project config. Run 'waniwani init <name>' first.",
2067
- "NO_MCP_ID"
2068
- );
2069
- }
2070
- const spinner = ora18("Collecting files...").start();
2071
- const files = await collectFiles(projectRoot);
2072
- if (files.length === 0) {
2073
- spinner.info("No files to sync");
2074
- if (json) {
2075
- formatOutput({ synced: 0, files: [] }, true);
2076
- }
2077
- return;
2078
- }
2079
- spinner.text = `Found ${files.length} files`;
2080
- if (options.dryRun) {
2081
- spinner.stop();
2082
- console.log();
2083
- console.log(chalk12.bold("Files that would be synced:"));
2084
- for (const file of files) {
2085
- console.log(` ${file.path}`);
2086
- }
2087
- console.log();
2088
- console.log(`Total: ${files.length} files`);
2089
- return;
2090
- }
2091
- const allWritten = [];
2092
- const totalBatches = Math.ceil(files.length / BATCH_SIZE2);
2093
- for (let i = 0; i < totalBatches; i++) {
2094
- const batch = files.slice(i * BATCH_SIZE2, (i + 1) * BATCH_SIZE2);
2095
- spinner.text = `Syncing files (${i + 1}/${totalBatches})...`;
2096
- const result = await api.post(
2097
- `/api/mcp/sandboxes/${mcpId}/files`,
2098
- {
2099
- files: batch.map((f) => ({
2100
- path: f.path,
2101
- content: f.content,
2102
- encoding: f.encoding
2103
- }))
2104
- }
2105
- );
2106
- allWritten.push(...result.written);
2107
- }
2108
- spinner.succeed(`Synced ${allWritten.length} files`);
2109
- if (json) {
2110
- formatOutput(
2111
- {
2112
- synced: allWritten.length,
2113
- files: allWritten
2114
- },
2115
- true
2116
- );
2117
- } else {
2118
- console.log();
2119
- formatSuccess(`${allWritten.length} files synced to sandbox`, false);
2120
- }
2121
- } catch (error) {
2122
- handleError(error, json);
2123
- process.exit(1);
2124
- }
2125
- });
2126
-
2127
2048
  // src/cli.ts
2128
2049
  var version = "0.1.0";
2129
- var program = new Command25().name("waniwani").description("WaniWani CLI for MCP development workflow").version(version).option("--json", "Output results as JSON").option("--verbose", "Enable verbose logging");
2050
+ var program = new Command24().name("waniwani").description("WaniWani CLI for MCP development workflow").version(version).option("--json", "Output results as JSON").option("--verbose", "Enable verbose logging");
2130
2051
  program.addCommand(loginCommand);
2131
2052
  program.addCommand(logoutCommand);
2132
- program.addCommand(initCommand);
2133
- program.addCommand(pushCommand);
2134
- program.addCommand(devCommand);
2135
2053
  program.addCommand(mcpCommand);
2136
2054
  program.addCommand(orgCommand);
2137
2055
  program.addCommand(configCommand);