dlw-machine-setup 0.6.1 → 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 +140 -333
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -3307,34 +3307,96 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
|
|
|
3307
3307
|
throw lastError || new Error("Failed after maximum retries");
|
|
3308
3308
|
}
|
|
3309
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
|
+
|
|
3310
3373
|
// src/utils/wizard-options.ts
|
|
3311
3374
|
async function fetchWizardOptions(token, repo) {
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
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",
|
|
3315
3383
|
"Authorization": `Bearer ${token}`
|
|
3316
|
-
};
|
|
3317
|
-
const releaseRes = await fetchWithRetry(
|
|
3318
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3319
|
-
{ headers }
|
|
3320
|
-
);
|
|
3321
|
-
if (!releaseRes.ok) return null;
|
|
3322
|
-
const release = await releaseRes.json();
|
|
3323
|
-
const asset = release.assets?.find((a) => a.name === "wizard-options.json");
|
|
3324
|
-
if (!asset) return null;
|
|
3325
|
-
const assetRes = await fetchWithRetry(asset.url, {
|
|
3326
|
-
headers: { ...headers, "Accept": "application/octet-stream" }
|
|
3327
|
-
});
|
|
3328
|
-
if (!assetRes.ok) return null;
|
|
3329
|
-
const data = await assetRes.json();
|
|
3330
|
-
const techArray = data.personas?.technologies ?? data.personas?.personas;
|
|
3331
|
-
if (!Array.isArray(data.agents) || !Array.isArray(techArray)) {
|
|
3332
|
-
return null;
|
|
3333
3384
|
}
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3385
|
+
});
|
|
3386
|
+
if (!assetRes.ok) {
|
|
3387
|
+
throw new Error(`wizard-options.json download failed: ${getReadableError(assetRes.status)}`);
|
|
3337
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)");
|
|
3398
|
+
}
|
|
3399
|
+
return { options: data, releaseVersion: release.tagName };
|
|
3338
3400
|
}
|
|
3339
3401
|
|
|
3340
3402
|
// src/utils/discover-repo.ts
|
|
@@ -3403,46 +3465,44 @@ async function tryOrgReposList(headers, topic) {
|
|
|
3403
3465
|
}
|
|
3404
3466
|
|
|
3405
3467
|
// src/utils/github-auth.ts
|
|
3406
|
-
var
|
|
3407
|
-
var
|
|
3468
|
+
var import_fs2 = require("fs");
|
|
3469
|
+
var import_path2 = require("path");
|
|
3408
3470
|
var import_os = require("os");
|
|
3409
|
-
var
|
|
3471
|
+
var import_child_process2 = require("child_process");
|
|
3410
3472
|
var GITHUB_CLIENT_ID = "Ov23liwpMumAhwVufZ7N";
|
|
3411
3473
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
3412
3474
|
var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
3475
|
+
var SOFT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3413
3476
|
function getTokenCachePath() {
|
|
3414
|
-
const configDir = (0,
|
|
3415
|
-
if (!(0,
|
|
3416
|
-
(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 });
|
|
3417
3480
|
}
|
|
3418
|
-
return (0,
|
|
3481
|
+
return (0, import_path2.join)(configDir, "github-token.json");
|
|
3419
3482
|
}
|
|
3420
3483
|
function loadCachedToken() {
|
|
3421
3484
|
const cachePath = getTokenCachePath();
|
|
3422
|
-
if (!(0,
|
|
3423
|
-
return null;
|
|
3424
|
-
}
|
|
3485
|
+
if (!(0, import_fs2.existsSync)(cachePath)) return null;
|
|
3425
3486
|
try {
|
|
3426
|
-
const data = (0,
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
} catch (error) {
|
|
3487
|
+
const data = (0, import_fs2.readFileSync)(cachePath, "utf-8");
|
|
3488
|
+
return JSON.parse(data);
|
|
3489
|
+
} catch {
|
|
3430
3490
|
return null;
|
|
3431
3491
|
}
|
|
3432
3492
|
}
|
|
3433
3493
|
function saveTokenToCache(token) {
|
|
3434
3494
|
const cachePath = getTokenCachePath();
|
|
3435
|
-
const
|
|
3495
|
+
const cached2 = {
|
|
3436
3496
|
access_token: token,
|
|
3437
3497
|
created_at: Date.now()
|
|
3438
3498
|
};
|
|
3439
|
-
(0,
|
|
3499
|
+
(0, import_fs2.writeFileSync)(cachePath, JSON.stringify(cached2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3440
3500
|
}
|
|
3441
3501
|
function clearTokenCache() {
|
|
3442
3502
|
const cachePath = getTokenCachePath();
|
|
3443
|
-
if ((0,
|
|
3503
|
+
if ((0, import_fs2.existsSync)(cachePath)) {
|
|
3444
3504
|
try {
|
|
3445
|
-
(0,
|
|
3505
|
+
(0, import_fs2.unlinkSync)(cachePath);
|
|
3446
3506
|
} catch (error) {
|
|
3447
3507
|
}
|
|
3448
3508
|
}
|
|
@@ -3517,11 +3577,14 @@ async function authenticateWithGitHub() {
|
|
|
3517
3577
|
}
|
|
3518
3578
|
console.log(" GITHUB_TOKEN env var is invalid or expired, falling back to authentication...\n");
|
|
3519
3579
|
}
|
|
3520
|
-
const
|
|
3521
|
-
if (
|
|
3522
|
-
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);
|
|
3523
3585
|
if (isValid) {
|
|
3524
|
-
|
|
3586
|
+
saveTokenToCache(cached2.access_token);
|
|
3587
|
+
return cached2.access_token;
|
|
3525
3588
|
}
|
|
3526
3589
|
clearTokenCache();
|
|
3527
3590
|
}
|
|
@@ -3534,13 +3597,13 @@ async function authenticateWithGitHub() {
|
|
|
3534
3597
|
try {
|
|
3535
3598
|
const os = (0, import_os.platform)();
|
|
3536
3599
|
if (os === "win32") {
|
|
3537
|
-
(0,
|
|
3600
|
+
(0, import_child_process2.execSync)(`echo ${deviceCodeData.user_code} | clip`, { stdio: "ignore" });
|
|
3538
3601
|
copied = true;
|
|
3539
3602
|
} else if (os === "darwin") {
|
|
3540
|
-
(0,
|
|
3603
|
+
(0, import_child_process2.execSync)(`echo "${deviceCodeData.user_code}" | pbcopy`, { stdio: "ignore" });
|
|
3541
3604
|
copied = true;
|
|
3542
3605
|
} else {
|
|
3543
|
-
(0,
|
|
3606
|
+
(0, import_child_process2.execSync)(`echo "${deviceCodeData.user_code}" | xclip -selection clipboard`, { stdio: "ignore" });
|
|
3544
3607
|
copied = true;
|
|
3545
3608
|
}
|
|
3546
3609
|
} catch {
|
|
@@ -3622,11 +3685,7 @@ function getAgentTarget(agent) {
|
|
|
3622
3685
|
|
|
3623
3686
|
// src/utils/mod.ts
|
|
3624
3687
|
async function loadWizardOptions(token, repo) {
|
|
3625
|
-
const
|
|
3626
|
-
if (!remote) {
|
|
3627
|
-
throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
|
|
3628
|
-
}
|
|
3629
|
-
const { options, releaseVersion } = remote;
|
|
3688
|
+
const { options, releaseVersion } = await fetchWizardOptions(token, repo);
|
|
3630
3689
|
const filteredMcpServers = Object.fromEntries(
|
|
3631
3690
|
Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
|
|
3632
3691
|
);
|
|
@@ -3693,78 +3752,15 @@ var import_fs4 = require("fs");
|
|
|
3693
3752
|
var import_path4 = require("path");
|
|
3694
3753
|
|
|
3695
3754
|
// src/utils/fs-copy.ts
|
|
3696
|
-
var import_fs2 = require("fs");
|
|
3697
|
-
var import_path2 = require("path");
|
|
3698
|
-
function copyDirectory(source, target) {
|
|
3699
|
-
if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
|
|
3700
|
-
for (const entry of (0, import_fs2.readdirSync)(source, { withFileTypes: true })) {
|
|
3701
|
-
const s = (0, import_path2.join)(source, entry.name);
|
|
3702
|
-
const t = (0, import_path2.join)(target, entry.name);
|
|
3703
|
-
if (entry.isDirectory()) copyDirectory(s, t);
|
|
3704
|
-
else (0, import_fs2.copyFileSync)(s, t);
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
|
|
3708
|
-
// src/utils/download-archive.ts
|
|
3709
3755
|
var import_fs3 = require("fs");
|
|
3710
3756
|
var import_path3 = require("path");
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
const res = await fetchWithRetry(`https://api.github.com/repos/${repo}/releases/latest`, { headers });
|
|
3719
|
-
if (!res.ok) {
|
|
3720
|
-
throw new Error(`GitHub API error (${res.status}): ${getReadableError(res.status)}`);
|
|
3721
|
-
}
|
|
3722
|
-
const data = await res.json();
|
|
3723
|
-
return { tagName: data.tag_name ?? "unknown", assets: data.assets ?? [] };
|
|
3724
|
-
}
|
|
3725
|
-
async function downloadAndExtractAsset(token, asset, projectPath, tempDirName) {
|
|
3726
|
-
const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
|
|
3727
|
-
if (tarCheck.status !== 0) {
|
|
3728
|
-
throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
|
|
3729
|
-
}
|
|
3730
|
-
const tempDir = (0, import_path3.join)(projectPath, tempDirName);
|
|
3731
|
-
if ((0, import_fs3.existsSync)(tempDir)) (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
|
|
3732
|
-
(0, import_fs3.mkdirSync)(tempDir, { recursive: true });
|
|
3733
|
-
const cleanup = () => {
|
|
3734
|
-
if ((0, import_fs3.existsSync)(tempDir)) {
|
|
3735
|
-
try {
|
|
3736
|
-
(0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
|
|
3737
|
-
} catch {
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
};
|
|
3741
|
-
try {
|
|
3742
|
-
const dlRes = await fetchWithRetry(asset.url, {
|
|
3743
|
-
headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
|
|
3744
|
-
});
|
|
3745
|
-
if (!dlRes.ok) {
|
|
3746
|
-
throw new Error(`Download failed: ${getReadableError(dlRes.status)}`);
|
|
3747
|
-
}
|
|
3748
|
-
const archivePath = (0, import_path3.join)(tempDir, asset.name);
|
|
3749
|
-
(0, import_fs3.writeFileSync)(archivePath, Buffer.from(await dlRes.arrayBuffer()));
|
|
3750
|
-
if ((0, import_fs3.statSync)(archivePath).size < MIN_FILE_SIZE) {
|
|
3751
|
-
throw new Error("File corrupted");
|
|
3752
|
-
}
|
|
3753
|
-
const list = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
3754
|
-
if (list.status !== 0) throw new Error("Failed to read archive contents");
|
|
3755
|
-
const resolvedTemp = (0, import_path3.resolve)(tempDir);
|
|
3756
|
-
for (const entry of list.stdout.split("\n").filter(Boolean)) {
|
|
3757
|
-
const path = (0, import_path3.resolve)((0, import_path3.join)(tempDir, entry.replace(/\/$/, "")));
|
|
3758
|
-
if (!path.startsWith(resolvedTemp + import_path3.sep)) {
|
|
3759
|
-
throw new Error(`Archive contains unsafe path: ${entry}`);
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
const ex = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3763
|
-
if (ex.status !== 0) throw new Error("Archive extraction failed");
|
|
3764
|
-
return { extractedRoot: tempDir, cleanup };
|
|
3765
|
-
} catch (err) {
|
|
3766
|
-
cleanup();
|
|
3767
|
-
throw err;
|
|
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);
|
|
3768
3764
|
}
|
|
3769
3765
|
}
|
|
3770
3766
|
|
|
@@ -4058,17 +4054,13 @@ var fetch_factory_default = defineStep({
|
|
|
4058
4054
|
execute: async (ctx) => {
|
|
4059
4055
|
const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
|
|
4060
4056
|
ctx.installed.factoryInstalled = result.success;
|
|
4061
|
-
ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
|
|
4062
4057
|
if (result.instructionsSnippet) {
|
|
4063
|
-
|
|
4064
|
-
stash["factory"] = result.instructionsSnippet;
|
|
4065
|
-
ctx.installed.bundleInstructions = stash;
|
|
4058
|
+
ctx.installed.factoryInstructionsSnippet = result.instructionsSnippet;
|
|
4066
4059
|
}
|
|
4067
4060
|
if (!result.success) {
|
|
4068
4061
|
return { status: "failed", detail: result.failureReason };
|
|
4069
4062
|
}
|
|
4070
|
-
|
|
4071
|
-
return { status: "success", message: result.filesInstalled.join(", ") + tag };
|
|
4063
|
+
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4072
4064
|
}
|
|
4073
4065
|
});
|
|
4074
4066
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
@@ -4097,13 +4089,11 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
4097
4089
|
}
|
|
4098
4090
|
const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
|
|
4099
4091
|
const manifestPath = (0, import_path7.join)(extractedPath, "install.json");
|
|
4100
|
-
if ((0, import_fs7.existsSync)(manifestPath)) {
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
installCopilotFactory(extractedPath, targetDir, result);
|
|
4104
|
-
} else {
|
|
4105
|
-
installClaudeFactory(extractedPath, targetDir, result);
|
|
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;
|
|
4106
4095
|
}
|
|
4096
|
+
await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
|
|
4107
4097
|
result.success = true;
|
|
4108
4098
|
} catch (error) {
|
|
4109
4099
|
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
@@ -4128,132 +4118,18 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
|
|
|
4128
4118
|
skipInstructions: true
|
|
4129
4119
|
// write-instructions.ts commits the snippet for correct ordering
|
|
4130
4120
|
});
|
|
4131
|
-
result.bundleManaged = true;
|
|
4132
4121
|
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4133
4122
|
result.filesInstalled = runResult.filesTouched;
|
|
4134
4123
|
}
|
|
4135
|
-
function installClaudeFactory(extractedPath, targetDir, result) {
|
|
4136
|
-
const claudeDir = (0, import_path7.join)(targetDir, ".claude");
|
|
4137
|
-
const factoryDir = (0, import_path7.join)(targetDir, "factory");
|
|
4138
|
-
const agentsSrc = (0, import_path7.join)(extractedPath, "agents");
|
|
4139
|
-
if ((0, import_fs7.existsSync)(agentsSrc)) {
|
|
4140
|
-
copyDirectory(agentsSrc, (0, import_path7.join)(claudeDir, "agents"));
|
|
4141
|
-
result.filesInstalled.push(".claude/agents/");
|
|
4142
|
-
}
|
|
4143
|
-
const hooksSrc = (0, import_path7.join)(extractedPath, "hooks");
|
|
4144
|
-
if ((0, import_fs7.existsSync)(hooksSrc)) {
|
|
4145
|
-
copyDirectory(hooksSrc, (0, import_path7.join)(claudeDir, "hooks"));
|
|
4146
|
-
result.filesInstalled.push(".claude/hooks/");
|
|
4147
|
-
}
|
|
4148
|
-
const skillsSrc = (0, import_path7.join)(extractedPath, "skills");
|
|
4149
|
-
if ((0, import_fs7.existsSync)(skillsSrc)) {
|
|
4150
|
-
copyDirectory(skillsSrc, (0, import_path7.join)(claudeDir, "skills"));
|
|
4151
|
-
result.filesInstalled.push(".claude/skills/");
|
|
4152
|
-
}
|
|
4153
|
-
const workflowSrc = (0, import_path7.join)(extractedPath, "workflow");
|
|
4154
|
-
if ((0, import_fs7.existsSync)(workflowSrc)) {
|
|
4155
|
-
copyDirectory(workflowSrc, (0, import_path7.join)(factoryDir, "workflow"));
|
|
4156
|
-
result.filesInstalled.push("factory/workflow/");
|
|
4157
|
-
}
|
|
4158
|
-
const utilsSrc = (0, import_path7.join)(extractedPath, "utils");
|
|
4159
|
-
if ((0, import_fs7.existsSync)(utilsSrc)) {
|
|
4160
|
-
copyDirectory(utilsSrc, (0, import_path7.join)(factoryDir, "utils"));
|
|
4161
|
-
result.filesInstalled.push("factory/utils/");
|
|
4162
|
-
}
|
|
4163
|
-
const docsSrc = (0, import_path7.join)(extractedPath, "docs");
|
|
4164
|
-
if ((0, import_fs7.existsSync)(docsSrc)) {
|
|
4165
|
-
copyDirectory(docsSrc, (0, import_path7.join)(factoryDir, "docs"));
|
|
4166
|
-
result.filesInstalled.push("factory/docs/");
|
|
4167
|
-
}
|
|
4168
|
-
const rootEntries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
|
|
4169
|
-
for (const entry of rootEntries) {
|
|
4170
|
-
if (entry.isFile()) {
|
|
4171
|
-
if (!(0, import_fs7.existsSync)(factoryDir)) (0, import_fs7.mkdirSync)(factoryDir, { recursive: true });
|
|
4172
|
-
(0, import_fs7.copyFileSync)((0, import_path7.join)(extractedPath, entry.name), (0, import_path7.join)(factoryDir, entry.name));
|
|
4173
|
-
result.filesInstalled.push(`factory/${entry.name}`);
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
configureSettings(claudeDir);
|
|
4177
|
-
result.filesInstalled.push(".claude/settings.json");
|
|
4178
|
-
}
|
|
4179
|
-
function installCopilotFactory(extractedPath, targetDir, result) {
|
|
4180
|
-
const factoryDir = (0, import_path7.join)(targetDir, "factory");
|
|
4181
|
-
copyDirectory(extractedPath, factoryDir);
|
|
4182
|
-
const entries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
|
|
4183
|
-
for (const entry of entries) {
|
|
4184
|
-
if (entry.isDirectory()) {
|
|
4185
|
-
result.filesInstalled.push(`factory/${entry.name}/`);
|
|
4186
|
-
} else {
|
|
4187
|
-
result.filesInstalled.push(`factory/${entry.name}`);
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4190
|
-
}
|
|
4191
|
-
function configureSettings(claudeDir) {
|
|
4192
|
-
const settingsPath = (0, import_path7.join)(claudeDir, "settings.json");
|
|
4193
|
-
let settings = {};
|
|
4194
|
-
if ((0, import_fs7.existsSync)(settingsPath)) {
|
|
4195
|
-
try {
|
|
4196
|
-
settings = JSON.parse((0, import_fs7.readFileSync)(settingsPath, "utf8"));
|
|
4197
|
-
} catch {
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
if (!settings.hooks) settings.hooks = {};
|
|
4201
|
-
const welcomeHook = {
|
|
4202
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-welcome.cjs" }]
|
|
4203
|
-
};
|
|
4204
|
-
const immutableGuardHook = {
|
|
4205
|
-
matcher: "Write|Edit",
|
|
4206
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-immutable-guard.cjs" }]
|
|
4207
|
-
};
|
|
4208
|
-
const stateAdvanceHook = {
|
|
4209
|
-
matcher: "Write|Edit",
|
|
4210
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-state-advance.cjs" }]
|
|
4211
|
-
};
|
|
4212
|
-
const contextMonitorHook = {
|
|
4213
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-context-monitor.cjs" }]
|
|
4214
|
-
};
|
|
4215
|
-
const stateGuardHook = {
|
|
4216
|
-
hooks: [{ type: "command", command: "node .claude/hooks/factory-state-guard.cjs" }]
|
|
4217
|
-
};
|
|
4218
|
-
for (const event of ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
4219
|
-
if (settings.hooks[event]) {
|
|
4220
|
-
settings.hooks[event] = settings.hooks[event].filter(
|
|
4221
|
-
(h) => !h.hooks?.some(
|
|
4222
|
-
(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")
|
|
4223
|
-
)
|
|
4224
|
-
);
|
|
4225
|
-
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
4226
|
-
}
|
|
4227
|
-
}
|
|
4228
|
-
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
4229
|
-
settings.hooks.SessionStart.unshift(welcomeHook);
|
|
4230
|
-
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
4231
|
-
settings.hooks.PreToolUse.unshift(immutableGuardHook);
|
|
4232
|
-
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
4233
|
-
settings.hooks.PostToolUse.unshift(stateAdvanceHook);
|
|
4234
|
-
if (!settings.hooks.UserPromptSubmit) {
|
|
4235
|
-
settings.hooks.UserPromptSubmit = [stateGuardHook, contextMonitorHook];
|
|
4236
|
-
} else {
|
|
4237
|
-
const hasGuard = settings.hooks.UserPromptSubmit.some(
|
|
4238
|
-
(h) => h.hooks?.some((hh) => hh.command?.includes("factory-state-guard"))
|
|
4239
|
-
);
|
|
4240
|
-
if (!hasGuard) settings.hooks.UserPromptSubmit.unshift(stateGuardHook);
|
|
4241
|
-
const hasMonitor = settings.hooks.UserPromptSubmit.some(
|
|
4242
|
-
(h) => h.hooks?.some((hh) => hh.command?.includes("factory-context-monitor"))
|
|
4243
|
-
);
|
|
4244
|
-
if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
|
|
4245
|
-
}
|
|
4246
|
-
settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
|
|
4247
|
-
if (!(0, import_fs7.existsSync)(claudeDir)) (0, import_fs7.mkdirSync)(claudeDir, { recursive: true });
|
|
4248
|
-
(0, import_fs7.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4249
|
-
}
|
|
4250
4124
|
|
|
4251
4125
|
// src/steps/setup/write-instructions.ts
|
|
4252
4126
|
var import_fs8 = require("fs");
|
|
4253
4127
|
var import_path8 = require("path");
|
|
4254
4128
|
|
|
4255
4129
|
// src/steps/shared.ts
|
|
4130
|
+
var cached = null;
|
|
4256
4131
|
function getFilteredMcpConfig(ctx) {
|
|
4132
|
+
if (cached) return cached;
|
|
4257
4133
|
const successfulDomains = new Set(ctx.installed.domainsInstalled ?? []);
|
|
4258
4134
|
const successfulTechnologies = ctx.config.technologies.filter(
|
|
4259
4135
|
(p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
|
|
@@ -4262,9 +4138,10 @@ function getFilteredMcpConfig(ctx) {
|
|
|
4262
4138
|
...ctx.config.baseMcpServers,
|
|
4263
4139
|
...successfulTechnologies.flatMap((p) => p.mcpServers)
|
|
4264
4140
|
]);
|
|
4265
|
-
|
|
4141
|
+
cached = Object.fromEntries(
|
|
4266
4142
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
|
|
4267
4143
|
);
|
|
4144
|
+
return cached;
|
|
4268
4145
|
}
|
|
4269
4146
|
|
|
4270
4147
|
// src/steps/setup/write-instructions.ts
|
|
@@ -4274,16 +4151,13 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4274
4151
|
var write_instructions_default = defineStep({
|
|
4275
4152
|
name: "write-instructions",
|
|
4276
4153
|
label: "Writing instruction file",
|
|
4277
|
-
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,
|
|
4278
4155
|
execute: async (ctx) => {
|
|
4279
4156
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4280
4157
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
4281
4158
|
const agent = ctx.config.agent;
|
|
4282
4159
|
const projectPath = ctx.config.projectPath;
|
|
4283
|
-
const
|
|
4284
|
-
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4285
|
-
const includeFactorySection = factoryInstalled && !factoryBundleManaged;
|
|
4286
|
-
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
|
|
4160
|
+
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4287
4161
|
const target = getAgentTarget(agent);
|
|
4288
4162
|
const filePath = (0, import_path8.join)(projectPath, target.instructions);
|
|
4289
4163
|
const fileDir = (0, import_path8.dirname)(filePath);
|
|
@@ -4316,13 +4190,12 @@ alwaysApply: true
|
|
|
4316
4190
|
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4317
4191
|
}
|
|
4318
4192
|
});
|
|
4319
|
-
|
|
4320
|
-
for (const bundleName of Object.keys(bundleSnippets).sort()) {
|
|
4193
|
+
if (ctx.installed.factoryInstructionsSnippet) {
|
|
4321
4194
|
upsertMarkerBlock(
|
|
4322
4195
|
filePath,
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4196
|
+
"<!-- factory:start -->",
|
|
4197
|
+
"<!-- factory:end -->",
|
|
4198
|
+
ctx.installed.factoryInstructionsSnippet
|
|
4326
4199
|
);
|
|
4327
4200
|
}
|
|
4328
4201
|
return { status: "success", message: target.instructions };
|
|
@@ -4426,67 +4299,19 @@ function buildMCPSection(mcpConfig) {
|
|
|
4426
4299
|
if (entries.length === 0) return "";
|
|
4427
4300
|
const lines2 = [`## MCP Servers`, ``, `The following MCP servers are available. Use them proactively when relevant:`, ``];
|
|
4428
4301
|
for (const [name, config] of entries) {
|
|
4429
|
-
|
|
4430
|
-
const useWhen = config.useWhen;
|
|
4431
|
-
if (!description) continue;
|
|
4302
|
+
if (!config.description) continue;
|
|
4432
4303
|
lines2.push(`### ${name}`);
|
|
4433
|
-
lines2.push(description);
|
|
4434
|
-
if (useWhen) lines2.push(`**Use when**: ${useWhen}`);
|
|
4304
|
+
lines2.push(config.description);
|
|
4305
|
+
if (config.useWhen) lines2.push(`**Use when**: ${config.useWhen}`);
|
|
4435
4306
|
lines2.push(``);
|
|
4436
4307
|
}
|
|
4437
4308
|
return lines2.join("\n");
|
|
4438
4309
|
}
|
|
4439
|
-
function
|
|
4440
|
-
if (agent === "github-copilot") {
|
|
4441
|
-
return [
|
|
4442
|
-
`## Factory Dev Workflow`,
|
|
4443
|
-
``,
|
|
4444
|
-
`Factory is installed. Skills are in \`factory/skills/\`, templates in \`factory/templates/\`.`,
|
|
4445
|
-
``,
|
|
4446
|
-
`Read \`factory/README.md\` and \`factory/PROTOCOL.md\` for usage instructions.`,
|
|
4447
|
-
``,
|
|
4448
|
-
`Available skills:`,
|
|
4449
|
-
`- \`factory-advisor\` \u2014 start or resume the workflow (main entry point)`,
|
|
4450
|
-
`- \`factory-init\` \u2014 initialize project state`,
|
|
4451
|
-
`- \`factory-planner\` \u2014 create story plan`,
|
|
4452
|
-
`- \`factory-executor\` \u2014 execute development tasks`,
|
|
4453
|
-
`- \`factory-verifier\` \u2014 code review`,
|
|
4454
|
-
`- \`factory-completer\` \u2014 finalize and complete work`,
|
|
4455
|
-
``
|
|
4456
|
-
].join("\n");
|
|
4457
|
-
}
|
|
4458
|
-
return [
|
|
4459
|
-
`## Factory Dev Workflow`,
|
|
4460
|
-
``,
|
|
4461
|
-
`Factory is installed. Use \`/factory-advisor\` to start or resume the workflow.`,
|
|
4462
|
-
``,
|
|
4463
|
-
`Available commands:`,
|
|
4464
|
-
`- \`/factory-advisor\` \u2014 start or resume the workflow (main entry point)`,
|
|
4465
|
-
`- \`/factory-flow\` \u2014 guided workflow execution`,
|
|
4466
|
-
`- \`/factory-feature-dev\` \u2014 add a feature to an existing project`,
|
|
4467
|
-
`- \`/factory-dev\` \u2014 execute development tasks`,
|
|
4468
|
-
`- \`/factory-progress\` \u2014 check status`,
|
|
4469
|
-
`- \`/factory-init-state\` \u2014 initialize state for a brownfield project`,
|
|
4470
|
-
`- \`/factory-analyst\` \u2014 validate requirements`,
|
|
4471
|
-
`- \`/factory-architect\` \u2014 design architecture`,
|
|
4472
|
-
`- \`/factory-planner\` \u2014 create story plan`,
|
|
4473
|
-
`- \`/factory-verifier\` \u2014 code review`,
|
|
4474
|
-
`- \`/factory-security\` \u2014 OWASP security review`,
|
|
4475
|
-
`- \`/factory-test-analyst\` \u2014 test quality review`,
|
|
4476
|
-
`- \`/factory-explorer\` \u2014 explore and understand code`,
|
|
4477
|
-
`- \`/factory-docs\` \u2014 documentation hub`,
|
|
4478
|
-
`- \`/factory-docs-generate\` \u2014 auto-generate documentation`,
|
|
4479
|
-
`- \`/factory-docs-manual\` \u2014 manual documentation workflow`,
|
|
4480
|
-
`- \`/factory-office\` \u2014 office mode discussion`,
|
|
4481
|
-
``
|
|
4482
|
-
].join("\n");
|
|
4483
|
-
}
|
|
4484
|
-
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
|
|
4310
|
+
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4485
4311
|
const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
|
|
4486
4312
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4487
4313
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4488
4314
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
4489
|
-
if (factoryInstalled) lines2.push(buildFactorySection(agent));
|
|
4490
4315
|
return lines2.join("\n");
|
|
4491
4316
|
}
|
|
4492
4317
|
|
|
@@ -4621,30 +4446,12 @@ var CORE_GITIGNORE_ENTRIES = [
|
|
|
4621
4446
|
".claude/plans/",
|
|
4622
4447
|
".claude/settings.local.json"
|
|
4623
4448
|
];
|
|
4624
|
-
var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
|
|
4625
|
-
"# Factory runtime state (generated by workflow, not committed)",
|
|
4626
|
-
"factory/STATE.md",
|
|
4627
|
-
"factory/PROJECT.md",
|
|
4628
|
-
"factory/REQUIREMENTS.md",
|
|
4629
|
-
"factory/ARCHITECTURE.md",
|
|
4630
|
-
"factory/ROADMAP.md",
|
|
4631
|
-
"factory/PLAN.md",
|
|
4632
|
-
"factory/GATE-REPORT.md",
|
|
4633
|
-
"factory/VERIFY-REPORT.md",
|
|
4634
|
-
"factory/COMPLETION.md",
|
|
4635
|
-
"factory/summaries/",
|
|
4636
|
-
"factory/ai-context/"
|
|
4637
|
-
];
|
|
4638
4449
|
var update_gitignore_default = defineStep({
|
|
4639
4450
|
name: "update-gitignore",
|
|
4640
4451
|
label: "Updating .gitignore",
|
|
4641
4452
|
execute: async (ctx) => {
|
|
4642
4453
|
const gitignorePath = (0, import_path10.join)(ctx.config.projectPath, ".gitignore");
|
|
4643
|
-
|
|
4644
|
-
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4645
|
-
const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
|
|
4646
|
-
const entries = includeLegacyFactory ? [...CORE_GITIGNORE_ENTRIES, "", ...LEGACY_FACTORY_GITIGNORE_ENTRIES] : CORE_GITIGNORE_ENTRIES;
|
|
4647
|
-
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, entries.join("\n"));
|
|
4454
|
+
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4648
4455
|
return { status: "success" };
|
|
4649
4456
|
}
|
|
4650
4457
|
});
|