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