dlw-machine-setup 0.6.0 → 0.6.2
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 +367 -636
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -2753,13 +2753,13 @@ var PromisePolyfill = class extends Promise {
|
|
|
2753
2753
|
// Available starting from Node 22
|
|
2754
2754
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
|
|
2755
2755
|
static withResolver() {
|
|
2756
|
-
let
|
|
2756
|
+
let resolve4;
|
|
2757
2757
|
let reject;
|
|
2758
2758
|
const promise = new Promise((res, rej) => {
|
|
2759
|
-
|
|
2759
|
+
resolve4 = res;
|
|
2760
2760
|
reject = rej;
|
|
2761
2761
|
});
|
|
2762
|
-
return { promise, resolve:
|
|
2762
|
+
return { promise, resolve: resolve4, reject };
|
|
2763
2763
|
}
|
|
2764
2764
|
};
|
|
2765
2765
|
|
|
@@ -2776,7 +2776,7 @@ function createPrompt(view) {
|
|
|
2776
2776
|
output
|
|
2777
2777
|
});
|
|
2778
2778
|
const screen = new ScreenManager(rl);
|
|
2779
|
-
const { promise, resolve:
|
|
2779
|
+
const { promise, resolve: resolve4, reject } = PromisePolyfill.withResolver();
|
|
2780
2780
|
const cancel = () => reject(new CancelPromptError());
|
|
2781
2781
|
if (signal) {
|
|
2782
2782
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
|
@@ -2800,7 +2800,7 @@ function createPrompt(view) {
|
|
|
2800
2800
|
cycle(() => {
|
|
2801
2801
|
try {
|
|
2802
2802
|
const nextView = view(config, (value) => {
|
|
2803
|
-
setImmediate(() =>
|
|
2803
|
+
setImmediate(() => resolve4(value));
|
|
2804
2804
|
});
|
|
2805
2805
|
const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
|
|
2806
2806
|
screen.render(content, bottomContent);
|
|
@@ -3244,14 +3244,32 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
|
|
|
3244
3244
|
});
|
|
3245
3245
|
|
|
3246
3246
|
// src/index.ts
|
|
3247
|
-
var
|
|
3247
|
+
var import_fs11 = require("fs");
|
|
3248
3248
|
var import_readline = require("readline");
|
|
3249
|
-
var
|
|
3249
|
+
var import_path12 = require("path");
|
|
3250
3250
|
|
|
3251
3251
|
// src/utils/fetch.ts
|
|
3252
3252
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
3253
3253
|
var DEFAULT_MAX_RETRIES = 3;
|
|
3254
3254
|
var DEFAULT_RETRY_DELAY_MS = 2e3;
|
|
3255
|
+
function getReadableError(status) {
|
|
3256
|
+
switch (status) {
|
|
3257
|
+
case 401:
|
|
3258
|
+
return "Authentication failed. Check your GitHub token.";
|
|
3259
|
+
case 403:
|
|
3260
|
+
return "Access denied. Verify token permissions and rate limits.";
|
|
3261
|
+
case 404:
|
|
3262
|
+
return "Resource not found. Verify the repository and release exist.";
|
|
3263
|
+
case 429:
|
|
3264
|
+
return "Rate limit exceeded. Please wait and try again.";
|
|
3265
|
+
case 500:
|
|
3266
|
+
case 502:
|
|
3267
|
+
case 503:
|
|
3268
|
+
return "GitHub server error. Please try again later.";
|
|
3269
|
+
default:
|
|
3270
|
+
return `Unexpected error (${status})`;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3255
3273
|
async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
3256
3274
|
const controller = new AbortController();
|
|
3257
3275
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -3283,40 +3301,102 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
|
|
|
3283
3301
|
}
|
|
3284
3302
|
if (attempt < maxRetries) {
|
|
3285
3303
|
const delay = retryDelayMs * Math.pow(2, attempt - 1);
|
|
3286
|
-
await new Promise((
|
|
3304
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
3287
3305
|
}
|
|
3288
3306
|
}
|
|
3289
3307
|
throw lastError || new Error("Failed after maximum retries");
|
|
3290
3308
|
}
|
|
3291
3309
|
|
|
3310
|
+
// src/utils/download-archive.ts
|
|
3311
|
+
var import_fs = require("fs");
|
|
3312
|
+
var import_path = require("path");
|
|
3313
|
+
var import_child_process = require("child_process");
|
|
3314
|
+
var MIN_FILE_SIZE = 1024;
|
|
3315
|
+
async function fetchLatestRelease(token, repo) {
|
|
3316
|
+
const headers = {
|
|
3317
|
+
"Accept": "application/vnd.github+json",
|
|
3318
|
+
"Authorization": `Bearer ${token}`
|
|
3319
|
+
};
|
|
3320
|
+
const res = await fetchWithRetry(`https://api.github.com/repos/${repo}/releases/latest`, { headers });
|
|
3321
|
+
if (!res.ok) {
|
|
3322
|
+
throw new Error(`GitHub API error (${res.status}): ${getReadableError(res.status)}`);
|
|
3323
|
+
}
|
|
3324
|
+
const data = await res.json();
|
|
3325
|
+
return { tagName: data.tag_name ?? "unknown", assets: data.assets ?? [] };
|
|
3326
|
+
}
|
|
3327
|
+
async function downloadAndExtractAsset(token, asset, projectPath, tempDirName) {
|
|
3328
|
+
const tarCheck = (0, import_child_process.spawnSync)("tar", ["--version"], { stdio: "ignore" });
|
|
3329
|
+
if (tarCheck.status !== 0) {
|
|
3330
|
+
throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
|
|
3331
|
+
}
|
|
3332
|
+
const tempDir = (0, import_path.join)(projectPath, tempDirName);
|
|
3333
|
+
if ((0, import_fs.existsSync)(tempDir)) (0, import_fs.rmSync)(tempDir, { recursive: true, force: true });
|
|
3334
|
+
(0, import_fs.mkdirSync)(tempDir, { recursive: true });
|
|
3335
|
+
const cleanup = () => {
|
|
3336
|
+
if ((0, import_fs.existsSync)(tempDir)) {
|
|
3337
|
+
try {
|
|
3338
|
+
(0, import_fs.rmSync)(tempDir, { recursive: true, force: true });
|
|
3339
|
+
} catch {
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
};
|
|
3343
|
+
try {
|
|
3344
|
+
const dlRes = await fetchWithRetry(asset.url, {
|
|
3345
|
+
headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
|
|
3346
|
+
});
|
|
3347
|
+
if (!dlRes.ok) {
|
|
3348
|
+
throw new Error(`Download failed: ${getReadableError(dlRes.status)}`);
|
|
3349
|
+
}
|
|
3350
|
+
const archivePath = (0, import_path.join)(tempDir, asset.name);
|
|
3351
|
+
(0, import_fs.writeFileSync)(archivePath, Buffer.from(await dlRes.arrayBuffer()));
|
|
3352
|
+
if ((0, import_fs.statSync)(archivePath).size < MIN_FILE_SIZE) {
|
|
3353
|
+
throw new Error("File corrupted");
|
|
3354
|
+
}
|
|
3355
|
+
const list = (0, import_child_process.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
3356
|
+
if (list.status !== 0) throw new Error("Failed to read archive contents");
|
|
3357
|
+
const resolvedTemp = (0, import_path.resolve)(tempDir);
|
|
3358
|
+
for (const entry of list.stdout.split("\n").filter(Boolean)) {
|
|
3359
|
+
const path = (0, import_path.resolve)((0, import_path.join)(tempDir, entry.replace(/\/$/, "")));
|
|
3360
|
+
if (!path.startsWith(resolvedTemp + import_path.sep)) {
|
|
3361
|
+
throw new Error(`Archive contains unsafe path: ${entry}`);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
const ex = (0, import_child_process.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3365
|
+
if (ex.status !== 0) throw new Error("Archive extraction failed");
|
|
3366
|
+
return { extractedRoot: tempDir, cleanup };
|
|
3367
|
+
} catch (err) {
|
|
3368
|
+
cleanup();
|
|
3369
|
+
throw err;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3292
3373
|
// src/utils/wizard-options.ts
|
|
3293
3374
|
async function fetchWizardOptions(token, repo) {
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3375
|
+
const release = await fetchLatestRelease(token, repo);
|
|
3376
|
+
const asset = release.assets.find((a) => a.name === "wizard-options.json");
|
|
3377
|
+
if (!asset) {
|
|
3378
|
+
throw new Error("wizard-options.json not present in latest release");
|
|
3379
|
+
}
|
|
3380
|
+
const assetRes = await fetchWithRetry(asset.url, {
|
|
3381
|
+
headers: {
|
|
3382
|
+
"Accept": "application/octet-stream",
|
|
3297
3383
|
"Authorization": `Bearer ${token}`
|
|
3298
|
-
};
|
|
3299
|
-
const releaseRes = await fetchWithRetry(
|
|
3300
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3301
|
-
{ headers }
|
|
3302
|
-
);
|
|
3303
|
-
if (!releaseRes.ok) return null;
|
|
3304
|
-
const release = await releaseRes.json();
|
|
3305
|
-
const asset = release.assets?.find((a) => a.name === "wizard-options.json");
|
|
3306
|
-
if (!asset) return null;
|
|
3307
|
-
const assetRes = await fetchWithRetry(asset.url, {
|
|
3308
|
-
headers: { ...headers, "Accept": "application/octet-stream" }
|
|
3309
|
-
});
|
|
3310
|
-
if (!assetRes.ok) return null;
|
|
3311
|
-
const data = await assetRes.json();
|
|
3312
|
-
const techArray = data.personas?.technologies ?? data.personas?.personas;
|
|
3313
|
-
if (!Array.isArray(data.agents) || !Array.isArray(techArray)) {
|
|
3314
|
-
return null;
|
|
3315
3384
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3385
|
+
});
|
|
3386
|
+
if (!assetRes.ok) {
|
|
3387
|
+
throw new Error(`wizard-options.json download failed: ${getReadableError(assetRes.status)}`);
|
|
3388
|
+
}
|
|
3389
|
+
let data;
|
|
3390
|
+
try {
|
|
3391
|
+
data = await assetRes.json();
|
|
3392
|
+
} catch (e) {
|
|
3393
|
+
throw new Error(`wizard-options.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
3394
|
+
}
|
|
3395
|
+
const techArray = data.personas?.technologies ?? data.personas?.personas;
|
|
3396
|
+
if (!Array.isArray(data.agents) || !Array.isArray(techArray)) {
|
|
3397
|
+
throw new Error("wizard-options.json is missing required arrays (agents, technologies)");
|
|
3319
3398
|
}
|
|
3399
|
+
return { options: data, releaseVersion: release.tagName };
|
|
3320
3400
|
}
|
|
3321
3401
|
|
|
3322
3402
|
// src/utils/discover-repo.ts
|
|
@@ -3385,46 +3465,44 @@ async function tryOrgReposList(headers, topic) {
|
|
|
3385
3465
|
}
|
|
3386
3466
|
|
|
3387
3467
|
// src/utils/github-auth.ts
|
|
3388
|
-
var
|
|
3389
|
-
var
|
|
3468
|
+
var import_fs2 = require("fs");
|
|
3469
|
+
var import_path2 = require("path");
|
|
3390
3470
|
var import_os = require("os");
|
|
3391
|
-
var
|
|
3471
|
+
var import_child_process2 = require("child_process");
|
|
3392
3472
|
var GITHUB_CLIENT_ID = "Ov23liwpMumAhwVufZ7N";
|
|
3393
3473
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
3394
3474
|
var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
3475
|
+
var SOFT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3395
3476
|
function getTokenCachePath() {
|
|
3396
|
-
const configDir = (0,
|
|
3397
|
-
if (!(0,
|
|
3398
|
-
(0,
|
|
3477
|
+
const configDir = (0, import_path2.join)((0, import_os.homedir)(), ".one-shot-installer");
|
|
3478
|
+
if (!(0, import_fs2.existsSync)(configDir)) {
|
|
3479
|
+
(0, import_fs2.mkdirSync)(configDir, { recursive: true });
|
|
3399
3480
|
}
|
|
3400
|
-
return (0,
|
|
3481
|
+
return (0, import_path2.join)(configDir, "github-token.json");
|
|
3401
3482
|
}
|
|
3402
3483
|
function loadCachedToken() {
|
|
3403
3484
|
const cachePath = getTokenCachePath();
|
|
3404
|
-
if (!(0,
|
|
3405
|
-
return null;
|
|
3406
|
-
}
|
|
3485
|
+
if (!(0, import_fs2.existsSync)(cachePath)) return null;
|
|
3407
3486
|
try {
|
|
3408
|
-
const data = (0,
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
} catch (error) {
|
|
3487
|
+
const data = (0, import_fs2.readFileSync)(cachePath, "utf-8");
|
|
3488
|
+
return JSON.parse(data);
|
|
3489
|
+
} catch {
|
|
3412
3490
|
return null;
|
|
3413
3491
|
}
|
|
3414
3492
|
}
|
|
3415
3493
|
function saveTokenToCache(token) {
|
|
3416
3494
|
const cachePath = getTokenCachePath();
|
|
3417
|
-
const
|
|
3495
|
+
const cached2 = {
|
|
3418
3496
|
access_token: token,
|
|
3419
3497
|
created_at: Date.now()
|
|
3420
3498
|
};
|
|
3421
|
-
(0,
|
|
3499
|
+
(0, import_fs2.writeFileSync)(cachePath, JSON.stringify(cached2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3422
3500
|
}
|
|
3423
3501
|
function clearTokenCache() {
|
|
3424
3502
|
const cachePath = getTokenCachePath();
|
|
3425
|
-
if ((0,
|
|
3503
|
+
if ((0, import_fs2.existsSync)(cachePath)) {
|
|
3426
3504
|
try {
|
|
3427
|
-
(0,
|
|
3505
|
+
(0, import_fs2.unlinkSync)(cachePath);
|
|
3428
3506
|
} catch (error) {
|
|
3429
3507
|
}
|
|
3430
3508
|
}
|
|
@@ -3451,7 +3529,7 @@ async function pollForToken(deviceCode, interval) {
|
|
|
3451
3529
|
const maxAttempts = 60;
|
|
3452
3530
|
let attempts = 0;
|
|
3453
3531
|
while (attempts < maxAttempts) {
|
|
3454
|
-
await new Promise((
|
|
3532
|
+
await new Promise((resolve4) => setTimeout(resolve4, interval * 1e3));
|
|
3455
3533
|
const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
|
|
3456
3534
|
method: "POST",
|
|
3457
3535
|
headers: {
|
|
@@ -3499,11 +3577,14 @@ async function authenticateWithGitHub() {
|
|
|
3499
3577
|
}
|
|
3500
3578
|
console.log(" GITHUB_TOKEN env var is invalid or expired, falling back to authentication...\n");
|
|
3501
3579
|
}
|
|
3502
|
-
const
|
|
3503
|
-
if (
|
|
3504
|
-
const
|
|
3580
|
+
const cached2 = loadCachedToken();
|
|
3581
|
+
if (cached2) {
|
|
3582
|
+
const fresh = Date.now() - cached2.created_at < SOFT_TTL_MS;
|
|
3583
|
+
if (fresh) return cached2.access_token;
|
|
3584
|
+
const isValid = await verifyToken(cached2.access_token);
|
|
3505
3585
|
if (isValid) {
|
|
3506
|
-
|
|
3586
|
+
saveTokenToCache(cached2.access_token);
|
|
3587
|
+
return cached2.access_token;
|
|
3507
3588
|
}
|
|
3508
3589
|
clearTokenCache();
|
|
3509
3590
|
}
|
|
@@ -3516,13 +3597,13 @@ async function authenticateWithGitHub() {
|
|
|
3516
3597
|
try {
|
|
3517
3598
|
const os = (0, import_os.platform)();
|
|
3518
3599
|
if (os === "win32") {
|
|
3519
|
-
(0,
|
|
3600
|
+
(0, import_child_process2.execSync)(`echo ${deviceCodeData.user_code} | clip`, { stdio: "ignore" });
|
|
3520
3601
|
copied = true;
|
|
3521
3602
|
} else if (os === "darwin") {
|
|
3522
|
-
(0,
|
|
3603
|
+
(0, import_child_process2.execSync)(`echo "${deviceCodeData.user_code}" | pbcopy`, { stdio: "ignore" });
|
|
3523
3604
|
copied = true;
|
|
3524
3605
|
} else {
|
|
3525
|
-
(0,
|
|
3606
|
+
(0, import_child_process2.execSync)(`echo "${deviceCodeData.user_code}" | xclip -selection clipboard`, { stdio: "ignore" });
|
|
3526
3607
|
copied = true;
|
|
3527
3608
|
}
|
|
3528
3609
|
} catch {
|
|
@@ -3604,11 +3685,7 @@ function getAgentTarget(agent) {
|
|
|
3604
3685
|
|
|
3605
3686
|
// src/utils/mod.ts
|
|
3606
3687
|
async function loadWizardOptions(token, repo) {
|
|
3607
|
-
const
|
|
3608
|
-
if (!remote) {
|
|
3609
|
-
throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
|
|
3610
|
-
}
|
|
3611
|
-
const { options, releaseVersion } = remote;
|
|
3688
|
+
const { options, releaseVersion } = await fetchWizardOptions(token, repo);
|
|
3612
3689
|
const filteredMcpServers = Object.fromEntries(
|
|
3613
3690
|
Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
|
|
3614
3691
|
);
|
|
@@ -3671,10 +3748,23 @@ __export(steps_exports, {
|
|
|
3671
3748
|
});
|
|
3672
3749
|
|
|
3673
3750
|
// src/steps/resources/fetch-contexts.ts
|
|
3674
|
-
var
|
|
3675
|
-
var
|
|
3676
|
-
|
|
3677
|
-
|
|
3751
|
+
var import_fs4 = require("fs");
|
|
3752
|
+
var import_path4 = require("path");
|
|
3753
|
+
|
|
3754
|
+
// src/utils/fs-copy.ts
|
|
3755
|
+
var import_fs3 = require("fs");
|
|
3756
|
+
var import_path3 = require("path");
|
|
3757
|
+
function copyDirectory(source, target) {
|
|
3758
|
+
if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
|
|
3759
|
+
for (const entry of (0, import_fs3.readdirSync)(source, { withFileTypes: true })) {
|
|
3760
|
+
const s = (0, import_path3.join)(source, entry.name);
|
|
3761
|
+
const t = (0, import_path3.join)(target, entry.name);
|
|
3762
|
+
if (entry.isDirectory()) copyDirectory(s, t);
|
|
3763
|
+
else (0, import_fs3.copyFileSync)(s, t);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
// src/steps/resources/fetch-contexts.ts
|
|
3678
3768
|
var fetch_contexts_default = defineStep({
|
|
3679
3769
|
name: "fetch-contexts",
|
|
3680
3770
|
label: "Downloading contexts",
|
|
@@ -3683,9 +3773,6 @@ var fetch_contexts_default = defineStep({
|
|
|
3683
3773
|
const uniqueDomains = [...new Set(ctx.config.technologies.flatMap((p) => p.domains))];
|
|
3684
3774
|
const domainValues = uniqueDomains.map((d) => d.toLowerCase());
|
|
3685
3775
|
if (!ctx.contextRepo) {
|
|
3686
|
-
for (const domain of domainValues) {
|
|
3687
|
-
ctx.installed.domainsFailed = domainValues;
|
|
3688
|
-
}
|
|
3689
3776
|
return {
|
|
3690
3777
|
status: "failed",
|
|
3691
3778
|
detail: "Context repo not found (topic: context-data)"
|
|
@@ -3693,8 +3780,6 @@ var fetch_contexts_default = defineStep({
|
|
|
3693
3780
|
}
|
|
3694
3781
|
const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
|
|
3695
3782
|
ctx.installed.domainsInstalled = downloadResult.successful;
|
|
3696
|
-
ctx.installed.domainsFailed = downloadResult.failed;
|
|
3697
|
-
ctx.installed.failureReasons = downloadResult.failureReasons;
|
|
3698
3783
|
ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
|
|
3699
3784
|
const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
|
|
3700
3785
|
console.log("");
|
|
@@ -3719,24 +3804,9 @@ var fetch_contexts_default = defineStep({
|
|
|
3719
3804
|
async function fetchContexts(domains, token, repo, targetDir) {
|
|
3720
3805
|
const result = { successful: [], failed: [], failureReasons: {} };
|
|
3721
3806
|
if (domains.length === 0) return result;
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
}
|
|
3726
|
-
const headers = {
|
|
3727
|
-
"Accept": "application/vnd.github+json",
|
|
3728
|
-
"Authorization": `Bearer ${token}`
|
|
3729
|
-
};
|
|
3730
|
-
const releaseResponse = await fetchWithRetry(
|
|
3731
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3732
|
-
{ headers }
|
|
3733
|
-
);
|
|
3734
|
-
if (!releaseResponse.ok) {
|
|
3735
|
-
throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
|
|
3736
|
-
}
|
|
3737
|
-
const releaseData = await releaseResponse.json();
|
|
3738
|
-
result.releaseVersion = releaseData.tag_name;
|
|
3739
|
-
const asset = releaseData.assets?.find((a) => a.name.endsWith(".tar.gz"));
|
|
3807
|
+
const release = await fetchLatestRelease(token, repo);
|
|
3808
|
+
result.releaseVersion = release.tagName;
|
|
3809
|
+
const asset = release.assets.find((a) => a.name.endsWith(".tar.gz"));
|
|
3740
3810
|
if (!asset) {
|
|
3741
3811
|
for (const domain of domains) {
|
|
3742
3812
|
result.failed.push(domain);
|
|
@@ -3744,39 +3814,11 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3744
3814
|
}
|
|
3745
3815
|
return result;
|
|
3746
3816
|
}
|
|
3747
|
-
const contextsDir = (0,
|
|
3748
|
-
|
|
3817
|
+
const contextsDir = (0, import_path4.join)(targetDir, "_ai-context");
|
|
3818
|
+
if (!(0, import_fs4.existsSync)(contextsDir)) (0, import_fs4.mkdirSync)(contextsDir, { recursive: true });
|
|
3819
|
+
const archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-download");
|
|
3749
3820
|
try {
|
|
3750
|
-
|
|
3751
|
-
if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
|
|
3752
|
-
(0, import_fs2.mkdirSync)(tempDir, { recursive: true });
|
|
3753
|
-
const downloadHeaders = {
|
|
3754
|
-
"Accept": "application/octet-stream",
|
|
3755
|
-
"Authorization": `Bearer ${token}`
|
|
3756
|
-
};
|
|
3757
|
-
const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
|
|
3758
|
-
if (!response.ok) {
|
|
3759
|
-
throw new Error(`Download failed: ${getReadableError(response.status)}`);
|
|
3760
|
-
}
|
|
3761
|
-
const archivePath = (0, import_path2.join)(tempDir, asset.name);
|
|
3762
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
3763
|
-
(0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
|
|
3764
|
-
const stats = (0, import_fs2.statSync)(archivePath);
|
|
3765
|
-
if (stats.size < MIN_FILE_SIZE) {
|
|
3766
|
-
throw new Error("File corrupted");
|
|
3767
|
-
}
|
|
3768
|
-
const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
3769
|
-
if (listResult.status !== 0) throw new Error("Failed to read archive contents");
|
|
3770
|
-
const resolvedTempDir = (0, import_path2.resolve)(tempDir);
|
|
3771
|
-
for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
|
|
3772
|
-
const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
|
|
3773
|
-
if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
|
|
3774
|
-
throw new Error(`Archive contains unsafe path: ${entry}`);
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3778
|
-
if (extractResult.status !== 0) throw new Error("Archive extraction failed");
|
|
3779
|
-
const extractedFolders = (0, import_fs2.readdirSync)(tempDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3821
|
+
const extractedFolders = (0, import_fs4.readdirSync)(archive.extractedRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3780
3822
|
for (const domain of domains) {
|
|
3781
3823
|
try {
|
|
3782
3824
|
const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
|
|
@@ -3785,8 +3827,8 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3785
3827
|
result.failureReasons[domain] = "Not found in archive";
|
|
3786
3828
|
continue;
|
|
3787
3829
|
}
|
|
3788
|
-
const domainPath = (0,
|
|
3789
|
-
copyDirectory((0,
|
|
3830
|
+
const domainPath = (0, import_path4.join)(contextsDir, domain);
|
|
3831
|
+
copyDirectory((0, import_path4.join)(archive.extractedRoot, match), domainPath);
|
|
3790
3832
|
result.successful.push(domain);
|
|
3791
3833
|
} catch (error) {
|
|
3792
3834
|
result.failed.push(domain);
|
|
@@ -3794,59 +3836,51 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3794
3836
|
}
|
|
3795
3837
|
}
|
|
3796
3838
|
} finally {
|
|
3797
|
-
|
|
3798
|
-
try {
|
|
3799
|
-
(0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
|
|
3800
|
-
} catch {
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3839
|
+
archive.cleanup();
|
|
3803
3840
|
}
|
|
3804
3841
|
return result;
|
|
3805
3842
|
}
|
|
3806
|
-
function copyDirectory(source, target) {
|
|
3807
|
-
if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
|
|
3808
|
-
const entries = (0, import_fs2.readdirSync)(source, { withFileTypes: true });
|
|
3809
|
-
for (const entry of entries) {
|
|
3810
|
-
const sourcePath = (0, import_path2.join)(source, entry.name);
|
|
3811
|
-
const targetPath = (0, import_path2.join)(target, entry.name);
|
|
3812
|
-
if (entry.isDirectory()) {
|
|
3813
|
-
copyDirectory(sourcePath, targetPath);
|
|
3814
|
-
} else {
|
|
3815
|
-
(0, import_fs2.copyFileSync)(sourcePath, targetPath);
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
}
|
|
3819
|
-
function getReadableError(status) {
|
|
3820
|
-
switch (status) {
|
|
3821
|
-
case 401:
|
|
3822
|
-
return "Authentication failed. Check your GitHub token.";
|
|
3823
|
-
case 403:
|
|
3824
|
-
return "Access denied. Verify token permissions and rate limits.";
|
|
3825
|
-
case 404:
|
|
3826
|
-
return "Resource not found. Verify the repository and release exist.";
|
|
3827
|
-
case 429:
|
|
3828
|
-
return "Rate limit exceeded. Please wait and try again.";
|
|
3829
|
-
case 500:
|
|
3830
|
-
case 502:
|
|
3831
|
-
case 503:
|
|
3832
|
-
return "GitHub server error. Please try again later.";
|
|
3833
|
-
default:
|
|
3834
|
-
return `Unexpected error (${status})`;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
3843
|
|
|
3838
3844
|
// src/steps/resources/fetch-factory.ts
|
|
3839
|
-
var
|
|
3840
|
-
var
|
|
3841
|
-
var import_child_process3 = require("child_process");
|
|
3845
|
+
var import_fs7 = require("fs");
|
|
3846
|
+
var import_path7 = require("path");
|
|
3842
3847
|
|
|
3843
3848
|
// src/bundles/run-bundle.ts
|
|
3844
|
-
var
|
|
3845
|
-
var
|
|
3849
|
+
var import_fs6 = require("fs");
|
|
3850
|
+
var import_path6 = require("path");
|
|
3846
3851
|
|
|
3847
3852
|
// src/bundles/types.ts
|
|
3848
3853
|
var SCHEMA_VERSION = 1;
|
|
3849
3854
|
|
|
3855
|
+
// src/utils/marker-block.ts
|
|
3856
|
+
var import_fs5 = require("fs");
|
|
3857
|
+
var import_path5 = require("path");
|
|
3858
|
+
function upsertMarkerBlock(filePath, startMarker, endMarker, body, opts = {}) {
|
|
3859
|
+
const block = `${startMarker}
|
|
3860
|
+
${body}
|
|
3861
|
+
${endMarker}`;
|
|
3862
|
+
(0, import_fs5.mkdirSync)((0, import_path5.dirname)(filePath), { recursive: true });
|
|
3863
|
+
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
3864
|
+
(0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
3868
|
+
const s = existing.indexOf(startMarker);
|
|
3869
|
+
const e = existing.indexOf(endMarker);
|
|
3870
|
+
const hasS = s !== -1;
|
|
3871
|
+
const hasE = e !== -1;
|
|
3872
|
+
if (hasS && !hasE || !hasS && hasE || hasS && hasE && e < s) {
|
|
3873
|
+
opts.onCorrupt?.(filePath, startMarker, endMarker);
|
|
3874
|
+
}
|
|
3875
|
+
if (hasS && hasE && e > s) {
|
|
3876
|
+
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
3877
|
+
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
3878
|
+
} else {
|
|
3879
|
+
const sep3 = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
3880
|
+
(0, import_fs5.writeFileSync)(filePath, existing + sep3 + block + "\n", "utf-8");
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3850
3884
|
// src/bundles/run-bundle.ts
|
|
3851
3885
|
var OWNER_KEY = "_bundleOwner";
|
|
3852
3886
|
async function runBundle(manifest, ctx) {
|
|
@@ -3864,7 +3898,7 @@ async function runBundle(manifest, ctx) {
|
|
|
3864
3898
|
}
|
|
3865
3899
|
if (manifest.gitignore?.length) {
|
|
3866
3900
|
upsertMarkerBlock(
|
|
3867
|
-
(0,
|
|
3901
|
+
(0, import_path6.join)(ctx.projectPath, ".gitignore"),
|
|
3868
3902
|
`# ${manifest.name}:start`,
|
|
3869
3903
|
`# ${manifest.name}:end`,
|
|
3870
3904
|
manifest.gitignore.join("\n")
|
|
@@ -3876,7 +3910,7 @@ async function runBundle(manifest, ctx) {
|
|
|
3876
3910
|
result.instructionsSnippet = snippet;
|
|
3877
3911
|
if (!ctx.skipInstructions) {
|
|
3878
3912
|
upsertMarkerBlock(
|
|
3879
|
-
(0,
|
|
3913
|
+
(0, import_path6.join)(ctx.projectPath, ctx.instructionsFile),
|
|
3880
3914
|
`<!-- ${manifest.name}:start -->`,
|
|
3881
3915
|
`<!-- ${manifest.name}:end -->`,
|
|
3882
3916
|
snippet
|
|
@@ -3903,12 +3937,12 @@ function executeOp(op, owner, ctx, result) {
|
|
|
3903
3937
|
function runCopy(op, ctx, result) {
|
|
3904
3938
|
const source = resolveBundlePath(op.from, ctx);
|
|
3905
3939
|
const target = resolveProjectPath(op.to, ctx);
|
|
3906
|
-
if (!(0,
|
|
3907
|
-
if ((0,
|
|
3908
|
-
|
|
3940
|
+
if (!(0, import_fs6.existsSync)(source)) return;
|
|
3941
|
+
if ((0, import_fs6.statSync)(source).isDirectory()) {
|
|
3942
|
+
copyDirectory(source, target);
|
|
3909
3943
|
} else {
|
|
3910
|
-
(0,
|
|
3911
|
-
(0,
|
|
3944
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
|
|
3945
|
+
(0, import_fs6.copyFileSync)(source, target);
|
|
3912
3946
|
}
|
|
3913
3947
|
result.filesTouched.push(op.to);
|
|
3914
3948
|
}
|
|
@@ -3918,14 +3952,14 @@ function runMergeJson(op, owner, ctx, result) {
|
|
|
3918
3952
|
const stripped = stripOwnedEntries(existing, owner);
|
|
3919
3953
|
const tagged = tagEntries(op.patch, owner);
|
|
3920
3954
|
const merged = deepMerge2(stripped, tagged);
|
|
3921
|
-
(0,
|
|
3922
|
-
(0,
|
|
3955
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(filePath), { recursive: true });
|
|
3956
|
+
(0, import_fs6.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3923
3957
|
result.filesTouched.push(op.file);
|
|
3924
3958
|
}
|
|
3925
3959
|
function readJsonOrEmpty(path) {
|
|
3926
|
-
if (!(0,
|
|
3960
|
+
if (!(0, import_fs6.existsSync)(path)) return {};
|
|
3927
3961
|
try {
|
|
3928
|
-
return JSON.parse((0,
|
|
3962
|
+
return JSON.parse((0, import_fs6.readFileSync)(path, "utf-8"));
|
|
3929
3963
|
} catch {
|
|
3930
3964
|
return {};
|
|
3931
3965
|
}
|
|
@@ -3970,38 +4004,18 @@ function deepMerge2(a, b) {
|
|
|
3970
4004
|
}
|
|
3971
4005
|
return b;
|
|
3972
4006
|
}
|
|
3973
|
-
function upsertMarkerBlock(filePath, startMarker, endMarker, body) {
|
|
3974
|
-
const block = `${startMarker}
|
|
3975
|
-
${body}
|
|
3976
|
-
${endMarker}`;
|
|
3977
|
-
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
3978
|
-
if (!(0, import_fs3.existsSync)(filePath)) {
|
|
3979
|
-
(0, import_fs3.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
3980
|
-
return;
|
|
3981
|
-
}
|
|
3982
|
-
const existing = (0, import_fs3.readFileSync)(filePath, "utf-8");
|
|
3983
|
-
const s = existing.indexOf(startMarker);
|
|
3984
|
-
const e = existing.indexOf(endMarker);
|
|
3985
|
-
if (s !== -1 && e !== -1 && e > s) {
|
|
3986
|
-
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
3987
|
-
(0, import_fs3.writeFileSync)(filePath, updated, "utf-8");
|
|
3988
|
-
} else {
|
|
3989
|
-
const joiner = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
3990
|
-
(0, import_fs3.writeFileSync)(filePath, existing + joiner + block + "\n", "utf-8");
|
|
3991
|
-
}
|
|
3992
|
-
}
|
|
3993
4007
|
function resolveBundlePath(rel, ctx) {
|
|
3994
|
-
const base = (0,
|
|
3995
|
-
const full = (0,
|
|
3996
|
-
if (full !== base && !full.startsWith(base +
|
|
4008
|
+
const base = (0, import_path6.resolve)(ctx.bundleRoot);
|
|
4009
|
+
const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
|
|
4010
|
+
if (full !== base && !full.startsWith(base + import_path6.sep)) {
|
|
3997
4011
|
throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
|
|
3998
4012
|
}
|
|
3999
4013
|
return full;
|
|
4000
4014
|
}
|
|
4001
4015
|
function resolveProjectPath(rel, ctx) {
|
|
4002
|
-
const base = (0,
|
|
4003
|
-
const full = (0,
|
|
4004
|
-
if (full !== base && !full.startsWith(base +
|
|
4016
|
+
const base = (0, import_path6.resolve)(ctx.projectPath);
|
|
4017
|
+
const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
|
|
4018
|
+
if (full !== base && !full.startsWith(base + import_path6.sep)) {
|
|
4005
4019
|
throw new Error(`Bundle op "to" escapes project path: ${rel}`);
|
|
4006
4020
|
}
|
|
4007
4021
|
return full;
|
|
@@ -4019,22 +4033,12 @@ function assertSchemaCompatible(manifest) {
|
|
|
4019
4033
|
}
|
|
4020
4034
|
}
|
|
4021
4035
|
function assertBundleRootExists(ctx) {
|
|
4022
|
-
if (!(0,
|
|
4036
|
+
if (!(0, import_fs6.existsSync)(ctx.bundleRoot) || !(0, import_fs6.statSync)(ctx.bundleRoot).isDirectory()) {
|
|
4023
4037
|
throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
|
|
4024
4038
|
}
|
|
4025
4039
|
}
|
|
4026
|
-
function copyDirectory2(source, target) {
|
|
4027
|
-
if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
|
|
4028
|
-
for (const entry of (0, import_fs3.readdirSync)(source, { withFileTypes: true })) {
|
|
4029
|
-
const s = (0, import_path3.join)(source, entry.name);
|
|
4030
|
-
const t = (0, import_path3.join)(target, entry.name);
|
|
4031
|
-
if (entry.isDirectory()) copyDirectory2(s, t);
|
|
4032
|
-
else (0, import_fs3.copyFileSync)(s, t);
|
|
4033
|
-
}
|
|
4034
|
-
}
|
|
4035
4040
|
|
|
4036
4041
|
// src/steps/resources/fetch-factory.ts
|
|
4037
|
-
var MIN_FILE_SIZE2 = 1024;
|
|
4038
4042
|
function getFactoryAsset(agent) {
|
|
4039
4043
|
switch (agent) {
|
|
4040
4044
|
case "github-copilot":
|
|
@@ -4050,114 +4054,58 @@ var fetch_factory_default = defineStep({
|
|
|
4050
4054
|
execute: async (ctx) => {
|
|
4051
4055
|
const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
|
|
4052
4056
|
ctx.installed.factoryInstalled = result.success;
|
|
4053
|
-
ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
|
|
4054
4057
|
if (result.instructionsSnippet) {
|
|
4055
|
-
|
|
4056
|
-
stash["factory"] = result.instructionsSnippet;
|
|
4057
|
-
ctx.installed.bundleInstructions = stash;
|
|
4058
|
+
ctx.installed.factoryInstructionsSnippet = result.instructionsSnippet;
|
|
4058
4059
|
}
|
|
4059
4060
|
if (!result.success) {
|
|
4060
4061
|
return { status: "failed", detail: result.failureReason };
|
|
4061
4062
|
}
|
|
4062
|
-
|
|
4063
|
-
return { status: "success", message: result.filesInstalled.join(", ") + tag };
|
|
4063
|
+
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4064
4064
|
}
|
|
4065
4065
|
});
|
|
4066
4066
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
4067
4067
|
const result = { success: false, filesInstalled: [] };
|
|
4068
|
-
const tarCheck = (0, import_child_process3.spawnSync)("tar", ["--version"], { stdio: "ignore" });
|
|
4069
|
-
if (tarCheck.status !== 0) {
|
|
4070
|
-
result.failureReason = "tar command not found";
|
|
4071
|
-
return result;
|
|
4072
|
-
}
|
|
4073
4068
|
const { assetName, rootFolder } = getFactoryAsset(agent);
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
}
|
|
4078
|
-
|
|
4079
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
4080
|
-
{ headers }
|
|
4081
|
-
);
|
|
4082
|
-
if (!releaseResponse.ok) {
|
|
4083
|
-
result.failureReason = `GitHub API error (${releaseResponse.status})`;
|
|
4069
|
+
let release;
|
|
4070
|
+
try {
|
|
4071
|
+
release = await fetchLatestRelease(token, repo);
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
result.failureReason = err instanceof Error ? err.message : String(err);
|
|
4084
4074
|
return result;
|
|
4085
4075
|
}
|
|
4086
|
-
const
|
|
4087
|
-
const asset = releaseData.assets?.find((a) => a.name === assetName);
|
|
4076
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
4088
4077
|
if (!asset) {
|
|
4089
4078
|
result.failureReason = `${assetName} not found in release`;
|
|
4090
4079
|
return result;
|
|
4091
4080
|
}
|
|
4092
|
-
|
|
4081
|
+
let archive = null;
|
|
4093
4082
|
try {
|
|
4094
|
-
|
|
4095
|
-
(0,
|
|
4096
|
-
const response = await fetchWithRetry(asset.url, {
|
|
4097
|
-
headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
|
|
4098
|
-
});
|
|
4099
|
-
if (!response.ok) {
|
|
4100
|
-
result.failureReason = `Download failed (${response.status})`;
|
|
4101
|
-
return result;
|
|
4102
|
-
}
|
|
4103
|
-
const archivePath = (0, import_path4.join)(tempDir, assetName);
|
|
4104
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
4105
|
-
(0, import_fs4.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
|
|
4106
|
-
const stats = (0, import_fs4.statSync)(archivePath);
|
|
4107
|
-
if (stats.size < MIN_FILE_SIZE2) {
|
|
4108
|
-
result.failureReason = "Downloaded file corrupted (too small)";
|
|
4109
|
-
return result;
|
|
4110
|
-
}
|
|
4111
|
-
const listResult = (0, import_child_process3.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
4112
|
-
if (listResult.status !== 0) {
|
|
4113
|
-
result.failureReason = "Failed to read archive contents";
|
|
4114
|
-
return result;
|
|
4115
|
-
}
|
|
4116
|
-
const resolvedTempDir = (0, import_path4.resolve)(tempDir);
|
|
4117
|
-
for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
|
|
4118
|
-
const entryPath = (0, import_path4.resolve)((0, import_path4.join)(tempDir, entry.replace(/\/$/, "")));
|
|
4119
|
-
if (!entryPath.startsWith(resolvedTempDir + import_path4.sep)) {
|
|
4120
|
-
result.failureReason = `Archive contains unsafe path: ${entry}`;
|
|
4121
|
-
return result;
|
|
4122
|
-
}
|
|
4123
|
-
}
|
|
4124
|
-
const extractResult = (0, import_child_process3.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
4125
|
-
if (extractResult.status !== 0) {
|
|
4126
|
-
result.failureReason = "Archive extraction failed";
|
|
4127
|
-
return result;
|
|
4128
|
-
}
|
|
4129
|
-
const extractedEntries = (0, import_fs4.readdirSync)(tempDir);
|
|
4083
|
+
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
|
|
4084
|
+
const extractedEntries = (0, import_fs7.readdirSync)(archive.extractedRoot);
|
|
4130
4085
|
const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
|
|
4131
4086
|
if (!extractedFolder) {
|
|
4132
4087
|
result.failureReason = `No ${rootFolder}/ folder in archive`;
|
|
4133
4088
|
return result;
|
|
4134
4089
|
}
|
|
4135
|
-
const extractedPath = (0,
|
|
4136
|
-
const manifestPath = (0,
|
|
4137
|
-
if ((0,
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
installCopilotFactory(extractedPath, targetDir, result);
|
|
4141
|
-
} else {
|
|
4142
|
-
installClaudeFactory(extractedPath, targetDir, result);
|
|
4090
|
+
const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
|
|
4091
|
+
const manifestPath = (0, import_path7.join)(extractedPath, "install.json");
|
|
4092
|
+
if (!(0, import_fs7.existsSync)(manifestPath)) {
|
|
4093
|
+
result.failureReason = "Factory archive has no install.json \u2014 upgrade Factory or downgrade installer";
|
|
4094
|
+
return result;
|
|
4143
4095
|
}
|
|
4096
|
+
await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
|
|
4144
4097
|
result.success = true;
|
|
4145
4098
|
} catch (error) {
|
|
4146
4099
|
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
4147
4100
|
} finally {
|
|
4148
|
-
|
|
4149
|
-
try {
|
|
4150
|
-
(0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
4151
|
-
} catch {
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4101
|
+
archive?.cleanup();
|
|
4154
4102
|
}
|
|
4155
4103
|
return result;
|
|
4156
4104
|
}
|
|
4157
4105
|
async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
|
|
4158
4106
|
let manifest;
|
|
4159
4107
|
try {
|
|
4160
|
-
manifest = JSON.parse((0,
|
|
4108
|
+
manifest = JSON.parse((0, import_fs7.readFileSync)(manifestPath, "utf-8"));
|
|
4161
4109
|
} catch (e) {
|
|
4162
4110
|
throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
4163
4111
|
}
|
|
@@ -4170,145 +4118,18 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
|
|
|
4170
4118
|
skipInstructions: true
|
|
4171
4119
|
// write-instructions.ts commits the snippet for correct ordering
|
|
4172
4120
|
});
|
|
4173
|
-
result.bundleManaged = true;
|
|
4174
4121
|
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4175
4122
|
result.filesInstalled = runResult.filesTouched;
|
|
4176
4123
|
}
|
|
4177
|
-
function installClaudeFactory(extractedPath, targetDir, result) {
|
|
4178
|
-
const claudeDir = (0, import_path4.join)(targetDir, ".claude");
|
|
4179
|
-
const factoryDir = (0, import_path4.join)(targetDir, "factory");
|
|
4180
|
-
const agentsSrc = (0, import_path4.join)(extractedPath, "agents");
|
|
4181
|
-
if ((0, import_fs4.existsSync)(agentsSrc)) {
|
|
4182
|
-
copyDirectory3(agentsSrc, (0, import_path4.join)(claudeDir, "agents"));
|
|
4183
|
-
result.filesInstalled.push(".claude/agents/");
|
|
4184
|
-
}
|
|
4185
|
-
const hooksSrc = (0, import_path4.join)(extractedPath, "hooks");
|
|
4186
|
-
if ((0, import_fs4.existsSync)(hooksSrc)) {
|
|
4187
|
-
copyDirectory3(hooksSrc, (0, import_path4.join)(claudeDir, "hooks"));
|
|
4188
|
-
result.filesInstalled.push(".claude/hooks/");
|
|
4189
|
-
}
|
|
4190
|
-
const skillsSrc = (0, import_path4.join)(extractedPath, "skills");
|
|
4191
|
-
if ((0, import_fs4.existsSync)(skillsSrc)) {
|
|
4192
|
-
copyDirectory3(skillsSrc, (0, import_path4.join)(claudeDir, "skills"));
|
|
4193
|
-
result.filesInstalled.push(".claude/skills/");
|
|
4194
|
-
}
|
|
4195
|
-
const workflowSrc = (0, import_path4.join)(extractedPath, "workflow");
|
|
4196
|
-
if ((0, import_fs4.existsSync)(workflowSrc)) {
|
|
4197
|
-
copyDirectory3(workflowSrc, (0, import_path4.join)(factoryDir, "workflow"));
|
|
4198
|
-
result.filesInstalled.push("factory/workflow/");
|
|
4199
|
-
}
|
|
4200
|
-
const utilsSrc = (0, import_path4.join)(extractedPath, "utils");
|
|
4201
|
-
if ((0, import_fs4.existsSync)(utilsSrc)) {
|
|
4202
|
-
copyDirectory3(utilsSrc, (0, import_path4.join)(factoryDir, "utils"));
|
|
4203
|
-
result.filesInstalled.push("factory/utils/");
|
|
4204
|
-
}
|
|
4205
|
-
const docsSrc = (0, import_path4.join)(extractedPath, "docs");
|
|
4206
|
-
if ((0, import_fs4.existsSync)(docsSrc)) {
|
|
4207
|
-
copyDirectory3(docsSrc, (0, import_path4.join)(factoryDir, "docs"));
|
|
4208
|
-
result.filesInstalled.push("factory/docs/");
|
|
4209
|
-
}
|
|
4210
|
-
const rootEntries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
|
|
4211
|
-
for (const entry of rootEntries) {
|
|
4212
|
-
if (entry.isFile()) {
|
|
4213
|
-
if (!(0, import_fs4.existsSync)(factoryDir)) (0, import_fs4.mkdirSync)(factoryDir, { recursive: true });
|
|
4214
|
-
(0, import_fs4.copyFileSync)((0, import_path4.join)(extractedPath, entry.name), (0, import_path4.join)(factoryDir, entry.name));
|
|
4215
|
-
result.filesInstalled.push(`factory/${entry.name}`);
|
|
4216
|
-
}
|
|
4217
|
-
}
|
|
4218
|
-
configureSettings(claudeDir);
|
|
4219
|
-
result.filesInstalled.push(".claude/settings.json");
|
|
4220
|
-
}
|
|
4221
|
-
function installCopilotFactory(extractedPath, targetDir, result) {
|
|
4222
|
-
const factoryDir = (0, import_path4.join)(targetDir, "factory");
|
|
4223
|
-
copyDirectory3(extractedPath, factoryDir);
|
|
4224
|
-
const entries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
|
|
4225
|
-
for (const entry of entries) {
|
|
4226
|
-
if (entry.isDirectory()) {
|
|
4227
|
-
result.filesInstalled.push(`factory/${entry.name}/`);
|
|
4228
|
-
} else {
|
|
4229
|
-
result.filesInstalled.push(`factory/${entry.name}`);
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4232
|
-
}
|
|
4233
|
-
function configureSettings(claudeDir) {
|
|
4234
|
-
const settingsPath = (0, import_path4.join)(claudeDir, "settings.json");
|
|
4235
|
-
let settings = {};
|
|
4236
|
-
if ((0, import_fs4.existsSync)(settingsPath)) {
|
|
4237
|
-
try {
|
|
4238
|
-
settings = JSON.parse((0, import_fs4.readFileSync)(settingsPath, "utf8"));
|
|
4239
|
-
} catch {
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
if (!settings.hooks) settings.hooks = {};
|
|
4243
|
-
const welcomeHook = {
|
|
4244
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-welcome.cjs" }]
|
|
4245
|
-
};
|
|
4246
|
-
const immutableGuardHook = {
|
|
4247
|
-
matcher: "Write|Edit",
|
|
4248
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-immutable-guard.cjs" }]
|
|
4249
|
-
};
|
|
4250
|
-
const stateAdvanceHook = {
|
|
4251
|
-
matcher: "Write|Edit",
|
|
4252
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-state-advance.cjs" }]
|
|
4253
|
-
};
|
|
4254
|
-
const contextMonitorHook = {
|
|
4255
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-context-monitor.cjs" }]
|
|
4256
|
-
};
|
|
4257
|
-
const stateGuardHook = {
|
|
4258
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-state-guard.cjs" }]
|
|
4259
|
-
};
|
|
4260
|
-
for (const event of ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
4261
|
-
if (settings.hooks[event]) {
|
|
4262
|
-
settings.hooks[event] = settings.hooks[event].filter(
|
|
4263
|
-
(h) => !h.hooks?.some(
|
|
4264
|
-
(hh) => hh.command?.includes("factory-welcome") || hh.command?.includes("factory-immutable-guard") || hh.command?.includes("factory-state-advance") || hh.command?.includes("factory-context-monitor") || hh.command?.includes("factory-state-guard")
|
|
4265
|
-
)
|
|
4266
|
-
);
|
|
4267
|
-
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
4268
|
-
}
|
|
4269
|
-
}
|
|
4270
|
-
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
4271
|
-
settings.hooks.SessionStart.unshift(welcomeHook);
|
|
4272
|
-
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
4273
|
-
settings.hooks.PreToolUse.unshift(immutableGuardHook);
|
|
4274
|
-
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
4275
|
-
settings.hooks.PostToolUse.unshift(stateAdvanceHook);
|
|
4276
|
-
if (!settings.hooks.UserPromptSubmit) {
|
|
4277
|
-
settings.hooks.UserPromptSubmit = [stateGuardHook, contextMonitorHook];
|
|
4278
|
-
} else {
|
|
4279
|
-
const hasGuard = settings.hooks.UserPromptSubmit.some(
|
|
4280
|
-
(h) => h.hooks?.some((hh) => hh.command?.includes("factory-state-guard"))
|
|
4281
|
-
);
|
|
4282
|
-
if (!hasGuard) settings.hooks.UserPromptSubmit.unshift(stateGuardHook);
|
|
4283
|
-
const hasMonitor = settings.hooks.UserPromptSubmit.some(
|
|
4284
|
-
(h) => h.hooks?.some((hh) => hh.command?.includes("factory-context-monitor"))
|
|
4285
|
-
);
|
|
4286
|
-
if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
|
|
4287
|
-
}
|
|
4288
|
-
settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
|
|
4289
|
-
if (!(0, import_fs4.existsSync)(claudeDir)) (0, import_fs4.mkdirSync)(claudeDir, { recursive: true });
|
|
4290
|
-
(0, import_fs4.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4291
|
-
}
|
|
4292
|
-
function copyDirectory3(source, target) {
|
|
4293
|
-
if (!(0, import_fs4.existsSync)(target)) (0, import_fs4.mkdirSync)(target, { recursive: true });
|
|
4294
|
-
const entries = (0, import_fs4.readdirSync)(source, { withFileTypes: true });
|
|
4295
|
-
for (const entry of entries) {
|
|
4296
|
-
const sourcePath = (0, import_path4.join)(source, entry.name);
|
|
4297
|
-
const targetPath = (0, import_path4.join)(target, entry.name);
|
|
4298
|
-
if (entry.isDirectory()) {
|
|
4299
|
-
copyDirectory3(sourcePath, targetPath);
|
|
4300
|
-
} else {
|
|
4301
|
-
(0, import_fs4.copyFileSync)(sourcePath, targetPath);
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
}
|
|
4305
4124
|
|
|
4306
4125
|
// src/steps/setup/write-instructions.ts
|
|
4307
|
-
var
|
|
4308
|
-
var
|
|
4126
|
+
var import_fs8 = require("fs");
|
|
4127
|
+
var import_path8 = require("path");
|
|
4309
4128
|
|
|
4310
4129
|
// src/steps/shared.ts
|
|
4130
|
+
var cached = null;
|
|
4311
4131
|
function getFilteredMcpConfig(ctx) {
|
|
4132
|
+
if (cached) return cached;
|
|
4312
4133
|
const successfulDomains = new Set(ctx.installed.domainsInstalled ?? []);
|
|
4313
4134
|
const successfulTechnologies = ctx.config.technologies.filter(
|
|
4314
4135
|
(p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
|
|
@@ -4317,9 +4138,10 @@ function getFilteredMcpConfig(ctx) {
|
|
|
4317
4138
|
...ctx.config.baseMcpServers,
|
|
4318
4139
|
...successfulTechnologies.flatMap((p) => p.mcpServers)
|
|
4319
4140
|
]);
|
|
4320
|
-
|
|
4141
|
+
cached = Object.fromEntries(
|
|
4321
4142
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
|
|
4322
4143
|
);
|
|
4144
|
+
return cached;
|
|
4323
4145
|
}
|
|
4324
4146
|
|
|
4325
4147
|
// src/steps/setup/write-instructions.ts
|
|
@@ -4329,107 +4151,67 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4329
4151
|
var write_instructions_default = defineStep({
|
|
4330
4152
|
name: "write-instructions",
|
|
4331
4153
|
label: "Writing instruction file",
|
|
4332
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.
|
|
4154
|
+
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet,
|
|
4333
4155
|
execute: async (ctx) => {
|
|
4334
4156
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4335
4157
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
4336
4158
|
const agent = ctx.config.agent;
|
|
4337
4159
|
const projectPath = ctx.config.projectPath;
|
|
4338
|
-
const
|
|
4339
|
-
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4340
|
-
const includeFactorySection = factoryInstalled && !factoryBundleManaged;
|
|
4341
|
-
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
|
|
4160
|
+
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4342
4161
|
const target = getAgentTarget(agent);
|
|
4343
|
-
const filePath = (0,
|
|
4344
|
-
const fileDir = (0,
|
|
4345
|
-
if (!(0,
|
|
4346
|
-
if (agent === "cursor" && !(0,
|
|
4347
|
-
(0,
|
|
4162
|
+
const filePath = (0, import_path8.join)(projectPath, target.instructions);
|
|
4163
|
+
const fileDir = (0, import_path8.dirname)(filePath);
|
|
4164
|
+
if (!(0, import_fs8.existsSync)(fileDir)) (0, import_fs8.mkdirSync)(fileDir, { recursive: true });
|
|
4165
|
+
if (agent === "cursor" && !(0, import_fs8.existsSync)(filePath)) {
|
|
4166
|
+
(0, import_fs8.writeFileSync)(filePath, `---
|
|
4348
4167
|
description: AI development instructions from One-Shot Installer
|
|
4349
4168
|
alwaysApply: true
|
|
4350
4169
|
---
|
|
4351
4170
|
|
|
4352
4171
|
`, "utf-8");
|
|
4353
4172
|
}
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4173
|
+
upsertMarkerBlock(filePath, MARKER_START, MARKER_END, content, {
|
|
4174
|
+
onCorrupt: (file) => {
|
|
4175
|
+
const fileName = file.split(/[/\\]/).pop() ?? file;
|
|
4176
|
+
const box = [
|
|
4177
|
+
"",
|
|
4178
|
+
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"),
|
|
4179
|
+
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
4180
|
+
red(" \u2551 \u2551"),
|
|
4181
|
+
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
4182
|
+
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
4183
|
+
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
4184
|
+
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
4185
|
+
red(" \u2551 Then re-run the installer. \u2551"),
|
|
4186
|
+
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"),
|
|
4187
|
+
""
|
|
4188
|
+
].join("\n");
|
|
4189
|
+
console.log(box);
|
|
4190
|
+
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4191
|
+
}
|
|
4192
|
+
});
|
|
4193
|
+
if (ctx.installed.factoryInstructionsSnippet) {
|
|
4194
|
+
upsertMarkerBlock(
|
|
4195
|
+
filePath,
|
|
4196
|
+
"<!-- factory:start -->",
|
|
4197
|
+
"<!-- factory:end -->",
|
|
4198
|
+
ctx.installed.factoryInstructionsSnippet
|
|
4199
|
+
);
|
|
4358
4200
|
}
|
|
4359
4201
|
return { status: "success", message: target.instructions };
|
|
4360
4202
|
}
|
|
4361
4203
|
});
|
|
4362
|
-
function upsertBlock(filePath, block) {
|
|
4363
|
-
const marked = `${MARKER_START}
|
|
4364
|
-
${block}
|
|
4365
|
-
${MARKER_END}`;
|
|
4366
|
-
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
4367
|
-
(0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
|
|
4368
|
-
return;
|
|
4369
|
-
}
|
|
4370
|
-
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
4371
|
-
const start = existing.indexOf(MARKER_START);
|
|
4372
|
-
const end = existing.indexOf(MARKER_END);
|
|
4373
|
-
const hasStart = start !== -1;
|
|
4374
|
-
const hasEnd = end !== -1;
|
|
4375
|
-
if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
|
|
4376
|
-
const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
4377
|
-
const box = [
|
|
4378
|
-
"",
|
|
4379
|
-
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"),
|
|
4380
|
-
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
4381
|
-
red(" \u2551 \u2551"),
|
|
4382
|
-
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
4383
|
-
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
4384
|
-
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
4385
|
-
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
4386
|
-
red(" \u2551 Then re-run the installer. \u2551"),
|
|
4387
|
-
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"),
|
|
4388
|
-
""
|
|
4389
|
-
].join("\n");
|
|
4390
|
-
console.log(box);
|
|
4391
|
-
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4392
|
-
}
|
|
4393
|
-
if (hasStart && hasEnd) {
|
|
4394
|
-
const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
|
|
4395
|
-
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
4396
|
-
} else {
|
|
4397
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4398
|
-
(0, import_fs5.writeFileSync)(filePath, existing + separator + marked, "utf-8");
|
|
4399
|
-
}
|
|
4400
|
-
}
|
|
4401
|
-
function upsertBundleBlock(filePath, bundleName, snippet) {
|
|
4402
|
-
const startMarker = `<!-- ${bundleName}:start -->`;
|
|
4403
|
-
const endMarker = `<!-- ${bundleName}:end -->`;
|
|
4404
|
-
const block = `${startMarker}
|
|
4405
|
-
${snippet}
|
|
4406
|
-
${endMarker}`;
|
|
4407
|
-
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
4408
|
-
(0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
4409
|
-
return;
|
|
4410
|
-
}
|
|
4411
|
-
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
4412
|
-
const s = existing.indexOf(startMarker);
|
|
4413
|
-
const e = existing.indexOf(endMarker);
|
|
4414
|
-
if (s !== -1 && e !== -1 && e > s) {
|
|
4415
|
-
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
4416
|
-
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
4417
|
-
} else {
|
|
4418
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4419
|
-
(0, import_fs5.writeFileSync)(filePath, existing + separator + block + "\n", "utf-8");
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
4204
|
function collectMdFiles(dir) {
|
|
4423
|
-
if (!(0,
|
|
4424
|
-
const entries = (0,
|
|
4205
|
+
if (!(0, import_fs8.existsSync)(dir)) return [];
|
|
4206
|
+
const entries = (0, import_fs8.readdirSync)(dir, { recursive: true });
|
|
4425
4207
|
return entries.filter((entry) => {
|
|
4426
|
-
const fullPath = (0,
|
|
4427
|
-
return entry.endsWith(".md") && (0,
|
|
4208
|
+
const fullPath = (0, import_path8.join)(dir, entry);
|
|
4209
|
+
return entry.endsWith(".md") && (0, import_fs8.statSync)(fullPath).isFile();
|
|
4428
4210
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4429
4211
|
}
|
|
4430
4212
|
function extractFirstHeading(filePath) {
|
|
4431
4213
|
try {
|
|
4432
|
-
const content = (0,
|
|
4214
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
4433
4215
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4434
4216
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4435
4217
|
if (match) {
|
|
@@ -4447,16 +4229,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4447
4229
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4448
4230
|
}
|
|
4449
4231
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4450
|
-
if ((0,
|
|
4232
|
+
if ((0, import_fs8.existsSync)(contextsDir)) {
|
|
4451
4233
|
try {
|
|
4452
|
-
const entries = (0,
|
|
4234
|
+
const entries = (0, import_fs8.readdirSync)(contextsDir);
|
|
4453
4235
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4454
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
4236
|
+
if (match) return { folderName: match, folderPath: (0, import_path8.join)(contextsDir, match) };
|
|
4455
4237
|
} catch {
|
|
4456
4238
|
}
|
|
4457
4239
|
}
|
|
4458
4240
|
const fallback = domain.toUpperCase();
|
|
4459
|
-
return { folderName: fallback, folderPath: (0,
|
|
4241
|
+
return { folderName: fallback, folderPath: (0, import_path8.join)(contextsDir, fallback) };
|
|
4460
4242
|
}
|
|
4461
4243
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4462
4244
|
const lines2 = [
|
|
@@ -4468,34 +4250,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4468
4250
|
let hasAnyFiles = false;
|
|
4469
4251
|
for (const domain of domains) {
|
|
4470
4252
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4471
|
-
if (!(0,
|
|
4253
|
+
if (!(0, import_fs8.existsSync)(domainPath)) continue;
|
|
4472
4254
|
const domainFiles = [];
|
|
4473
|
-
const ctxInstructions = (0,
|
|
4474
|
-
if ((0,
|
|
4255
|
+
const ctxInstructions = (0, import_path8.join)(domainPath, "context-instructions.md");
|
|
4256
|
+
if ((0, import_fs8.existsSync)(ctxInstructions)) {
|
|
4475
4257
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4476
4258
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4477
4259
|
}
|
|
4478
|
-
const instructionsMd = (0,
|
|
4479
|
-
if ((0,
|
|
4260
|
+
const instructionsMd = (0, import_path8.join)(domainPath, "core", "instructions.md");
|
|
4261
|
+
if ((0, import_fs8.existsSync)(instructionsMd)) {
|
|
4480
4262
|
const desc = extractFirstHeading(instructionsMd);
|
|
4481
4263
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4482
4264
|
}
|
|
4483
|
-
const coreDir = (0,
|
|
4484
|
-
if ((0,
|
|
4265
|
+
const coreDir = (0, import_path8.join)(domainPath, "core");
|
|
4266
|
+
if ((0, import_fs8.existsSync)(coreDir)) {
|
|
4485
4267
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4486
4268
|
for (const file of coreFiles) {
|
|
4487
|
-
const desc = extractFirstHeading((0,
|
|
4269
|
+
const desc = extractFirstHeading((0, import_path8.join)(coreDir, file));
|
|
4488
4270
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4489
4271
|
}
|
|
4490
4272
|
}
|
|
4491
|
-
const refDir = (0,
|
|
4492
|
-
if ((0,
|
|
4273
|
+
const refDir = (0, import_path8.join)(domainPath, "reference");
|
|
4274
|
+
if ((0, import_fs8.existsSync)(refDir)) {
|
|
4493
4275
|
const refFiles = collectMdFiles(refDir);
|
|
4494
4276
|
if (refFiles.length > 0) {
|
|
4495
4277
|
domainFiles.push(``);
|
|
4496
4278
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4497
4279
|
for (const file of refFiles) {
|
|
4498
|
-
const desc = extractFirstHeading((0,
|
|
4280
|
+
const desc = extractFirstHeading((0, import_path8.join)(refDir, file));
|
|
4499
4281
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4500
4282
|
}
|
|
4501
4283
|
}
|
|
@@ -4517,73 +4299,25 @@ function buildMCPSection(mcpConfig) {
|
|
|
4517
4299
|
if (entries.length === 0) return "";
|
|
4518
4300
|
const lines2 = [`## MCP Servers`, ``, `The following MCP servers are available. Use them proactively when relevant:`, ``];
|
|
4519
4301
|
for (const [name, config] of entries) {
|
|
4520
|
-
|
|
4521
|
-
const useWhen = config.useWhen;
|
|
4522
|
-
if (!description) continue;
|
|
4302
|
+
if (!config.description) continue;
|
|
4523
4303
|
lines2.push(`### ${name}`);
|
|
4524
|
-
lines2.push(description);
|
|
4525
|
-
if (useWhen) lines2.push(`**Use when**: ${useWhen}`);
|
|
4304
|
+
lines2.push(config.description);
|
|
4305
|
+
if (config.useWhen) lines2.push(`**Use when**: ${config.useWhen}`);
|
|
4526
4306
|
lines2.push(``);
|
|
4527
4307
|
}
|
|
4528
4308
|
return lines2.join("\n");
|
|
4529
4309
|
}
|
|
4530
|
-
function
|
|
4531
|
-
|
|
4532
|
-
return [
|
|
4533
|
-
`## Factory Dev Workflow`,
|
|
4534
|
-
``,
|
|
4535
|
-
`Factory is installed. Skills are in \`factory/skills/\`, templates in \`factory/templates/\`.`,
|
|
4536
|
-
``,
|
|
4537
|
-
`Read \`factory/README.md\` and \`factory/PROTOCOL.md\` for usage instructions.`,
|
|
4538
|
-
``,
|
|
4539
|
-
`Available skills:`,
|
|
4540
|
-
`- \`factory-advisor\` \u2014 start or resume the workflow (main entry point)`,
|
|
4541
|
-
`- \`factory-init\` \u2014 initialize project state`,
|
|
4542
|
-
`- \`factory-planner\` \u2014 create story plan`,
|
|
4543
|
-
`- \`factory-executor\` \u2014 execute development tasks`,
|
|
4544
|
-
`- \`factory-verifier\` \u2014 code review`,
|
|
4545
|
-
`- \`factory-completer\` \u2014 finalize and complete work`,
|
|
4546
|
-
``
|
|
4547
|
-
].join("\n");
|
|
4548
|
-
}
|
|
4549
|
-
return [
|
|
4550
|
-
`## Factory Dev Workflow`,
|
|
4551
|
-
``,
|
|
4552
|
-
`Factory is installed. Use \`/factory-advisor\` to start or resume the workflow.`,
|
|
4553
|
-
``,
|
|
4554
|
-
`Available commands:`,
|
|
4555
|
-
`- \`/factory-advisor\` \u2014 start or resume the workflow (main entry point)`,
|
|
4556
|
-
`- \`/factory-flow\` \u2014 guided workflow execution`,
|
|
4557
|
-
`- \`/factory-feature-dev\` \u2014 add a feature to an existing project`,
|
|
4558
|
-
`- \`/factory-dev\` \u2014 execute development tasks`,
|
|
4559
|
-
`- \`/factory-progress\` \u2014 check status`,
|
|
4560
|
-
`- \`/factory-init-state\` \u2014 initialize state for a brownfield project`,
|
|
4561
|
-
`- \`/factory-analyst\` \u2014 validate requirements`,
|
|
4562
|
-
`- \`/factory-architect\` \u2014 design architecture`,
|
|
4563
|
-
`- \`/factory-planner\` \u2014 create story plan`,
|
|
4564
|
-
`- \`/factory-verifier\` \u2014 code review`,
|
|
4565
|
-
`- \`/factory-security\` \u2014 OWASP security review`,
|
|
4566
|
-
`- \`/factory-test-analyst\` \u2014 test quality review`,
|
|
4567
|
-
`- \`/factory-explorer\` \u2014 explore and understand code`,
|
|
4568
|
-
`- \`/factory-docs\` \u2014 documentation hub`,
|
|
4569
|
-
`- \`/factory-docs-generate\` \u2014 auto-generate documentation`,
|
|
4570
|
-
`- \`/factory-docs-manual\` \u2014 manual documentation workflow`,
|
|
4571
|
-
`- \`/factory-office\` \u2014 office mode discussion`,
|
|
4572
|
-
``
|
|
4573
|
-
].join("\n");
|
|
4574
|
-
}
|
|
4575
|
-
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
|
|
4576
|
-
const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
|
|
4310
|
+
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4311
|
+
const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
|
|
4577
4312
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4578
4313
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4579
4314
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
4580
|
-
if (factoryInstalled) lines2.push(buildFactorySection(agent));
|
|
4581
4315
|
return lines2.join("\n");
|
|
4582
4316
|
}
|
|
4583
4317
|
|
|
4584
4318
|
// src/steps/setup/write-mcp-config.ts
|
|
4585
|
-
var
|
|
4586
|
-
var
|
|
4319
|
+
var import_fs9 = require("fs");
|
|
4320
|
+
var import_path9 = require("path");
|
|
4587
4321
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4588
4322
|
var write_mcp_config_default = defineStep({
|
|
4589
4323
|
name: "write-mcp-config",
|
|
@@ -4592,15 +4326,15 @@ var write_mcp_config_default = defineStep({
|
|
|
4592
4326
|
execute: async (ctx) => {
|
|
4593
4327
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4594
4328
|
const target = getAgentTarget(ctx.config.agent);
|
|
4595
|
-
const mcpJsonPath = (0,
|
|
4329
|
+
const mcpJsonPath = (0, import_path9.join)(ctx.config.projectPath, target.mcpConfig);
|
|
4596
4330
|
if (target.mcpDir) {
|
|
4597
|
-
const dir = (0,
|
|
4598
|
-
if (!(0,
|
|
4331
|
+
const dir = (0, import_path9.join)(ctx.config.projectPath, target.mcpDir);
|
|
4332
|
+
if (!(0, import_fs9.existsSync)(dir)) (0, import_fs9.mkdirSync)(dir, { recursive: true });
|
|
4599
4333
|
}
|
|
4600
4334
|
let existingFile = {};
|
|
4601
|
-
if ((0,
|
|
4335
|
+
if ((0, import_fs9.existsSync)(mcpJsonPath)) {
|
|
4602
4336
|
try {
|
|
4603
|
-
existingFile = JSON.parse((0,
|
|
4337
|
+
existingFile = JSON.parse((0, import_fs9.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4604
4338
|
} catch {
|
|
4605
4339
|
const box = [
|
|
4606
4340
|
"",
|
|
@@ -4626,7 +4360,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4626
4360
|
}
|
|
4627
4361
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4628
4362
|
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4629
|
-
(0,
|
|
4363
|
+
(0, import_fs9.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4630
4364
|
ctx.installed.mcpServersAdded = addedServers;
|
|
4631
4365
|
return {
|
|
4632
4366
|
status: "success",
|
|
@@ -4636,7 +4370,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4636
4370
|
});
|
|
4637
4371
|
|
|
4638
4372
|
// src/steps/setup/run-mcp-install-commands.ts
|
|
4639
|
-
var
|
|
4373
|
+
var import_child_process3 = require("child_process");
|
|
4640
4374
|
var run_mcp_install_commands_default = defineStep({
|
|
4641
4375
|
name: "run-mcp-install-commands",
|
|
4642
4376
|
label: "Registering MCP servers with Claude Code",
|
|
@@ -4658,7 +4392,7 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4658
4392
|
for (const [name, cfg] of Object.entries(filtered)) {
|
|
4659
4393
|
const cmd = cfg.installCommand;
|
|
4660
4394
|
if (typeof cmd !== "string" || cmd.length === 0) continue;
|
|
4661
|
-
const result = (0,
|
|
4395
|
+
const result = (0, import_child_process3.spawnSync)(cmd, {
|
|
4662
4396
|
shell: true,
|
|
4663
4397
|
stdio: "pipe",
|
|
4664
4398
|
encoding: "utf-8",
|
|
@@ -4672,7 +4406,6 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4672
4406
|
failed.push({ name, reason });
|
|
4673
4407
|
}
|
|
4674
4408
|
}
|
|
4675
|
-
ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
|
|
4676
4409
|
if (failed.length === 0) {
|
|
4677
4410
|
return {
|
|
4678
4411
|
status: "success",
|
|
@@ -4691,13 +4424,12 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4691
4424
|
}
|
|
4692
4425
|
});
|
|
4693
4426
|
function isClaudeCliAvailable() {
|
|
4694
|
-
const check2 = (0,
|
|
4427
|
+
const check2 = (0, import_child_process3.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
|
|
4695
4428
|
return check2.status === 0;
|
|
4696
4429
|
}
|
|
4697
4430
|
|
|
4698
4431
|
// src/steps/setup/update-gitignore.ts
|
|
4699
|
-
var
|
|
4700
|
-
var import_path7 = require("path");
|
|
4432
|
+
var import_path10 = require("path");
|
|
4701
4433
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4702
4434
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4703
4435
|
var CORE_GITIGNORE_ENTRIES = [
|
|
@@ -4714,58 +4446,52 @@ var CORE_GITIGNORE_ENTRIES = [
|
|
|
4714
4446
|
".claude/plans/",
|
|
4715
4447
|
".claude/settings.local.json"
|
|
4716
4448
|
];
|
|
4717
|
-
var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
|
|
4718
|
-
"# Factory runtime state (generated by workflow, not committed)",
|
|
4719
|
-
"factory/STATE.md",
|
|
4720
|
-
"factory/PROJECT.md",
|
|
4721
|
-
"factory/REQUIREMENTS.md",
|
|
4722
|
-
"factory/ARCHITECTURE.md",
|
|
4723
|
-
"factory/ROADMAP.md",
|
|
4724
|
-
"factory/PLAN.md",
|
|
4725
|
-
"factory/GATE-REPORT.md",
|
|
4726
|
-
"factory/VERIFY-REPORT.md",
|
|
4727
|
-
"factory/COMPLETION.md",
|
|
4728
|
-
"factory/summaries/",
|
|
4729
|
-
"factory/ai-context/"
|
|
4730
|
-
];
|
|
4731
4449
|
var update_gitignore_default = defineStep({
|
|
4732
4450
|
name: "update-gitignore",
|
|
4733
4451
|
label: "Updating .gitignore",
|
|
4734
4452
|
execute: async (ctx) => {
|
|
4735
|
-
const gitignorePath = (0,
|
|
4736
|
-
|
|
4737
|
-
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4738
|
-
const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
|
|
4739
|
-
const entries = includeLegacyFactory ? [...CORE_GITIGNORE_ENTRIES, "", ...LEGACY_FACTORY_GITIGNORE_ENTRIES] : CORE_GITIGNORE_ENTRIES;
|
|
4740
|
-
const block = [MARKER_START2, ...entries, MARKER_END2].join("\n");
|
|
4741
|
-
if (!(0, import_fs7.existsSync)(gitignorePath)) {
|
|
4742
|
-
(0, import_fs7.writeFileSync)(gitignorePath, block + "\n", "utf-8");
|
|
4743
|
-
return { status: "success" };
|
|
4744
|
-
}
|
|
4745
|
-
const existing = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
|
|
4746
|
-
const start = existing.indexOf(MARKER_START2);
|
|
4747
|
-
const end = existing.indexOf(MARKER_END2);
|
|
4748
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
4749
|
-
const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
|
|
4750
|
-
(0, import_fs7.writeFileSync)(gitignorePath, updated, "utf-8");
|
|
4751
|
-
} else {
|
|
4752
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4753
|
-
(0, import_fs7.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
|
|
4754
|
-
}
|
|
4453
|
+
const gitignorePath = (0, import_path10.join)(ctx.config.projectPath, ".gitignore");
|
|
4454
|
+
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4755
4455
|
return { status: "success" };
|
|
4756
4456
|
}
|
|
4757
4457
|
});
|
|
4758
4458
|
|
|
4759
4459
|
// src/steps/setup/write-state.ts
|
|
4760
|
-
var
|
|
4761
|
-
var
|
|
4460
|
+
var import_fs10 = require("fs");
|
|
4461
|
+
var import_path11 = require("path");
|
|
4762
4462
|
var import_os2 = require("os");
|
|
4763
|
-
|
|
4463
|
+
|
|
4464
|
+
// package.json
|
|
4465
|
+
var package_default = {
|
|
4466
|
+
name: "one-shot-setup",
|
|
4467
|
+
version: "0.6.0",
|
|
4468
|
+
description: "One-shot installation tool for The Machine toolchain",
|
|
4469
|
+
type: "module",
|
|
4470
|
+
main: "src/index.ts",
|
|
4471
|
+
scripts: {
|
|
4472
|
+
dev: "tsx src/index.ts",
|
|
4473
|
+
build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
|
|
4474
|
+
"publish:wrapper": "cd wrapper && npm publish"
|
|
4475
|
+
},
|
|
4476
|
+
dependencies: {
|
|
4477
|
+
"@inquirer/prompts": "^5.0.0"
|
|
4478
|
+
},
|
|
4479
|
+
devDependencies: {
|
|
4480
|
+
"@types/node": "^20.11.0",
|
|
4481
|
+
esbuild: "latest",
|
|
4482
|
+
tsx: "latest"
|
|
4483
|
+
},
|
|
4484
|
+
engines: {
|
|
4485
|
+
node: ">=18.0.0"
|
|
4486
|
+
}
|
|
4487
|
+
};
|
|
4488
|
+
|
|
4489
|
+
// src/steps/setup/write-state.ts
|
|
4764
4490
|
var write_state_default = defineStep({
|
|
4765
4491
|
name: "write-state",
|
|
4766
4492
|
label: "Saving installation state",
|
|
4767
4493
|
execute: async (ctx) => {
|
|
4768
|
-
const statePath = (0,
|
|
4494
|
+
const statePath = (0, import_path11.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4769
4495
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4770
4496
|
const filteredMcpConfig = Object.fromEntries(
|
|
4771
4497
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
@@ -4773,7 +4499,7 @@ var write_state_default = defineStep({
|
|
|
4773
4499
|
const target = getAgentTarget(ctx.config.agent);
|
|
4774
4500
|
const state = {
|
|
4775
4501
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4776
|
-
installerVersion:
|
|
4502
|
+
installerVersion: package_default.version,
|
|
4777
4503
|
releaseVersion: ctx.config.releaseVersion,
|
|
4778
4504
|
contextReleaseVersion: ctx.installed.contextReleaseVersion ?? null,
|
|
4779
4505
|
projectPath: ctx.config.projectPath,
|
|
@@ -4788,10 +4514,10 @@ var write_state_default = defineStep({
|
|
|
4788
4514
|
mcpConfig: target.mcpConfig,
|
|
4789
4515
|
contexts: "_ai-context/",
|
|
4790
4516
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4791
|
-
globalConfig: (0,
|
|
4517
|
+
globalConfig: (0, import_path11.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4792
4518
|
}
|
|
4793
4519
|
};
|
|
4794
|
-
(0,
|
|
4520
|
+
(0, import_fs10.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4795
4521
|
return { status: "success" };
|
|
4796
4522
|
}
|
|
4797
4523
|
});
|
|
@@ -4806,27 +4532,27 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
|
4806
4532
|
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
4807
4533
|
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4808
4534
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4809
|
-
if (!(0,
|
|
4810
|
-
const content = (0,
|
|
4535
|
+
if (!(0, import_fs11.existsSync)(filePath)) return green("create");
|
|
4536
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
4811
4537
|
if (content.includes(markerStart)) return yellow("update");
|
|
4812
4538
|
return yellow("append");
|
|
4813
4539
|
}
|
|
4814
4540
|
function detectMCPFileMode(filePath) {
|
|
4815
|
-
if (!(0,
|
|
4541
|
+
if (!(0, import_fs11.existsSync)(filePath)) return green("create");
|
|
4816
4542
|
return yellow("merge");
|
|
4817
4543
|
}
|
|
4818
4544
|
function detectContextMode(projectPath, domain) {
|
|
4819
|
-
const contextDir = (0,
|
|
4820
|
-
const contextDirLower = (0,
|
|
4821
|
-
if ((0,
|
|
4545
|
+
const contextDir = (0, import_path12.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
4546
|
+
const contextDirLower = (0, import_path12.join)(projectPath, "_ai-context", domain);
|
|
4547
|
+
if ((0, import_fs11.existsSync)(contextDir) || (0, import_fs11.existsSync)(contextDirLower)) return yellow("overwrite");
|
|
4822
4548
|
return green("create");
|
|
4823
4549
|
}
|
|
4824
4550
|
function waitForEnter() {
|
|
4825
4551
|
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
4826
|
-
return new Promise((
|
|
4552
|
+
return new Promise((resolve4) => {
|
|
4827
4553
|
rl.question(" Press Enter to close...", () => {
|
|
4828
4554
|
rl.close();
|
|
4829
|
-
|
|
4555
|
+
resolve4();
|
|
4830
4556
|
});
|
|
4831
4557
|
});
|
|
4832
4558
|
}
|
|
@@ -4916,13 +4642,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4916
4642
|
}
|
|
4917
4643
|
const projectInput = await esm_default4({
|
|
4918
4644
|
message: "Project directory:",
|
|
4919
|
-
default: (0,
|
|
4645
|
+
default: (0, import_path12.resolve)(process.cwd())
|
|
4920
4646
|
});
|
|
4921
4647
|
return {
|
|
4922
4648
|
technologies: selectedTechnologies,
|
|
4923
4649
|
agent,
|
|
4924
4650
|
azureDevOpsOrg,
|
|
4925
|
-
projectPath: (0,
|
|
4651
|
+
projectPath: (0, import_path12.resolve)(projectInput),
|
|
4926
4652
|
baseMcpServers: options.baseMcpServers,
|
|
4927
4653
|
mcpConfig,
|
|
4928
4654
|
releaseVersion,
|
|
@@ -4936,9 +4662,9 @@ async function previewAndConfirm(config, options) {
|
|
|
4936
4662
|
const instructionFile = target.instructions;
|
|
4937
4663
|
const mcpConfigFile = target.mcpConfig;
|
|
4938
4664
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
4939
|
-
const instructionFilePath = (0,
|
|
4940
|
-
const mcpConfigFilePath = (0,
|
|
4941
|
-
const gitignorePath = (0,
|
|
4665
|
+
const instructionFilePath = (0, import_path12.join)(config.projectPath, instructionFile);
|
|
4666
|
+
const mcpConfigFilePath = (0, import_path12.join)(config.projectPath, mcpConfigFile);
|
|
4667
|
+
const gitignorePath = (0, import_path12.join)(config.projectPath, ".gitignore");
|
|
4942
4668
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
4943
4669
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
4944
4670
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -4964,7 +4690,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4964
4690
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
4965
4691
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
4966
4692
|
if (config.installFactory) {
|
|
4967
|
-
const factoryExists = (0,
|
|
4693
|
+
const factoryExists = (0, import_fs11.existsSync)((0, import_path12.join)(config.projectPath, "factory"));
|
|
4968
4694
|
const factoryMode = factoryExists ? yellow("overwrite") : green("create");
|
|
4969
4695
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
4970
4696
|
}
|
|
@@ -4983,6 +4709,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4983
4709
|
function printSummary(result, config) {
|
|
4984
4710
|
const failed = result.entries.filter((e) => e.result.status === "failed");
|
|
4985
4711
|
const succeeded = result.entries.filter((e) => e.result.status === "success");
|
|
4712
|
+
const skipped = result.entries.filter((e) => e.result.status === "skipped");
|
|
4986
4713
|
const hasErrors = failed.length > 0;
|
|
4987
4714
|
console.log("\u2500".repeat(48));
|
|
4988
4715
|
console.log(hasErrors ? " Done (with errors).\n" : " Done.\n");
|
|
@@ -4990,6 +4717,10 @@ function printSummary(result, config) {
|
|
|
4990
4717
|
const msg = entry.result.message ? ` ${entry.result.message}` : "";
|
|
4991
4718
|
console.log(` ${entry.label}${msg}`);
|
|
4992
4719
|
}
|
|
4720
|
+
for (const entry of skipped) {
|
|
4721
|
+
const detail = entry.result.detail ? ` ${entry.result.detail}` : "";
|
|
4722
|
+
console.log(` ${entry.label} (skipped)${detail}`);
|
|
4723
|
+
}
|
|
4993
4724
|
if (hasErrors) {
|
|
4994
4725
|
console.log("\n What went wrong:");
|
|
4995
4726
|
for (const entry of failed) {
|