dlw-machine-setup 0.4.6 → 0.4.8
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/bin/installer.js +158 -123
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -3244,6 +3244,47 @@ var import_fs5 = require("fs");
|
|
|
3244
3244
|
var import_readline = require("readline");
|
|
3245
3245
|
var import_path5 = require("path");
|
|
3246
3246
|
|
|
3247
|
+
// src/utils/fetch.ts
|
|
3248
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
3249
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
3250
|
+
var DEFAULT_RETRY_DELAY_MS = 2e3;
|
|
3251
|
+
async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
3252
|
+
const controller = new AbortController();
|
|
3253
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3254
|
+
try {
|
|
3255
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
3256
|
+
} catch (error) {
|
|
3257
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3258
|
+
throw new Error(`Request timed out after ${timeoutMs / 1e3}s`);
|
|
3259
|
+
}
|
|
3260
|
+
throw error;
|
|
3261
|
+
} finally {
|
|
3262
|
+
clearTimeout(timer);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIES, retryDelayMs = DEFAULT_RETRY_DELAY_MS) {
|
|
3266
|
+
let lastError = null;
|
|
3267
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
3268
|
+
try {
|
|
3269
|
+
const response = await fetchWithTimeout(url, options);
|
|
3270
|
+
if (!response.ok && response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
3271
|
+
return response;
|
|
3272
|
+
}
|
|
3273
|
+
if (response.ok) {
|
|
3274
|
+
return response;
|
|
3275
|
+
}
|
|
3276
|
+
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3277
|
+
} catch (error) {
|
|
3278
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3279
|
+
}
|
|
3280
|
+
if (attempt < maxRetries) {
|
|
3281
|
+
const delay = retryDelayMs * Math.pow(2, attempt - 1);
|
|
3282
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
throw lastError || new Error("Failed after maximum retries");
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3247
3288
|
// src/utils/data/fetch-wizard-options.ts
|
|
3248
3289
|
async function fetchWizardOptions(token, repo) {
|
|
3249
3290
|
try {
|
|
@@ -3251,7 +3292,7 @@ async function fetchWizardOptions(token, repo) {
|
|
|
3251
3292
|
"Accept": "application/vnd.github+json",
|
|
3252
3293
|
"Authorization": `Bearer ${token}`
|
|
3253
3294
|
};
|
|
3254
|
-
const releaseRes = await
|
|
3295
|
+
const releaseRes = await fetchWithRetry(
|
|
3255
3296
|
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3256
3297
|
{ headers }
|
|
3257
3298
|
);
|
|
@@ -3259,7 +3300,7 @@ async function fetchWizardOptions(token, repo) {
|
|
|
3259
3300
|
const release = await releaseRes.json();
|
|
3260
3301
|
const asset = release.assets?.find((a) => a.name === "wizard-options.json");
|
|
3261
3302
|
if (!asset) return null;
|
|
3262
|
-
const assetRes = await
|
|
3303
|
+
const assetRes = await fetchWithRetry(asset.url, {
|
|
3263
3304
|
headers: { ...headers, "Accept": "application/octet-stream" }
|
|
3264
3305
|
});
|
|
3265
3306
|
if (!assetRes.ok) return null;
|
|
@@ -3267,7 +3308,7 @@ async function fetchWizardOptions(token, repo) {
|
|
|
3267
3308
|
if (!Array.isArray(data.agents) || !Array.isArray(data.personas?.personas)) {
|
|
3268
3309
|
return null;
|
|
3269
3310
|
}
|
|
3270
|
-
return data;
|
|
3311
|
+
return { options: data, releaseVersion: release.tag_name ?? "unknown" };
|
|
3271
3312
|
} catch {
|
|
3272
3313
|
return null;
|
|
3273
3314
|
}
|
|
@@ -3276,8 +3317,6 @@ async function fetchWizardOptions(token, repo) {
|
|
|
3276
3317
|
// src/utils/data/discover-repo.ts
|
|
3277
3318
|
var GITHUB_ORG = "DLW-INT-SAP-DEV";
|
|
3278
3319
|
var REPO_TOPIC = "one-shot-data";
|
|
3279
|
-
var MAX_RETRIES = 3;
|
|
3280
|
-
var RETRY_DELAY_MS = 2e3;
|
|
3281
3320
|
async function discoverRepo(token) {
|
|
3282
3321
|
const headers = {
|
|
3283
3322
|
"Accept": "application/vnd.github+json",
|
|
@@ -3339,25 +3378,6 @@ async function tryOrgReposList(headers) {
|
|
|
3339
3378
|
return null;
|
|
3340
3379
|
}
|
|
3341
3380
|
}
|
|
3342
|
-
async function fetchWithRetry(url, options) {
|
|
3343
|
-
let lastError = null;
|
|
3344
|
-
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
3345
|
-
try {
|
|
3346
|
-
const response = await fetch(url, options);
|
|
3347
|
-
if (!response.ok && response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
3348
|
-
return response;
|
|
3349
|
-
}
|
|
3350
|
-
if (response.ok) return response;
|
|
3351
|
-
lastError = new Error(`HTTP ${response.status}`);
|
|
3352
|
-
} catch (error) {
|
|
3353
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3354
|
-
}
|
|
3355
|
-
if (attempt < MAX_RETRIES) {
|
|
3356
|
-
await new Promise((resolve3) => setTimeout(resolve3, RETRY_DELAY_MS * Math.pow(2, attempt - 1)));
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
return new Response(null, { status: 503, statusText: "Retry exhausted" });
|
|
3360
|
-
}
|
|
3361
3381
|
|
|
3362
3382
|
// src/utils/data/fetch-contexts.ts
|
|
3363
3383
|
var import_fs2 = require("fs");
|
|
@@ -3409,7 +3429,7 @@ function clearTokenCache() {
|
|
|
3409
3429
|
}
|
|
3410
3430
|
}
|
|
3411
3431
|
async function requestDeviceCode() {
|
|
3412
|
-
const response = await
|
|
3432
|
+
const response = await fetchWithTimeout(GITHUB_DEVICE_CODE_URL, {
|
|
3413
3433
|
method: "POST",
|
|
3414
3434
|
headers: {
|
|
3415
3435
|
"Accept": "application/json",
|
|
@@ -3431,7 +3451,7 @@ async function pollForToken(deviceCode, interval) {
|
|
|
3431
3451
|
let attempts = 0;
|
|
3432
3452
|
while (attempts < maxAttempts) {
|
|
3433
3453
|
await new Promise((resolve3) => setTimeout(resolve3, interval * 1e3));
|
|
3434
|
-
const response = await
|
|
3454
|
+
const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
|
|
3435
3455
|
method: "POST",
|
|
3436
3456
|
headers: {
|
|
3437
3457
|
"Accept": "application/json",
|
|
@@ -3472,7 +3492,11 @@ async function pollForToken(deviceCode, interval) {
|
|
|
3472
3492
|
async function authenticateWithGitHub() {
|
|
3473
3493
|
const envToken = process.env.GITHUB_TOKEN;
|
|
3474
3494
|
if (envToken) {
|
|
3475
|
-
|
|
3495
|
+
const isValid = await verifyToken(envToken);
|
|
3496
|
+
if (isValid) {
|
|
3497
|
+
return envToken;
|
|
3498
|
+
}
|
|
3499
|
+
console.log(" GITHUB_TOKEN env var is invalid or expired, falling back to authentication...\n");
|
|
3476
3500
|
}
|
|
3477
3501
|
const cachedToken = loadCachedToken();
|
|
3478
3502
|
if (cachedToken) {
|
|
@@ -3504,7 +3528,7 @@ async function authenticateWithGitHub() {
|
|
|
3504
3528
|
}
|
|
3505
3529
|
async function verifyToken(token) {
|
|
3506
3530
|
try {
|
|
3507
|
-
const response = await
|
|
3531
|
+
const response = await fetchWithTimeout("https://api.github.com/user", {
|
|
3508
3532
|
headers: {
|
|
3509
3533
|
"Authorization": `Bearer ${token}`,
|
|
3510
3534
|
"Accept": "application/vnd.github+json"
|
|
@@ -3520,8 +3544,6 @@ async function getGitHubToken() {
|
|
|
3520
3544
|
}
|
|
3521
3545
|
|
|
3522
3546
|
// src/utils/data/fetch-contexts.ts
|
|
3523
|
-
var MAX_RETRIES2 = 3;
|
|
3524
|
-
var RETRY_DELAY_MS2 = 2e3;
|
|
3525
3547
|
var MIN_FILE_SIZE = 1024;
|
|
3526
3548
|
async function fetchContexts(options = {}) {
|
|
3527
3549
|
const { domains = [], targetDir = process.cwd(), force = false, token, repo } = options;
|
|
@@ -3545,7 +3567,7 @@ async function fetchContexts(options = {}) {
|
|
|
3545
3567
|
"Accept": "application/vnd.github+json",
|
|
3546
3568
|
"Authorization": `Bearer ${githubToken}`
|
|
3547
3569
|
};
|
|
3548
|
-
const releaseResponse = await
|
|
3570
|
+
const releaseResponse = await fetchWithRetry(releaseUrl, { headers });
|
|
3549
3571
|
if (!releaseResponse.ok) {
|
|
3550
3572
|
throw new Error(
|
|
3551
3573
|
`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`
|
|
@@ -3583,7 +3605,7 @@ async function fetchContexts(options = {}) {
|
|
|
3583
3605
|
"Authorization": `Bearer ${githubToken}`
|
|
3584
3606
|
};
|
|
3585
3607
|
const downloadUrl = asset.url;
|
|
3586
|
-
const response = await
|
|
3608
|
+
const response = await fetchWithRetry(downloadUrl, { headers: downloadHeaders });
|
|
3587
3609
|
if (!response.ok) {
|
|
3588
3610
|
throw new Error(`Download failed: ${getReadableError(response.status)}`);
|
|
3589
3611
|
}
|
|
@@ -3605,14 +3627,17 @@ async function fetchContexts(options = {}) {
|
|
|
3605
3627
|
throw new Error(`Archive contains unsafe path: ${entry}`);
|
|
3606
3628
|
}
|
|
3607
3629
|
}
|
|
3608
|
-
(0, import_child_process.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3630
|
+
const extractResult = (0, import_child_process.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3631
|
+
if (extractResult.status !== 0) {
|
|
3632
|
+
throw new Error("Archive extraction failed");
|
|
3633
|
+
}
|
|
3609
3634
|
const extractedEntries = (0, import_fs2.readdirSync)(tempDir);
|
|
3610
3635
|
const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
3611
3636
|
if (!extractedFolder) {
|
|
3612
3637
|
throw new Error("Extraction failed");
|
|
3613
3638
|
}
|
|
3614
3639
|
const extractedPath = (0, import_path2.join)(tempDir, extractedFolder);
|
|
3615
|
-
|
|
3640
|
+
copyDirectory(extractedPath, domainPath);
|
|
3616
3641
|
result.successful.push(domain);
|
|
3617
3642
|
} catch (error) {
|
|
3618
3643
|
const reason = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -3646,28 +3671,6 @@ async function checkPrerequisites() {
|
|
|
3646
3671
|
);
|
|
3647
3672
|
}
|
|
3648
3673
|
}
|
|
3649
|
-
async function fetchWithRetry2(url, options, maxRetries) {
|
|
3650
|
-
let lastError = null;
|
|
3651
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
3652
|
-
try {
|
|
3653
|
-
const response = await fetch(url, options);
|
|
3654
|
-
if (!response.ok && response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
3655
|
-
return response;
|
|
3656
|
-
}
|
|
3657
|
-
if (response.ok) {
|
|
3658
|
-
return response;
|
|
3659
|
-
}
|
|
3660
|
-
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
3661
|
-
} catch (error) {
|
|
3662
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3663
|
-
}
|
|
3664
|
-
if (attempt < maxRetries) {
|
|
3665
|
-
const delay = RETRY_DELAY_MS2 * Math.pow(2, attempt - 1);
|
|
3666
|
-
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
throw lastError || new Error("Failed after maximum retries");
|
|
3670
|
-
}
|
|
3671
3674
|
function getReadableError(status) {
|
|
3672
3675
|
switch (status) {
|
|
3673
3676
|
case 401:
|
|
@@ -3686,9 +3689,9 @@ function getReadableError(status) {
|
|
|
3686
3689
|
return `Unexpected error (${status})`;
|
|
3687
3690
|
}
|
|
3688
3691
|
}
|
|
3689
|
-
function
|
|
3692
|
+
function copyDirectory(source, target) {
|
|
3690
3693
|
let added = 0;
|
|
3691
|
-
let
|
|
3694
|
+
let overwritten = 0;
|
|
3692
3695
|
if (!(0, import_fs2.existsSync)(target)) {
|
|
3693
3696
|
(0, import_fs2.mkdirSync)(target, { recursive: true });
|
|
3694
3697
|
}
|
|
@@ -3697,19 +3700,19 @@ function mergeDirectories(source, target) {
|
|
|
3697
3700
|
const sourcePath = (0, import_path2.join)(source, entry.name);
|
|
3698
3701
|
const targetPath = (0, import_path2.join)(target, entry.name);
|
|
3699
3702
|
if (entry.isDirectory()) {
|
|
3700
|
-
const result =
|
|
3703
|
+
const result = copyDirectory(sourcePath, targetPath);
|
|
3701
3704
|
added += result.added;
|
|
3702
|
-
|
|
3705
|
+
overwritten += result.overwritten;
|
|
3703
3706
|
} else {
|
|
3704
3707
|
if ((0, import_fs2.existsSync)(targetPath)) {
|
|
3705
|
-
|
|
3708
|
+
overwritten++;
|
|
3706
3709
|
} else {
|
|
3707
|
-
(0, import_fs2.copyFileSync)(sourcePath, targetPath);
|
|
3708
3710
|
added++;
|
|
3709
3711
|
}
|
|
3712
|
+
(0, import_fs2.copyFileSync)(sourcePath, targetPath);
|
|
3710
3713
|
}
|
|
3711
3714
|
}
|
|
3712
|
-
return { added,
|
|
3715
|
+
return { added, overwritten };
|
|
3713
3716
|
}
|
|
3714
3717
|
|
|
3715
3718
|
// src/utils/setup/setup-mcp.ts
|
|
@@ -3735,28 +3738,36 @@ async function setupMCPConfiguration(projectPath, mcpConfig, agent) {
|
|
|
3735
3738
|
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
3736
3739
|
}
|
|
3737
3740
|
}
|
|
3741
|
+
const red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
3738
3742
|
let existingFile = {};
|
|
3739
3743
|
if ((0, import_fs3.existsSync)(mcpJsonPath)) {
|
|
3740
3744
|
try {
|
|
3741
3745
|
const content = (0, import_fs3.readFileSync)(mcpJsonPath, "utf-8");
|
|
3742
3746
|
existingFile = JSON.parse(content);
|
|
3743
3747
|
} catch {
|
|
3748
|
+
const box = [
|
|
3749
|
+
"",
|
|
3750
|
+
red2(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
|
|
3751
|
+
red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
|
|
3752
|
+
red2(" \u2551 \u2551"),
|
|
3753
|
+
red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
|
|
3754
|
+
red2(" \u2551 Existing MCP servers may be lost. \u2551"),
|
|
3755
|
+
red2(" \u2551 Check the file after installation completes. \u2551"),
|
|
3756
|
+
red2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
|
|
3757
|
+
""
|
|
3758
|
+
].join("\n");
|
|
3759
|
+
console.log(box);
|
|
3744
3760
|
}
|
|
3745
3761
|
}
|
|
3746
|
-
const existing = existingFile[target.rootKey] ?? {};
|
|
3747
3762
|
const addedServers = [];
|
|
3748
3763
|
const skippedServers = [];
|
|
3749
|
-
const
|
|
3764
|
+
const servers = {};
|
|
3750
3765
|
for (const [serverName, serverConfig] of Object.entries(mcpConfig)) {
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
const { description, useWhen, active, ...mcpFields } = serverConfig;
|
|
3755
|
-
mergedServers[serverName] = mcpFields;
|
|
3756
|
-
addedServers.push(serverName);
|
|
3757
|
-
}
|
|
3766
|
+
const { description, useWhen, active, ...mcpFields } = serverConfig;
|
|
3767
|
+
servers[serverName] = mcpFields;
|
|
3768
|
+
addedServers.push(serverName);
|
|
3758
3769
|
}
|
|
3759
|
-
const outputFile = { ...existingFile, [target.rootKey]:
|
|
3770
|
+
const outputFile = { ...existingFile, [target.rootKey]: servers };
|
|
3760
3771
|
(0, import_fs3.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
3761
3772
|
return { addedServers, skippedServers };
|
|
3762
3773
|
}
|
|
@@ -3780,9 +3791,9 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
|
|
|
3780
3791
|
// src/utils/setup/setup-instructions.ts
|
|
3781
3792
|
var import_fs4 = require("fs");
|
|
3782
3793
|
var import_path4 = require("path");
|
|
3783
|
-
var CONTEXTS_DIR = (0, import_path4.join)(process.cwd(), "_ai-context");
|
|
3784
3794
|
var MARKER_START = "<!-- one-shot-installer:start -->";
|
|
3785
3795
|
var MARKER_END = "<!-- one-shot-installer:end -->";
|
|
3796
|
+
var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
3786
3797
|
function upsertBlock(filePath, block) {
|
|
3787
3798
|
const marked = `${MARKER_START}
|
|
3788
3799
|
${block}
|
|
@@ -3794,7 +3805,27 @@ ${MARKER_END}`;
|
|
|
3794
3805
|
const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
|
|
3795
3806
|
const start = existing.indexOf(MARKER_START);
|
|
3796
3807
|
const end = existing.indexOf(MARKER_END);
|
|
3797
|
-
|
|
3808
|
+
const hasStart = start !== -1;
|
|
3809
|
+
const hasEnd = end !== -1;
|
|
3810
|
+
if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
|
|
3811
|
+
const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
3812
|
+
const box = [
|
|
3813
|
+
"",
|
|
3814
|
+
red(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
|
|
3815
|
+
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
3816
|
+
red(" \u2551 \u2551"),
|
|
3817
|
+
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
3818
|
+
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
3819
|
+
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
3820
|
+
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
3821
|
+
red(" \u2551 Then re-run the installer. \u2551"),
|
|
3822
|
+
red(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
|
|
3823
|
+
""
|
|
3824
|
+
].join("\n");
|
|
3825
|
+
console.log(box);
|
|
3826
|
+
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
3827
|
+
}
|
|
3828
|
+
if (hasStart && hasEnd) {
|
|
3798
3829
|
const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
|
|
3799
3830
|
(0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
|
|
3800
3831
|
} else {
|
|
@@ -3845,19 +3876,19 @@ function buildMCPSection(mcpConfig) {
|
|
|
3845
3876
|
}
|
|
3846
3877
|
return lines2.join("\n");
|
|
3847
3878
|
}
|
|
3848
|
-
function resolveDomainFolder(domain) {
|
|
3849
|
-
if ((0, import_fs4.existsSync)(
|
|
3879
|
+
function resolveDomainFolder(domain, contextsDir) {
|
|
3880
|
+
if ((0, import_fs4.existsSync)(contextsDir)) {
|
|
3850
3881
|
try {
|
|
3851
|
-
const entries = (0, import_fs4.readdirSync)(
|
|
3882
|
+
const entries = (0, import_fs4.readdirSync)(contextsDir);
|
|
3852
3883
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
3853
3884
|
if (match) {
|
|
3854
|
-
return { folderName: match, folderPath: (0, import_path4.join)(
|
|
3885
|
+
return { folderName: match, folderPath: (0, import_path4.join)(contextsDir, match) };
|
|
3855
3886
|
}
|
|
3856
3887
|
} catch {
|
|
3857
3888
|
}
|
|
3858
3889
|
}
|
|
3859
3890
|
const fallback = domain.toUpperCase();
|
|
3860
|
-
return { folderName: fallback, folderPath: (0, import_path4.join)(
|
|
3891
|
+
return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
|
|
3861
3892
|
}
|
|
3862
3893
|
function formatPathRef(contextPath, description, agent) {
|
|
3863
3894
|
if (agent === "github-copilot") {
|
|
@@ -3865,7 +3896,7 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
3865
3896
|
}
|
|
3866
3897
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
3867
3898
|
}
|
|
3868
|
-
function buildContextRefsSection(domains, agent) {
|
|
3899
|
+
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
3869
3900
|
const lines2 = [
|
|
3870
3901
|
`## Context References`,
|
|
3871
3902
|
``,
|
|
@@ -3874,7 +3905,7 @@ function buildContextRefsSection(domains, agent) {
|
|
|
3874
3905
|
];
|
|
3875
3906
|
let hasAnyFiles = false;
|
|
3876
3907
|
for (const domain of domains) {
|
|
3877
|
-
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain);
|
|
3908
|
+
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
3878
3909
|
if (!(0, import_fs4.existsSync)(domainPath)) continue;
|
|
3879
3910
|
const domainFiles = [];
|
|
3880
3911
|
const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
|
|
@@ -3919,29 +3950,30 @@ function buildContextRefsSection(domains, agent) {
|
|
|
3919
3950
|
}
|
|
3920
3951
|
return lines2.join("\n");
|
|
3921
3952
|
}
|
|
3922
|
-
function buildCombinedInstructions(domains, mcpConfig, agent = "") {
|
|
3953
|
+
function buildCombinedInstructions(domains, mcpConfig, agent = "", projectPath = process.cwd()) {
|
|
3954
|
+
const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
|
|
3923
3955
|
const lines2 = [
|
|
3924
3956
|
`# AI Development Instructions`,
|
|
3925
3957
|
``,
|
|
3926
3958
|
`> Generated by One-Shot Installer`,
|
|
3927
3959
|
``
|
|
3928
3960
|
];
|
|
3929
|
-
lines2.push(buildContextRefsSection(domains, agent));
|
|
3961
|
+
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
3930
3962
|
if (mcpConfig && Object.keys(mcpConfig).length > 0) {
|
|
3931
3963
|
lines2.push(buildMCPSection(mcpConfig));
|
|
3932
3964
|
}
|
|
3933
3965
|
return lines2.join("\n");
|
|
3934
3966
|
}
|
|
3935
3967
|
async function setupInstructions(config) {
|
|
3936
|
-
const { domains, agent, mcpConfig } = config;
|
|
3968
|
+
const { domains, agent, mcpConfig, projectPath = process.cwd() } = config;
|
|
3937
3969
|
switch (agent) {
|
|
3938
3970
|
case "claude-code": {
|
|
3939
|
-
const content = buildCombinedInstructions(domains, mcpConfig, agent);
|
|
3940
|
-
upsertBlock((0, import_path4.join)(
|
|
3971
|
+
const content = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3972
|
+
upsertBlock((0, import_path4.join)(projectPath, "CLAUDE.md"), content);
|
|
3941
3973
|
break;
|
|
3942
3974
|
}
|
|
3943
3975
|
case "github-copilot": {
|
|
3944
|
-
const agentsDir = (0, import_path4.join)(
|
|
3976
|
+
const agentsDir = (0, import_path4.join)(projectPath, ".github", "agents");
|
|
3945
3977
|
if (!(0, import_fs4.existsSync)(agentsDir)) (0, import_fs4.mkdirSync)(agentsDir, { recursive: true });
|
|
3946
3978
|
const filePath = (0, import_path4.join)(agentsDir, "instructions.md");
|
|
3947
3979
|
if (!(0, import_fs4.existsSync)(filePath)) {
|
|
@@ -3951,14 +3983,14 @@ applyTo: "**"
|
|
|
3951
3983
|
|
|
3952
3984
|
`, "utf-8");
|
|
3953
3985
|
}
|
|
3954
|
-
const body = buildCombinedInstructions(domains, mcpConfig, agent);
|
|
3986
|
+
const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3955
3987
|
upsertBlock(filePath, body);
|
|
3956
3988
|
break;
|
|
3957
3989
|
}
|
|
3958
3990
|
case "cursor": {
|
|
3959
|
-
const cursorDir = (0, import_path4.join)(
|
|
3991
|
+
const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
|
|
3960
3992
|
if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
|
|
3961
|
-
const body = buildCombinedInstructions(domains, mcpConfig, agent);
|
|
3993
|
+
const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3962
3994
|
upsertBlock((0, import_path4.join)(cursorDir, `instructions.mdc`), body);
|
|
3963
3995
|
break;
|
|
3964
3996
|
}
|
|
@@ -3973,18 +4005,21 @@ async function loadWizardOptions(token, repo) {
|
|
|
3973
4005
|
if (!remote) {
|
|
3974
4006
|
throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
|
|
3975
4007
|
}
|
|
4008
|
+
const { options, releaseVersion } = remote;
|
|
3976
4009
|
const filteredMcpServers = Object.fromEntries(
|
|
3977
|
-
Object.entries(
|
|
4010
|
+
Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
|
|
3978
4011
|
);
|
|
3979
4012
|
return {
|
|
3980
|
-
personas:
|
|
3981
|
-
agents:
|
|
3982
|
-
baseMcpServers:
|
|
3983
|
-
mcpServers: filteredMcpServers
|
|
4013
|
+
personas: options.personas.personas.filter((p) => p.active !== false),
|
|
4014
|
+
agents: options.agents.filter((a) => a.active !== false),
|
|
4015
|
+
baseMcpServers: options.personas.baseMcpServers,
|
|
4016
|
+
mcpServers: filteredMcpServers,
|
|
4017
|
+
releaseVersion
|
|
3984
4018
|
};
|
|
3985
4019
|
}
|
|
3986
4020
|
|
|
3987
4021
|
// src/index.ts
|
|
4022
|
+
var INSTALLER_VERSION = "0.4.8";
|
|
3988
4023
|
function getInstructionFilePath(agent) {
|
|
3989
4024
|
switch (agent) {
|
|
3990
4025
|
case "claude-code":
|
|
@@ -4031,7 +4066,7 @@ async function main() {
|
|
|
4031
4066
|
const repo = await discoverRepo(token);
|
|
4032
4067
|
console.log(" Loading configuration...");
|
|
4033
4068
|
const options = await loadWizardOptions(token, repo);
|
|
4034
|
-
const config = await collectInputs(options);
|
|
4069
|
+
const config = await collectInputs(options, options.releaseVersion);
|
|
4035
4070
|
if (!config) {
|
|
4036
4071
|
await waitForEnter();
|
|
4037
4072
|
return;
|
|
@@ -4050,7 +4085,7 @@ async function main() {
|
|
|
4050
4085
|
await waitForEnter();
|
|
4051
4086
|
}
|
|
4052
4087
|
}
|
|
4053
|
-
async function collectInputs(options) {
|
|
4088
|
+
async function collectInputs(options, releaseVersion) {
|
|
4054
4089
|
const selectedIds = await esm_default2({
|
|
4055
4090
|
message: "Personas:",
|
|
4056
4091
|
instructions: " Space to select \xB7 Enter to confirm",
|
|
@@ -4089,7 +4124,9 @@ async function collectInputs(options) {
|
|
|
4089
4124
|
agent,
|
|
4090
4125
|
azureDevOpsOrg,
|
|
4091
4126
|
projectPath: (0, import_path5.resolve)(projectInput),
|
|
4092
|
-
|
|
4127
|
+
baseMcpServers: options.baseMcpServers,
|
|
4128
|
+
mcpConfig,
|
|
4129
|
+
releaseVersion
|
|
4093
4130
|
};
|
|
4094
4131
|
}
|
|
4095
4132
|
async function previewAndConfirm(config, options) {
|
|
@@ -4141,7 +4178,7 @@ async function execute(config, token, repo) {
|
|
|
4141
4178
|
const domainValues = uniqueDomains.map((d) => d.toLowerCase());
|
|
4142
4179
|
console.log(` Downloading contexts...`);
|
|
4143
4180
|
try {
|
|
4144
|
-
const downloadResult = await fetchContexts({ domains: domainValues, token, repo });
|
|
4181
|
+
const downloadResult = await fetchContexts({ domains: domainValues, token, repo, targetDir: config.projectPath });
|
|
4145
4182
|
result.domainsInstalled = downloadResult.successful;
|
|
4146
4183
|
result.domainsFailed = downloadResult.failed;
|
|
4147
4184
|
result.failureReasons = downloadResult.failureReasons;
|
|
@@ -4162,28 +4199,26 @@ async function execute(config, token, repo) {
|
|
|
4162
4199
|
result.success = false;
|
|
4163
4200
|
return result;
|
|
4164
4201
|
}
|
|
4202
|
+
const successfulDomains = new Set(result.domainsInstalled);
|
|
4203
|
+
const successfulPersonas = config.personas.filter(
|
|
4204
|
+
(p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
|
|
4205
|
+
);
|
|
4206
|
+
const successfulMcpServers = /* @__PURE__ */ new Set([
|
|
4207
|
+
...config.baseMcpServers,
|
|
4208
|
+
...successfulPersonas.flatMap((p) => p.mcpServers)
|
|
4209
|
+
]);
|
|
4210
|
+
const filteredMcpConfig = Object.fromEntries(
|
|
4211
|
+
Object.entries(config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
|
|
4212
|
+
);
|
|
4165
4213
|
const statePath = (0, import_path5.join)(config.projectPath, ".one-shot-state.json");
|
|
4166
|
-
|
|
4167
|
-
try {
|
|
4168
|
-
if ((0, import_fs5.existsSync)(statePath)) {
|
|
4169
|
-
previousState = JSON.parse((0, import_fs5.readFileSync)(statePath, "utf-8"));
|
|
4170
|
-
}
|
|
4171
|
-
} catch {
|
|
4172
|
-
}
|
|
4173
|
-
const allDomains = [.../* @__PURE__ */ new Set([
|
|
4174
|
-
...previousState.domains ?? [],
|
|
4175
|
-
...result.domainsInstalled
|
|
4176
|
-
])];
|
|
4177
|
-
const mergedMcpConfig = {
|
|
4178
|
-
...previousState.mcpConfigs ?? {},
|
|
4179
|
-
...config.mcpConfig
|
|
4180
|
-
};
|
|
4214
|
+
const allDomains = result.domainsInstalled;
|
|
4181
4215
|
process.stdout.write(` Writing ${instructionFilePath}... `);
|
|
4182
4216
|
try {
|
|
4183
4217
|
await setupInstructions({
|
|
4184
4218
|
domains: allDomains,
|
|
4185
4219
|
agent: config.agent,
|
|
4186
|
-
mcpConfig:
|
|
4220
|
+
mcpConfig: filteredMcpConfig,
|
|
4221
|
+
projectPath: config.projectPath
|
|
4187
4222
|
});
|
|
4188
4223
|
result.instructionsCreated = true;
|
|
4189
4224
|
console.log("\u2713");
|
|
@@ -4196,7 +4231,7 @@ async function execute(config, token, repo) {
|
|
|
4196
4231
|
try {
|
|
4197
4232
|
const mcpResult = await setupMCPConfiguration(
|
|
4198
4233
|
config.projectPath,
|
|
4199
|
-
|
|
4234
|
+
filteredMcpConfig,
|
|
4200
4235
|
config.agent
|
|
4201
4236
|
);
|
|
4202
4237
|
result.mcpConfigured = true;
|
|
@@ -4208,17 +4243,17 @@ async function execute(config, token, repo) {
|
|
|
4208
4243
|
console.log(` Path: ${mcpConfigPath}`);
|
|
4209
4244
|
}
|
|
4210
4245
|
result.success = result.domainsFailed.length === 0 && result.instructionsCreated && result.mcpConfigured;
|
|
4211
|
-
const allPersonas =
|
|
4212
|
-
...previousState.personas ?? [],
|
|
4213
|
-
...config.personas.map((p) => p.id)
|
|
4214
|
-
])];
|
|
4246
|
+
const allPersonas = config.personas.map((p) => p.id);
|
|
4215
4247
|
const state = {
|
|
4216
4248
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4249
|
+
installerVersion: INSTALLER_VERSION,
|
|
4250
|
+
releaseVersion: config.releaseVersion,
|
|
4251
|
+
projectPath: config.projectPath,
|
|
4217
4252
|
agent: config.agent,
|
|
4218
4253
|
personas: allPersonas,
|
|
4219
4254
|
domains: allDomains,
|
|
4220
|
-
mcpServers: Object.keys(
|
|
4221
|
-
mcpConfigs:
|
|
4255
|
+
mcpServers: Object.keys(filteredMcpConfig),
|
|
4256
|
+
mcpConfigs: filteredMcpConfig,
|
|
4222
4257
|
files: {
|
|
4223
4258
|
instructions: instructionFilePath,
|
|
4224
4259
|
mcpConfig: mcpConfigPath,
|