dlw-machine-setup 0.4.5 → 0.4.7
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 +152 -89
- 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,7 +3627,10 @@ 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) {
|
|
@@ -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:
|
|
@@ -3735,12 +3738,25 @@ 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
3762
|
const existing = existingFile[target.rootKey] ?? {};
|
|
@@ -3780,9 +3796,9 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
|
|
|
3780
3796
|
// src/utils/setup/setup-instructions.ts
|
|
3781
3797
|
var import_fs4 = require("fs");
|
|
3782
3798
|
var import_path4 = require("path");
|
|
3783
|
-
var CONTEXTS_DIR = (0, import_path4.join)(process.cwd(), "_ai-context");
|
|
3784
3799
|
var MARKER_START = "<!-- one-shot-installer:start -->";
|
|
3785
3800
|
var MARKER_END = "<!-- one-shot-installer:end -->";
|
|
3801
|
+
var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
3786
3802
|
function upsertBlock(filePath, block) {
|
|
3787
3803
|
const marked = `${MARKER_START}
|
|
3788
3804
|
${block}
|
|
@@ -3794,7 +3810,27 @@ ${MARKER_END}`;
|
|
|
3794
3810
|
const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
|
|
3795
3811
|
const start = existing.indexOf(MARKER_START);
|
|
3796
3812
|
const end = existing.indexOf(MARKER_END);
|
|
3797
|
-
|
|
3813
|
+
const hasStart = start !== -1;
|
|
3814
|
+
const hasEnd = end !== -1;
|
|
3815
|
+
if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
|
|
3816
|
+
const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
3817
|
+
const box = [
|
|
3818
|
+
"",
|
|
3819
|
+
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"),
|
|
3820
|
+
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
3821
|
+
red(" \u2551 \u2551"),
|
|
3822
|
+
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
3823
|
+
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
3824
|
+
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
3825
|
+
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
3826
|
+
red(" \u2551 Then re-run the installer. \u2551"),
|
|
3827
|
+
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"),
|
|
3828
|
+
""
|
|
3829
|
+
].join("\n");
|
|
3830
|
+
console.log(box);
|
|
3831
|
+
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
3832
|
+
}
|
|
3833
|
+
if (hasStart && hasEnd) {
|
|
3798
3834
|
const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
|
|
3799
3835
|
(0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
|
|
3800
3836
|
} else {
|
|
@@ -3845,21 +3881,27 @@ function buildMCPSection(mcpConfig) {
|
|
|
3845
3881
|
}
|
|
3846
3882
|
return lines2.join("\n");
|
|
3847
3883
|
}
|
|
3848
|
-
function resolveDomainFolder(domain) {
|
|
3849
|
-
if ((0, import_fs4.existsSync)(
|
|
3884
|
+
function resolveDomainFolder(domain, contextsDir) {
|
|
3885
|
+
if ((0, import_fs4.existsSync)(contextsDir)) {
|
|
3850
3886
|
try {
|
|
3851
|
-
const entries = (0, import_fs4.readdirSync)(
|
|
3887
|
+
const entries = (0, import_fs4.readdirSync)(contextsDir);
|
|
3852
3888
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
3853
3889
|
if (match) {
|
|
3854
|
-
return { folderName: match, folderPath: (0, import_path4.join)(
|
|
3890
|
+
return { folderName: match, folderPath: (0, import_path4.join)(contextsDir, match) };
|
|
3855
3891
|
}
|
|
3856
3892
|
} catch {
|
|
3857
3893
|
}
|
|
3858
3894
|
}
|
|
3859
3895
|
const fallback = domain.toUpperCase();
|
|
3860
|
-
return { folderName: fallback, folderPath: (0, import_path4.join)(
|
|
3896
|
+
return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
|
|
3861
3897
|
}
|
|
3862
|
-
function
|
|
3898
|
+
function formatPathRef(contextPath, description, agent) {
|
|
3899
|
+
if (agent === "github-copilot") {
|
|
3900
|
+
return `- [${contextPath}](../../${contextPath}) \u2014 ${description}`;
|
|
3901
|
+
}
|
|
3902
|
+
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
3903
|
+
}
|
|
3904
|
+
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
3863
3905
|
const lines2 = [
|
|
3864
3906
|
`## Context References`,
|
|
3865
3907
|
``,
|
|
@@ -3868,25 +3910,25 @@ function buildContextRefsSection(domains) {
|
|
|
3868
3910
|
];
|
|
3869
3911
|
let hasAnyFiles = false;
|
|
3870
3912
|
for (const domain of domains) {
|
|
3871
|
-
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain);
|
|
3913
|
+
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
3872
3914
|
if (!(0, import_fs4.existsSync)(domainPath)) continue;
|
|
3873
3915
|
const domainFiles = [];
|
|
3874
3916
|
const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
|
|
3875
3917
|
if ((0, import_fs4.existsSync)(ctxInstructions)) {
|
|
3876
3918
|
const desc = extractFirstHeading(ctxInstructions);
|
|
3877
|
-
domainFiles.push(
|
|
3919
|
+
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
3878
3920
|
}
|
|
3879
3921
|
const instructionsMd = (0, import_path4.join)(domainPath, "core", "instructions.md");
|
|
3880
3922
|
if ((0, import_fs4.existsSync)(instructionsMd)) {
|
|
3881
3923
|
const desc = extractFirstHeading(instructionsMd);
|
|
3882
|
-
domainFiles.push(
|
|
3924
|
+
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
3883
3925
|
}
|
|
3884
3926
|
const coreDir = (0, import_path4.join)(domainPath, "core");
|
|
3885
3927
|
if ((0, import_fs4.existsSync)(coreDir)) {
|
|
3886
3928
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
3887
3929
|
for (const file of coreFiles) {
|
|
3888
3930
|
const desc = extractFirstHeading((0, import_path4.join)(coreDir, file));
|
|
3889
|
-
domainFiles.push(
|
|
3931
|
+
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
3890
3932
|
}
|
|
3891
3933
|
}
|
|
3892
3934
|
const refDir = (0, import_path4.join)(domainPath, "reference");
|
|
@@ -3897,7 +3939,7 @@ function buildContextRefsSection(domains) {
|
|
|
3897
3939
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
3898
3940
|
for (const file of refFiles) {
|
|
3899
3941
|
const desc = extractFirstHeading((0, import_path4.join)(refDir, file));
|
|
3900
|
-
domainFiles.push(
|
|
3942
|
+
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
3901
3943
|
}
|
|
3902
3944
|
}
|
|
3903
3945
|
}
|
|
@@ -3913,29 +3955,30 @@ function buildContextRefsSection(domains) {
|
|
|
3913
3955
|
}
|
|
3914
3956
|
return lines2.join("\n");
|
|
3915
3957
|
}
|
|
3916
|
-
function buildCombinedInstructions(domains, mcpConfig) {
|
|
3958
|
+
function buildCombinedInstructions(domains, mcpConfig, agent = "", projectPath = process.cwd()) {
|
|
3959
|
+
const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
|
|
3917
3960
|
const lines2 = [
|
|
3918
3961
|
`# AI Development Instructions`,
|
|
3919
3962
|
``,
|
|
3920
3963
|
`> Generated by One-Shot Installer`,
|
|
3921
3964
|
``
|
|
3922
3965
|
];
|
|
3923
|
-
lines2.push(buildContextRefsSection(domains));
|
|
3966
|
+
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
3924
3967
|
if (mcpConfig && Object.keys(mcpConfig).length > 0) {
|
|
3925
3968
|
lines2.push(buildMCPSection(mcpConfig));
|
|
3926
3969
|
}
|
|
3927
3970
|
return lines2.join("\n");
|
|
3928
3971
|
}
|
|
3929
3972
|
async function setupInstructions(config) {
|
|
3930
|
-
const { domains, agent, mcpConfig } = config;
|
|
3973
|
+
const { domains, agent, mcpConfig, projectPath = process.cwd() } = config;
|
|
3931
3974
|
switch (agent) {
|
|
3932
3975
|
case "claude-code": {
|
|
3933
|
-
const content = buildCombinedInstructions(domains, mcpConfig);
|
|
3934
|
-
upsertBlock((0, import_path4.join)(
|
|
3976
|
+
const content = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3977
|
+
upsertBlock((0, import_path4.join)(projectPath, "CLAUDE.md"), content);
|
|
3935
3978
|
break;
|
|
3936
3979
|
}
|
|
3937
3980
|
case "github-copilot": {
|
|
3938
|
-
const agentsDir = (0, import_path4.join)(
|
|
3981
|
+
const agentsDir = (0, import_path4.join)(projectPath, ".github", "agents");
|
|
3939
3982
|
if (!(0, import_fs4.existsSync)(agentsDir)) (0, import_fs4.mkdirSync)(agentsDir, { recursive: true });
|
|
3940
3983
|
const filePath = (0, import_path4.join)(agentsDir, "instructions.md");
|
|
3941
3984
|
if (!(0, import_fs4.existsSync)(filePath)) {
|
|
@@ -3945,14 +3988,14 @@ applyTo: "**"
|
|
|
3945
3988
|
|
|
3946
3989
|
`, "utf-8");
|
|
3947
3990
|
}
|
|
3948
|
-
const body = buildCombinedInstructions(domains, mcpConfig);
|
|
3991
|
+
const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3949
3992
|
upsertBlock(filePath, body);
|
|
3950
3993
|
break;
|
|
3951
3994
|
}
|
|
3952
3995
|
case "cursor": {
|
|
3953
|
-
const cursorDir = (0, import_path4.join)(
|
|
3996
|
+
const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
|
|
3954
3997
|
if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
|
|
3955
|
-
const body = buildCombinedInstructions(domains, mcpConfig);
|
|
3998
|
+
const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath);
|
|
3956
3999
|
upsertBlock((0, import_path4.join)(cursorDir, `instructions.mdc`), body);
|
|
3957
4000
|
break;
|
|
3958
4001
|
}
|
|
@@ -3967,18 +4010,21 @@ async function loadWizardOptions(token, repo) {
|
|
|
3967
4010
|
if (!remote) {
|
|
3968
4011
|
throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
|
|
3969
4012
|
}
|
|
4013
|
+
const { options, releaseVersion } = remote;
|
|
3970
4014
|
const filteredMcpServers = Object.fromEntries(
|
|
3971
|
-
Object.entries(
|
|
4015
|
+
Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
|
|
3972
4016
|
);
|
|
3973
4017
|
return {
|
|
3974
|
-
personas:
|
|
3975
|
-
agents:
|
|
3976
|
-
baseMcpServers:
|
|
3977
|
-
mcpServers: filteredMcpServers
|
|
4018
|
+
personas: options.personas.personas.filter((p) => p.active !== false),
|
|
4019
|
+
agents: options.agents.filter((a) => a.active !== false),
|
|
4020
|
+
baseMcpServers: options.personas.baseMcpServers,
|
|
4021
|
+
mcpServers: filteredMcpServers,
|
|
4022
|
+
releaseVersion
|
|
3978
4023
|
};
|
|
3979
4024
|
}
|
|
3980
4025
|
|
|
3981
4026
|
// src/index.ts
|
|
4027
|
+
var INSTALLER_VERSION = "0.4.7";
|
|
3982
4028
|
function getInstructionFilePath(agent) {
|
|
3983
4029
|
switch (agent) {
|
|
3984
4030
|
case "claude-code":
|
|
@@ -4025,7 +4071,7 @@ async function main() {
|
|
|
4025
4071
|
const repo = await discoverRepo(token);
|
|
4026
4072
|
console.log(" Loading configuration...");
|
|
4027
4073
|
const options = await loadWizardOptions(token, repo);
|
|
4028
|
-
const config = await collectInputs(options);
|
|
4074
|
+
const config = await collectInputs(options, options.releaseVersion);
|
|
4029
4075
|
if (!config) {
|
|
4030
4076
|
await waitForEnter();
|
|
4031
4077
|
return;
|
|
@@ -4044,7 +4090,7 @@ async function main() {
|
|
|
4044
4090
|
await waitForEnter();
|
|
4045
4091
|
}
|
|
4046
4092
|
}
|
|
4047
|
-
async function collectInputs(options) {
|
|
4093
|
+
async function collectInputs(options, releaseVersion) {
|
|
4048
4094
|
const selectedIds = await esm_default2({
|
|
4049
4095
|
message: "Personas:",
|
|
4050
4096
|
instructions: " Space to select \xB7 Enter to confirm",
|
|
@@ -4083,7 +4129,9 @@ async function collectInputs(options) {
|
|
|
4083
4129
|
agent,
|
|
4084
4130
|
azureDevOpsOrg,
|
|
4085
4131
|
projectPath: (0, import_path5.resolve)(projectInput),
|
|
4086
|
-
|
|
4132
|
+
baseMcpServers: options.baseMcpServers,
|
|
4133
|
+
mcpConfig,
|
|
4134
|
+
releaseVersion
|
|
4087
4135
|
};
|
|
4088
4136
|
}
|
|
4089
4137
|
async function previewAndConfirm(config, options) {
|
|
@@ -4135,7 +4183,7 @@ async function execute(config, token, repo) {
|
|
|
4135
4183
|
const domainValues = uniqueDomains.map((d) => d.toLowerCase());
|
|
4136
4184
|
console.log(` Downloading contexts...`);
|
|
4137
4185
|
try {
|
|
4138
|
-
const downloadResult = await fetchContexts({ domains: domainValues, token, repo });
|
|
4186
|
+
const downloadResult = await fetchContexts({ domains: domainValues, token, repo, targetDir: config.projectPath });
|
|
4139
4187
|
result.domainsInstalled = downloadResult.successful;
|
|
4140
4188
|
result.domainsFailed = downloadResult.failed;
|
|
4141
4189
|
result.failureReasons = downloadResult.failureReasons;
|
|
@@ -4156,6 +4204,17 @@ async function execute(config, token, repo) {
|
|
|
4156
4204
|
result.success = false;
|
|
4157
4205
|
return result;
|
|
4158
4206
|
}
|
|
4207
|
+
const successfulDomains = new Set(result.domainsInstalled);
|
|
4208
|
+
const successfulPersonas = config.personas.filter(
|
|
4209
|
+
(p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
|
|
4210
|
+
);
|
|
4211
|
+
const successfulMcpServers = /* @__PURE__ */ new Set([
|
|
4212
|
+
...config.baseMcpServers,
|
|
4213
|
+
...successfulPersonas.flatMap((p) => p.mcpServers)
|
|
4214
|
+
]);
|
|
4215
|
+
const filteredMcpConfig = Object.fromEntries(
|
|
4216
|
+
Object.entries(config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
|
|
4217
|
+
);
|
|
4159
4218
|
const statePath = (0, import_path5.join)(config.projectPath, ".one-shot-state.json");
|
|
4160
4219
|
let previousState = {};
|
|
4161
4220
|
try {
|
|
@@ -4170,14 +4229,15 @@ async function execute(config, token, repo) {
|
|
|
4170
4229
|
])];
|
|
4171
4230
|
const mergedMcpConfig = {
|
|
4172
4231
|
...previousState.mcpConfigs ?? {},
|
|
4173
|
-
...
|
|
4232
|
+
...filteredMcpConfig
|
|
4174
4233
|
};
|
|
4175
4234
|
process.stdout.write(` Writing ${instructionFilePath}... `);
|
|
4176
4235
|
try {
|
|
4177
4236
|
await setupInstructions({
|
|
4178
4237
|
domains: allDomains,
|
|
4179
4238
|
agent: config.agent,
|
|
4180
|
-
mcpConfig: mergedMcpConfig
|
|
4239
|
+
mcpConfig: mergedMcpConfig,
|
|
4240
|
+
projectPath: config.projectPath
|
|
4181
4241
|
});
|
|
4182
4242
|
result.instructionsCreated = true;
|
|
4183
4243
|
console.log("\u2713");
|
|
@@ -4190,7 +4250,7 @@ async function execute(config, token, repo) {
|
|
|
4190
4250
|
try {
|
|
4191
4251
|
const mcpResult = await setupMCPConfiguration(
|
|
4192
4252
|
config.projectPath,
|
|
4193
|
-
|
|
4253
|
+
filteredMcpConfig,
|
|
4194
4254
|
config.agent
|
|
4195
4255
|
);
|
|
4196
4256
|
result.mcpConfigured = true;
|
|
@@ -4208,6 +4268,9 @@ async function execute(config, token, repo) {
|
|
|
4208
4268
|
])];
|
|
4209
4269
|
const state = {
|
|
4210
4270
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4271
|
+
installerVersion: INSTALLER_VERSION,
|
|
4272
|
+
releaseVersion: config.releaseVersion,
|
|
4273
|
+
projectPath: config.projectPath,
|
|
4211
4274
|
agent: config.agent,
|
|
4212
4275
|
personas: allPersonas,
|
|
4213
4276
|
domains: allDomains,
|