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