dlw-machine-setup 0.6.1 → 0.7.1
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 +352 -356
- 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)}`);
|
|
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)}`);
|
|
3337
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
|
|
|
@@ -3854,7 +3850,7 @@ var import_fs6 = require("fs");
|
|
|
3854
3850
|
var import_path6 = require("path");
|
|
3855
3851
|
|
|
3856
3852
|
// src/bundles/types.ts
|
|
3857
|
-
var
|
|
3853
|
+
var SUPPORTED_SCHEMA_VERSIONS = [1, 2];
|
|
3858
3854
|
|
|
3859
3855
|
// src/utils/marker-block.ts
|
|
3860
3856
|
var import_fs5 = require("fs");
|
|
@@ -3885,11 +3881,106 @@ ${endMarker}`;
|
|
|
3885
3881
|
}
|
|
3886
3882
|
}
|
|
3887
3883
|
|
|
3884
|
+
// src/profiles/claude-code.ts
|
|
3885
|
+
var claudeCodeProfile = {
|
|
3886
|
+
agent: "claude-code",
|
|
3887
|
+
instructionsFile: "CLAUDE.md",
|
|
3888
|
+
handlers: {
|
|
3889
|
+
agent: {
|
|
3890
|
+
supported: true,
|
|
3891
|
+
destination: (name) => `.claude/agents/${name}.md`
|
|
3892
|
+
},
|
|
3893
|
+
skill: {
|
|
3894
|
+
supported: true,
|
|
3895
|
+
// Claude reads skills from a folder per skill: `.claude/skills/<name>/SKILL.md`.
|
|
3896
|
+
destination: (name) => `.claude/skills/${name}/SKILL.md`
|
|
3897
|
+
},
|
|
3898
|
+
hook: {
|
|
3899
|
+
supported: true,
|
|
3900
|
+
configFile: ".claude/settings.json",
|
|
3901
|
+
configKey: "hooks",
|
|
3902
|
+
scriptDir: ".claude/hooks"
|
|
3903
|
+
},
|
|
3904
|
+
"instructions-snippet": {
|
|
3905
|
+
supported: true
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
// src/profiles/github-copilot.ts
|
|
3911
|
+
var githubCopilotProfile = {
|
|
3912
|
+
agent: "github-copilot",
|
|
3913
|
+
instructionsFile: ".github/copilot-instructions.md",
|
|
3914
|
+
handlers: {
|
|
3915
|
+
agent: {
|
|
3916
|
+
supported: true,
|
|
3917
|
+
// TODO(phase-3): verify destination path against Copilot CLI docs.
|
|
3918
|
+
destination: (name) => `.github/agents/${name}.md`
|
|
3919
|
+
// TODO(phase-3): supply frontmatter rewriter that maps Claude's
|
|
3920
|
+
// {name, description, disable-model-invocation, ...} to whatever
|
|
3921
|
+
// Copilot expects ({name, description, category, version, ...}).
|
|
3922
|
+
},
|
|
3923
|
+
skill: {
|
|
3924
|
+
supported: true,
|
|
3925
|
+
// TODO(phase-3): verify destination path against Copilot CLI docs.
|
|
3926
|
+
destination: (name) => `.github/skills/${name}.skill.md`
|
|
3927
|
+
// TODO(phase-3): supply frontmatter rewriter.
|
|
3928
|
+
},
|
|
3929
|
+
hook: {
|
|
3930
|
+
// TODO(phase-3): verify whether Copilot CLI has a hook primitive.
|
|
3931
|
+
// If yes, flip to supported: true and fill in configFile/configKey/scriptDir.
|
|
3932
|
+
// If no, leave as unsupported — assets of type 'hook' are silently skipped.
|
|
3933
|
+
supported: false
|
|
3934
|
+
},
|
|
3935
|
+
"instructions-snippet": {
|
|
3936
|
+
supported: true
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
|
|
3941
|
+
// src/profiles/cursor.ts
|
|
3942
|
+
var cursorProfile = {
|
|
3943
|
+
agent: "cursor",
|
|
3944
|
+
instructionsFile: ".cursor/rules/instructions.mdc",
|
|
3945
|
+
handlers: {
|
|
3946
|
+
agent: { supported: false },
|
|
3947
|
+
skill: { supported: false },
|
|
3948
|
+
hook: { supported: false },
|
|
3949
|
+
"instructions-snippet": { supported: true }
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3953
|
+
// src/profiles/index.ts
|
|
3954
|
+
var PROFILES = {
|
|
3955
|
+
"claude-code": claudeCodeProfile,
|
|
3956
|
+
"github-copilot": githubCopilotProfile,
|
|
3957
|
+
"cursor": cursorProfile
|
|
3958
|
+
};
|
|
3959
|
+
function getProfile(agent) {
|
|
3960
|
+
const profile = PROFILES[agent];
|
|
3961
|
+
if (!profile) {
|
|
3962
|
+
const known = Object.keys(PROFILES).map((k) => `"${k}"`).join(", ");
|
|
3963
|
+
throw new Error(
|
|
3964
|
+
`No profile registered for agent "${agent}". Known agents: ${known}. Add src/profiles/${agent}.ts and register it in src/profiles/index.ts.`
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
return profile;
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3888
3970
|
// src/bundles/run-bundle.ts
|
|
3889
3971
|
var OWNER_KEY = "_bundleOwner";
|
|
3890
3972
|
async function runBundle(manifest, ctx) {
|
|
3891
3973
|
assertSchemaCompatible(manifest);
|
|
3974
|
+
assertNameValid(manifest);
|
|
3892
3975
|
assertBundleRootExists(ctx);
|
|
3976
|
+
switch (manifest.schemaVersion) {
|
|
3977
|
+
case 1:
|
|
3978
|
+
return runBundleV1(manifest, ctx);
|
|
3979
|
+
case 2:
|
|
3980
|
+
return runBundleV2(manifest, ctx);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
function runBundleV1(manifest, ctx) {
|
|
3893
3984
|
const result = {
|
|
3894
3985
|
name: manifest.name,
|
|
3895
3986
|
opsExecuted: 0,
|
|
@@ -3900,30 +3991,120 @@ async function runBundle(manifest, ctx) {
|
|
|
3900
3991
|
executeOp(op, manifest.name, ctx, result);
|
|
3901
3992
|
result.opsExecuted++;
|
|
3902
3993
|
}
|
|
3903
|
-
|
|
3904
|
-
upsertMarkerBlock(
|
|
3905
|
-
(0, import_path6.join)(ctx.projectPath, ".gitignore"),
|
|
3906
|
-
`# ${manifest.name}:start`,
|
|
3907
|
-
`# ${manifest.name}:end`,
|
|
3908
|
-
manifest.gitignore.join("\n")
|
|
3909
|
-
);
|
|
3910
|
-
result.filesTouched.push(".gitignore");
|
|
3911
|
-
}
|
|
3994
|
+
writeGitignoreBlock(manifest, ctx, result);
|
|
3912
3995
|
const snippet = manifest.instructions?.[ctx.agent];
|
|
3913
3996
|
if (snippet) {
|
|
3914
3997
|
result.instructionsSnippet = snippet;
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3998
|
+
writeInstructionsBlock(manifest.name, snippet, ctx.instructionsFile, ctx, result);
|
|
3999
|
+
}
|
|
4000
|
+
return result;
|
|
4001
|
+
}
|
|
4002
|
+
function runBundleV2(manifest, ctx) {
|
|
4003
|
+
const profile = getProfile(ctx.agent);
|
|
4004
|
+
const result = {
|
|
4005
|
+
name: manifest.name,
|
|
4006
|
+
opsExecuted: 0,
|
|
4007
|
+
filesTouched: []
|
|
4008
|
+
};
|
|
4009
|
+
const hookPatches = /* @__PURE__ */ new Map();
|
|
4010
|
+
for (const asset of manifest.assets ?? []) {
|
|
4011
|
+
runAsset(asset, profile, hookPatches, ctx, result);
|
|
4012
|
+
result.opsExecuted++;
|
|
4013
|
+
}
|
|
4014
|
+
for (const [file, patch] of hookPatches) {
|
|
4015
|
+
runMergeJson({ op: "merge-json", file, patch }, manifest.name, ctx, result);
|
|
4016
|
+
}
|
|
4017
|
+
for (const op of manifest.ops ?? []) {
|
|
4018
|
+
executeOp(op, manifest.name, ctx, result);
|
|
4019
|
+
result.opsExecuted++;
|
|
4020
|
+
}
|
|
4021
|
+
writeGitignoreBlock(manifest, ctx, result);
|
|
4022
|
+
const snippet = result.instructionsSnippet;
|
|
4023
|
+
if (snippet) {
|
|
4024
|
+
const instructionsFile = profile.instructionsFile || ctx.instructionsFile;
|
|
4025
|
+
writeInstructionsBlock(manifest.name, snippet, instructionsFile, ctx, result);
|
|
3924
4026
|
}
|
|
3925
4027
|
return result;
|
|
3926
4028
|
}
|
|
4029
|
+
function runAsset(asset, profile, hookPatches, ctx, result) {
|
|
4030
|
+
switch (asset.type) {
|
|
4031
|
+
case "agent":
|
|
4032
|
+
runAssetCopy(asset, profile.handlers.agent, ctx, result);
|
|
4033
|
+
return;
|
|
4034
|
+
case "skill":
|
|
4035
|
+
runAssetCopy(asset, profile.handlers.skill, ctx, result);
|
|
4036
|
+
return;
|
|
4037
|
+
case "hook":
|
|
4038
|
+
accumulateHook(asset, profile.handlers.hook, hookPatches, ctx, result);
|
|
4039
|
+
return;
|
|
4040
|
+
case "instructions-snippet":
|
|
4041
|
+
if (profile.handlers["instructions-snippet"].supported) {
|
|
4042
|
+
result.instructionsSnippet = asset.content;
|
|
4043
|
+
}
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
function runAssetCopy(asset, handler, ctx, result) {
|
|
4048
|
+
if (!handler.supported || !handler.destination) return;
|
|
4049
|
+
const source = resolveBundlePath(asset.source, ctx);
|
|
4050
|
+
if (!(0, import_fs6.existsSync)(source)) return;
|
|
4051
|
+
const target = resolveProjectPath(handler.destination(asset.name), ctx);
|
|
4052
|
+
if ((0, import_fs6.statSync)(source).isDirectory()) {
|
|
4053
|
+
copyDirectory(source, target);
|
|
4054
|
+
} else {
|
|
4055
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
|
|
4056
|
+
(0, import_fs6.copyFileSync)(source, target);
|
|
4057
|
+
}
|
|
4058
|
+
result.filesTouched.push(handler.destination(asset.name));
|
|
4059
|
+
}
|
|
4060
|
+
function accumulateHook(asset, handler, hookPatches, ctx, result) {
|
|
4061
|
+
if (!handler.supported) return;
|
|
4062
|
+
if (!handler.configFile || !handler.configKey) return;
|
|
4063
|
+
if (asset.script && handler.scriptDir) {
|
|
4064
|
+
const scriptSource = resolveBundlePath(asset.script, ctx);
|
|
4065
|
+
if ((0, import_fs6.existsSync)(scriptSource)) {
|
|
4066
|
+
const scriptName = (0, import_path6.basename)(asset.script);
|
|
4067
|
+
const scriptRelDest = `${handler.scriptDir}/${scriptName}`;
|
|
4068
|
+
const scriptDest = resolveProjectPath(scriptRelDest, ctx);
|
|
4069
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(scriptDest), { recursive: true });
|
|
4070
|
+
(0, import_fs6.copyFileSync)(scriptSource, scriptDest);
|
|
4071
|
+
result.filesTouched.push(scriptRelDest);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
const command = handler.scriptDir ? asset.command.replace(/\{hookDir\}/g, handler.scriptDir) : asset.command;
|
|
4075
|
+
const entry = {
|
|
4076
|
+
hooks: [{ type: "command", command }]
|
|
4077
|
+
};
|
|
4078
|
+
if (asset.matcher) entry.matcher = asset.matcher;
|
|
4079
|
+
let patch = hookPatches.get(handler.configFile);
|
|
4080
|
+
if (!patch) {
|
|
4081
|
+
patch = {};
|
|
4082
|
+
hookPatches.set(handler.configFile, patch);
|
|
4083
|
+
}
|
|
4084
|
+
const root = patch[handler.configKey] ??= {};
|
|
4085
|
+
const events = root[asset.event] ??= [];
|
|
4086
|
+
events.push(entry);
|
|
4087
|
+
}
|
|
4088
|
+
function writeGitignoreBlock(manifest, ctx, result) {
|
|
4089
|
+
if (!manifest.gitignore?.length) return;
|
|
4090
|
+
upsertMarkerBlock(
|
|
4091
|
+
(0, import_path6.join)(ctx.projectPath, ".gitignore"),
|
|
4092
|
+
`# ${manifest.name}:start`,
|
|
4093
|
+
`# ${manifest.name}:end`,
|
|
4094
|
+
manifest.gitignore.join("\n")
|
|
4095
|
+
);
|
|
4096
|
+
result.filesTouched.push(".gitignore");
|
|
4097
|
+
}
|
|
4098
|
+
function writeInstructionsBlock(bundleName, snippet, instructionsFile, ctx, result) {
|
|
4099
|
+
if (ctx.skipInstructions) return;
|
|
4100
|
+
upsertMarkerBlock(
|
|
4101
|
+
(0, import_path6.join)(ctx.projectPath, instructionsFile),
|
|
4102
|
+
`<!-- ${bundleName}:start -->`,
|
|
4103
|
+
`<!-- ${bundleName}:end -->`,
|
|
4104
|
+
snippet
|
|
4105
|
+
);
|
|
4106
|
+
result.filesTouched.push(instructionsFile);
|
|
4107
|
+
}
|
|
3927
4108
|
function executeOp(op, owner, ctx, result) {
|
|
3928
4109
|
switch (op.op) {
|
|
3929
4110
|
case "copy":
|
|
@@ -4025,11 +4206,14 @@ function resolveProjectPath(rel, ctx) {
|
|
|
4025
4206
|
return full;
|
|
4026
4207
|
}
|
|
4027
4208
|
function assertSchemaCompatible(manifest) {
|
|
4028
|
-
if (manifest.schemaVersion
|
|
4209
|
+
if (!SUPPORTED_SCHEMA_VERSIONS.includes(manifest.schemaVersion)) {
|
|
4210
|
+
const supported = SUPPORTED_SCHEMA_VERSIONS.join(", ");
|
|
4029
4211
|
throw new Error(
|
|
4030
|
-
`Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${
|
|
4212
|
+
`Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${supported}. Upgrade the installer or re-pin the bundle.`
|
|
4031
4213
|
);
|
|
4032
4214
|
}
|
|
4215
|
+
}
|
|
4216
|
+
function assertNameValid(manifest) {
|
|
4033
4217
|
if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
|
|
4034
4218
|
throw new Error(
|
|
4035
4219
|
`Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
|
|
@@ -4058,17 +4242,13 @@ var fetch_factory_default = defineStep({
|
|
|
4058
4242
|
execute: async (ctx) => {
|
|
4059
4243
|
const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
|
|
4060
4244
|
ctx.installed.factoryInstalled = result.success;
|
|
4061
|
-
ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
|
|
4062
4245
|
if (result.instructionsSnippet) {
|
|
4063
|
-
|
|
4064
|
-
stash["factory"] = result.instructionsSnippet;
|
|
4065
|
-
ctx.installed.bundleInstructions = stash;
|
|
4246
|
+
ctx.installed.factoryInstructionsSnippet = result.instructionsSnippet;
|
|
4066
4247
|
}
|
|
4067
4248
|
if (!result.success) {
|
|
4068
4249
|
return { status: "failed", detail: result.failureReason };
|
|
4069
4250
|
}
|
|
4070
|
-
|
|
4071
|
-
return { status: "success", message: result.filesInstalled.join(", ") + tag };
|
|
4251
|
+
return { status: "success", message: result.filesInstalled.join(", ") };
|
|
4072
4252
|
}
|
|
4073
4253
|
});
|
|
4074
4254
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
@@ -4097,13 +4277,11 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
4097
4277
|
}
|
|
4098
4278
|
const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
|
|
4099
4279
|
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);
|
|
4280
|
+
if (!(0, import_fs7.existsSync)(manifestPath)) {
|
|
4281
|
+
result.failureReason = "Factory archive has no install.json \u2014 upgrade Factory or downgrade installer";
|
|
4282
|
+
return result;
|
|
4106
4283
|
}
|
|
4284
|
+
await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
|
|
4107
4285
|
result.success = true;
|
|
4108
4286
|
} catch (error) {
|
|
4109
4287
|
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
@@ -4128,132 +4306,18 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
|
|
|
4128
4306
|
skipInstructions: true
|
|
4129
4307
|
// write-instructions.ts commits the snippet for correct ordering
|
|
4130
4308
|
});
|
|
4131
|
-
result.bundleManaged = true;
|
|
4132
4309
|
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4133
4310
|
result.filesInstalled = runResult.filesTouched;
|
|
4134
4311
|
}
|
|
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
4312
|
|
|
4251
4313
|
// src/steps/setup/write-instructions.ts
|
|
4252
4314
|
var import_fs8 = require("fs");
|
|
4253
4315
|
var import_path8 = require("path");
|
|
4254
4316
|
|
|
4255
4317
|
// src/steps/shared.ts
|
|
4318
|
+
var cached = null;
|
|
4256
4319
|
function getFilteredMcpConfig(ctx) {
|
|
4320
|
+
if (cached) return cached;
|
|
4257
4321
|
const successfulDomains = new Set(ctx.installed.domainsInstalled ?? []);
|
|
4258
4322
|
const successfulTechnologies = ctx.config.technologies.filter(
|
|
4259
4323
|
(p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
|
|
@@ -4262,9 +4326,10 @@ function getFilteredMcpConfig(ctx) {
|
|
|
4262
4326
|
...ctx.config.baseMcpServers,
|
|
4263
4327
|
...successfulTechnologies.flatMap((p) => p.mcpServers)
|
|
4264
4328
|
]);
|
|
4265
|
-
|
|
4329
|
+
cached = Object.fromEntries(
|
|
4266
4330
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
|
|
4267
4331
|
);
|
|
4332
|
+
return cached;
|
|
4268
4333
|
}
|
|
4269
4334
|
|
|
4270
4335
|
// src/steps/setup/write-instructions.ts
|
|
@@ -4274,16 +4339,13 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4274
4339
|
var write_instructions_default = defineStep({
|
|
4275
4340
|
name: "write-instructions",
|
|
4276
4341
|
label: "Writing instruction file",
|
|
4277
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.
|
|
4342
|
+
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || !!ctx.installed.factoryInstructionsSnippet,
|
|
4278
4343
|
execute: async (ctx) => {
|
|
4279
4344
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4280
4345
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
4281
4346
|
const agent = ctx.config.agent;
|
|
4282
4347
|
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);
|
|
4348
|
+
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath);
|
|
4287
4349
|
const target = getAgentTarget(agent);
|
|
4288
4350
|
const filePath = (0, import_path8.join)(projectPath, target.instructions);
|
|
4289
4351
|
const fileDir = (0, import_path8.dirname)(filePath);
|
|
@@ -4316,13 +4378,12 @@ alwaysApply: true
|
|
|
4316
4378
|
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4317
4379
|
}
|
|
4318
4380
|
});
|
|
4319
|
-
|
|
4320
|
-
for (const bundleName of Object.keys(bundleSnippets).sort()) {
|
|
4381
|
+
if (ctx.installed.factoryInstructionsSnippet) {
|
|
4321
4382
|
upsertMarkerBlock(
|
|
4322
4383
|
filePath,
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4384
|
+
"<!-- factory:start -->",
|
|
4385
|
+
"<!-- factory:end -->",
|
|
4386
|
+
ctx.installed.factoryInstructionsSnippet
|
|
4326
4387
|
);
|
|
4327
4388
|
}
|
|
4328
4389
|
return { status: "success", message: target.instructions };
|
|
@@ -4346,8 +4407,8 @@ function extractFirstHeading(filePath) {
|
|
|
4346
4407
|
}
|
|
4347
4408
|
} catch {
|
|
4348
4409
|
}
|
|
4349
|
-
const
|
|
4350
|
-
return
|
|
4410
|
+
const basename2 = filePath.split(/[/\\]/).pop() ?? "";
|
|
4411
|
+
return basename2.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
|
|
4351
4412
|
}
|
|
4352
4413
|
function formatPathRef(contextPath, description, agent) {
|
|
4353
4414
|
if (agent === "github-copilot") {
|
|
@@ -4426,67 +4487,19 @@ function buildMCPSection(mcpConfig) {
|
|
|
4426
4487
|
if (entries.length === 0) return "";
|
|
4427
4488
|
const lines2 = [`## MCP Servers`, ``, `The following MCP servers are available. Use them proactively when relevant:`, ``];
|
|
4428
4489
|
for (const [name, config] of entries) {
|
|
4429
|
-
|
|
4430
|
-
const useWhen = config.useWhen;
|
|
4431
|
-
if (!description) continue;
|
|
4490
|
+
if (!config.description) continue;
|
|
4432
4491
|
lines2.push(`### ${name}`);
|
|
4433
|
-
lines2.push(description);
|
|
4434
|
-
if (useWhen) lines2.push(`**Use when**: ${useWhen}`);
|
|
4492
|
+
lines2.push(config.description);
|
|
4493
|
+
if (config.useWhen) lines2.push(`**Use when**: ${config.useWhen}`);
|
|
4435
4494
|
lines2.push(``);
|
|
4436
4495
|
}
|
|
4437
4496
|
return lines2.join("\n");
|
|
4438
4497
|
}
|
|
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) {
|
|
4498
|
+
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath) {
|
|
4485
4499
|
const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
|
|
4486
4500
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4487
4501
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4488
4502
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
4489
|
-
if (factoryInstalled) lines2.push(buildFactorySection(agent));
|
|
4490
4503
|
return lines2.join("\n");
|
|
4491
4504
|
}
|
|
4492
4505
|
|
|
@@ -4621,30 +4634,12 @@ var CORE_GITIGNORE_ENTRIES = [
|
|
|
4621
4634
|
".claude/plans/",
|
|
4622
4635
|
".claude/settings.local.json"
|
|
4623
4636
|
];
|
|
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
4637
|
var update_gitignore_default = defineStep({
|
|
4639
4638
|
name: "update-gitignore",
|
|
4640
4639
|
label: "Updating .gitignore",
|
|
4641
4640
|
execute: async (ctx) => {
|
|
4642
4641
|
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"));
|
|
4642
|
+
upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, CORE_GITIGNORE_ENTRIES.join("\n"));
|
|
4648
4643
|
return { status: "success" };
|
|
4649
4644
|
}
|
|
4650
4645
|
});
|
|
@@ -4664,6 +4659,7 @@ var package_default = {
|
|
|
4664
4659
|
scripts: {
|
|
4665
4660
|
dev: "tsx src/index.ts",
|
|
4666
4661
|
build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
|
|
4662
|
+
"build:all": "npm run build",
|
|
4667
4663
|
"publish:wrapper": "cd wrapper && npm publish"
|
|
4668
4664
|
},
|
|
4669
4665
|
dependencies: {
|