@waniwani/cli 0.0.32 → 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 +812 -894
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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
|
|
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
|
-
|
|
29
|
-
|
|
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()).
|
|
63
|
+
return (await this.load()).mcp.id;
|
|
61
64
|
}
|
|
62
65
|
async setMcpId(id) {
|
|
63
66
|
const data = await this.load();
|
|
64
|
-
data.
|
|
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
|
|
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/
|
|
239
|
-
import {
|
|
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/
|
|
354
|
-
var
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
488
|
-
const
|
|
489
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
522
|
-
".
|
|
523
|
-
"
|
|
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
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
663
|
+
console.log(chalk3.bold("\nWaniWani CLI Login\n"));
|
|
1145
664
|
}
|
|
1146
|
-
const spinner =
|
|
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(
|
|
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
|
|
1213
|
-
var logoutCommand = new
|
|
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 {
|
|
1242
|
-
import
|
|
1243
|
-
|
|
1244
|
-
|
|
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/
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
-
|
|
1279
|
-
|
|
1280
|
-
mcpId
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
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 =
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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(
|
|
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("
|
|
1129
|
+
formatSuccess("Files pushed to GitHub!", false);
|
|
1302
1130
|
console.log();
|
|
1303
|
-
console.log(
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
1309
|
-
|
|
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
|
|
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
|
|
1324
|
-
import
|
|
1325
|
-
var listCommand = new
|
|
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
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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/
|
|
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
|
|
1376
|
-
import
|
|
1377
|
-
var readCommand = new
|
|
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
|
-
|
|
1382
|
-
|
|
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 =
|
|
1321
|
+
const spinner = ora6(`Reading ${path}...`).start();
|
|
1392
1322
|
const result = await api.get(
|
|
1393
|
-
`/api/mcp/
|
|
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
|
|
1423
|
-
import { Command as
|
|
1424
|
-
import
|
|
1425
|
-
var writeCommand = new
|
|
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
|
-
|
|
1430
|
-
|
|
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
|
|
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 =
|
|
1382
|
+
const spinner = ora7(`Writing ${path}...`).start();
|
|
1460
1383
|
const result = await api.post(
|
|
1461
|
-
`/api/mcp/
|
|
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
|
|
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").
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1560
|
-
|
|
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/
|
|
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
|
|
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/
|
|
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
|
-
|
|
1703
|
-
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
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}`
|
|
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/
|
|
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
|
|
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
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
-
|
|
1825
|
-
|
|
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({
|
|
1862
|
+
formatOutput({ stopped: true }, true);
|
|
1838
1863
|
} else {
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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/
|
|
1874
|
+
// src/commands/mcp/sync.ts
|
|
1867
1875
|
import { Command as Command18 } from "commander";
|
|
1868
1876
|
import ora14 from "ora";
|
|
1869
|
-
var
|
|
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
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
1936
|
-
console.log(" waniwani mcp
|
|
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
|
|
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
|
|
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(
|
|
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 ?
|
|
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: ${
|
|
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
|
|
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);
|