dlw-machine-setup 0.5.18 → 0.6.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 +541 -416
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -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);
|
|
@@ -3577,6 +3595,31 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
|
|
|
3577
3595
|
return config;
|
|
3578
3596
|
}
|
|
3579
3597
|
|
|
3598
|
+
// src/utils/agent-targets.ts
|
|
3599
|
+
var AGENT_TARGETS = {
|
|
3600
|
+
"claude-code": {
|
|
3601
|
+
instructions: "CLAUDE.md",
|
|
3602
|
+
mcpConfig: ".mcp.json",
|
|
3603
|
+
mcpRootKey: "mcpServers",
|
|
3604
|
+
mcpDir: null
|
|
3605
|
+
},
|
|
3606
|
+
"github-copilot": {
|
|
3607
|
+
instructions: ".github/copilot-instructions.md",
|
|
3608
|
+
mcpConfig: ".vscode/mcp.json",
|
|
3609
|
+
mcpRootKey: "servers",
|
|
3610
|
+
mcpDir: ".vscode"
|
|
3611
|
+
},
|
|
3612
|
+
"cursor": {
|
|
3613
|
+
instructions: ".cursor/rules/instructions.mdc",
|
|
3614
|
+
mcpConfig: ".cursor/mcp.json",
|
|
3615
|
+
mcpRootKey: "mcpServers",
|
|
3616
|
+
mcpDir: ".cursor"
|
|
3617
|
+
}
|
|
3618
|
+
};
|
|
3619
|
+
function getAgentTarget(agent) {
|
|
3620
|
+
return AGENT_TARGETS[agent] ?? AGENT_TARGETS["claude-code"];
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3580
3623
|
// src/utils/mod.ts
|
|
3581
3624
|
async function loadWizardOptions(token, repo) {
|
|
3582
3625
|
const remote = await fetchWizardOptions(token, repo);
|
|
@@ -3646,10 +3689,86 @@ __export(steps_exports, {
|
|
|
3646
3689
|
});
|
|
3647
3690
|
|
|
3648
3691
|
// src/steps/resources/fetch-contexts.ts
|
|
3692
|
+
var import_fs4 = require("fs");
|
|
3693
|
+
var import_path4 = require("path");
|
|
3694
|
+
|
|
3695
|
+
// src/utils/fs-copy.ts
|
|
3649
3696
|
var import_fs2 = require("fs");
|
|
3650
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
|
+
var import_fs3 = require("fs");
|
|
3710
|
+
var import_path3 = require("path");
|
|
3651
3711
|
var import_child_process2 = require("child_process");
|
|
3652
3712
|
var MIN_FILE_SIZE = 1024;
|
|
3713
|
+
async function fetchLatestRelease(token, repo) {
|
|
3714
|
+
const headers = {
|
|
3715
|
+
"Accept": "application/vnd.github+json",
|
|
3716
|
+
"Authorization": `Bearer ${token}`
|
|
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;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
// src/steps/resources/fetch-contexts.ts
|
|
3653
3772
|
var fetch_contexts_default = defineStep({
|
|
3654
3773
|
name: "fetch-contexts",
|
|
3655
3774
|
label: "Downloading contexts",
|
|
@@ -3658,9 +3777,6 @@ var fetch_contexts_default = defineStep({
|
|
|
3658
3777
|
const uniqueDomains = [...new Set(ctx.config.technologies.flatMap((p) => p.domains))];
|
|
3659
3778
|
const domainValues = uniqueDomains.map((d) => d.toLowerCase());
|
|
3660
3779
|
if (!ctx.contextRepo) {
|
|
3661
|
-
for (const domain of domainValues) {
|
|
3662
|
-
ctx.installed.domainsFailed = domainValues;
|
|
3663
|
-
}
|
|
3664
3780
|
return {
|
|
3665
3781
|
status: "failed",
|
|
3666
3782
|
detail: "Context repo not found (topic: context-data)"
|
|
@@ -3668,8 +3784,6 @@ var fetch_contexts_default = defineStep({
|
|
|
3668
3784
|
}
|
|
3669
3785
|
const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
|
|
3670
3786
|
ctx.installed.domainsInstalled = downloadResult.successful;
|
|
3671
|
-
ctx.installed.domainsFailed = downloadResult.failed;
|
|
3672
|
-
ctx.installed.failureReasons = downloadResult.failureReasons;
|
|
3673
3787
|
ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
|
|
3674
3788
|
const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
|
|
3675
3789
|
console.log("");
|
|
@@ -3694,24 +3808,9 @@ var fetch_contexts_default = defineStep({
|
|
|
3694
3808
|
async function fetchContexts(domains, token, repo, targetDir) {
|
|
3695
3809
|
const result = { successful: [], failed: [], failureReasons: {} };
|
|
3696
3810
|
if (domains.length === 0) return result;
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
}
|
|
3701
|
-
const headers = {
|
|
3702
|
-
"Accept": "application/vnd.github+json",
|
|
3703
|
-
"Authorization": `Bearer ${token}`
|
|
3704
|
-
};
|
|
3705
|
-
const releaseResponse = await fetchWithRetry(
|
|
3706
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3707
|
-
{ headers }
|
|
3708
|
-
);
|
|
3709
|
-
if (!releaseResponse.ok) {
|
|
3710
|
-
throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
|
|
3711
|
-
}
|
|
3712
|
-
const releaseData = await releaseResponse.json();
|
|
3713
|
-
result.releaseVersion = releaseData.tag_name;
|
|
3714
|
-
const asset = releaseData.assets?.find((a) => a.name.endsWith(".tar.gz"));
|
|
3811
|
+
const release = await fetchLatestRelease(token, repo);
|
|
3812
|
+
result.releaseVersion = release.tagName;
|
|
3813
|
+
const asset = release.assets.find((a) => a.name.endsWith(".tar.gz"));
|
|
3715
3814
|
if (!asset) {
|
|
3716
3815
|
for (const domain of domains) {
|
|
3717
3816
|
result.failed.push(domain);
|
|
@@ -3719,39 +3818,11 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3719
3818
|
}
|
|
3720
3819
|
return result;
|
|
3721
3820
|
}
|
|
3722
|
-
const contextsDir = (0,
|
|
3723
|
-
|
|
3821
|
+
const contextsDir = (0, import_path4.join)(targetDir, "_ai-context");
|
|
3822
|
+
if (!(0, import_fs4.existsSync)(contextsDir)) (0, import_fs4.mkdirSync)(contextsDir, { recursive: true });
|
|
3823
|
+
const archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-download");
|
|
3724
3824
|
try {
|
|
3725
|
-
|
|
3726
|
-
if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
|
|
3727
|
-
(0, import_fs2.mkdirSync)(tempDir, { recursive: true });
|
|
3728
|
-
const downloadHeaders = {
|
|
3729
|
-
"Accept": "application/octet-stream",
|
|
3730
|
-
"Authorization": `Bearer ${token}`
|
|
3731
|
-
};
|
|
3732
|
-
const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
|
|
3733
|
-
if (!response.ok) {
|
|
3734
|
-
throw new Error(`Download failed: ${getReadableError(response.status)}`);
|
|
3735
|
-
}
|
|
3736
|
-
const archivePath = (0, import_path2.join)(tempDir, asset.name);
|
|
3737
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
3738
|
-
(0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
|
|
3739
|
-
const stats = (0, import_fs2.statSync)(archivePath);
|
|
3740
|
-
if (stats.size < MIN_FILE_SIZE) {
|
|
3741
|
-
throw new Error("File corrupted");
|
|
3742
|
-
}
|
|
3743
|
-
const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
3744
|
-
if (listResult.status !== 0) throw new Error("Failed to read archive contents");
|
|
3745
|
-
const resolvedTempDir = (0, import_path2.resolve)(tempDir);
|
|
3746
|
-
for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
|
|
3747
|
-
const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
|
|
3748
|
-
if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
|
|
3749
|
-
throw new Error(`Archive contains unsafe path: ${entry}`);
|
|
3750
|
-
}
|
|
3751
|
-
}
|
|
3752
|
-
const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3753
|
-
if (extractResult.status !== 0) throw new Error("Archive extraction failed");
|
|
3754
|
-
const extractedFolders = (0, import_fs2.readdirSync)(tempDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3825
|
+
const extractedFolders = (0, import_fs4.readdirSync)(archive.extractedRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3755
3826
|
for (const domain of domains) {
|
|
3756
3827
|
try {
|
|
3757
3828
|
const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
|
|
@@ -3760,8 +3831,8 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3760
3831
|
result.failureReasons[domain] = "Not found in archive";
|
|
3761
3832
|
continue;
|
|
3762
3833
|
}
|
|
3763
|
-
const domainPath = (0,
|
|
3764
|
-
copyDirectory((0,
|
|
3834
|
+
const domainPath = (0, import_path4.join)(contextsDir, domain);
|
|
3835
|
+
copyDirectory((0, import_path4.join)(archive.extractedRoot, match), domainPath);
|
|
3765
3836
|
result.successful.push(domain);
|
|
3766
3837
|
} catch (error) {
|
|
3767
3838
|
result.failed.push(domain);
|
|
@@ -3769,52 +3840,209 @@ async function fetchContexts(domains, token, repo, targetDir) {
|
|
|
3769
3840
|
}
|
|
3770
3841
|
}
|
|
3771
3842
|
} finally {
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3843
|
+
archive.cleanup();
|
|
3844
|
+
}
|
|
3845
|
+
return result;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
// src/steps/resources/fetch-factory.ts
|
|
3849
|
+
var import_fs7 = require("fs");
|
|
3850
|
+
var import_path7 = require("path");
|
|
3851
|
+
|
|
3852
|
+
// src/bundles/run-bundle.ts
|
|
3853
|
+
var import_fs6 = require("fs");
|
|
3854
|
+
var import_path6 = require("path");
|
|
3855
|
+
|
|
3856
|
+
// src/bundles/types.ts
|
|
3857
|
+
var SCHEMA_VERSION = 1;
|
|
3858
|
+
|
|
3859
|
+
// src/utils/marker-block.ts
|
|
3860
|
+
var import_fs5 = require("fs");
|
|
3861
|
+
var import_path5 = require("path");
|
|
3862
|
+
function upsertMarkerBlock(filePath, startMarker, endMarker, body, opts = {}) {
|
|
3863
|
+
const block = `${startMarker}
|
|
3864
|
+
${body}
|
|
3865
|
+
${endMarker}`;
|
|
3866
|
+
(0, import_fs5.mkdirSync)((0, import_path5.dirname)(filePath), { recursive: true });
|
|
3867
|
+
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
3868
|
+
(0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
3872
|
+
const s = existing.indexOf(startMarker);
|
|
3873
|
+
const e = existing.indexOf(endMarker);
|
|
3874
|
+
const hasS = s !== -1;
|
|
3875
|
+
const hasE = e !== -1;
|
|
3876
|
+
if (hasS && !hasE || !hasS && hasE || hasS && hasE && e < s) {
|
|
3877
|
+
opts.onCorrupt?.(filePath, startMarker, endMarker);
|
|
3878
|
+
}
|
|
3879
|
+
if (hasS && hasE && e > s) {
|
|
3880
|
+
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
3881
|
+
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
3882
|
+
} else {
|
|
3883
|
+
const sep3 = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
3884
|
+
(0, import_fs5.writeFileSync)(filePath, existing + sep3 + block + "\n", "utf-8");
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
// src/bundles/run-bundle.ts
|
|
3889
|
+
var OWNER_KEY = "_bundleOwner";
|
|
3890
|
+
async function runBundle(manifest, ctx) {
|
|
3891
|
+
assertSchemaCompatible(manifest);
|
|
3892
|
+
assertBundleRootExists(ctx);
|
|
3893
|
+
const result = {
|
|
3894
|
+
name: manifest.name,
|
|
3895
|
+
opsExecuted: 0,
|
|
3896
|
+
filesTouched: []
|
|
3897
|
+
};
|
|
3898
|
+
const ops = manifest.targets?.[ctx.agent] ?? [];
|
|
3899
|
+
for (const op of ops) {
|
|
3900
|
+
executeOp(op, manifest.name, ctx, result);
|
|
3901
|
+
result.opsExecuted++;
|
|
3902
|
+
}
|
|
3903
|
+
if (manifest.gitignore?.length) {
|
|
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
|
+
}
|
|
3912
|
+
const snippet = manifest.instructions?.[ctx.agent];
|
|
3913
|
+
if (snippet) {
|
|
3914
|
+
result.instructionsSnippet = snippet;
|
|
3915
|
+
if (!ctx.skipInstructions) {
|
|
3916
|
+
upsertMarkerBlock(
|
|
3917
|
+
(0, import_path6.join)(ctx.projectPath, ctx.instructionsFile),
|
|
3918
|
+
`<!-- ${manifest.name}:start -->`,
|
|
3919
|
+
`<!-- ${manifest.name}:end -->`,
|
|
3920
|
+
snippet
|
|
3921
|
+
);
|
|
3922
|
+
result.filesTouched.push(ctx.instructionsFile);
|
|
3777
3923
|
}
|
|
3778
3924
|
}
|
|
3779
3925
|
return result;
|
|
3780
3926
|
}
|
|
3781
|
-
function
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3927
|
+
function executeOp(op, owner, ctx, result) {
|
|
3928
|
+
switch (op.op) {
|
|
3929
|
+
case "copy":
|
|
3930
|
+
return runCopy(op, ctx, result);
|
|
3931
|
+
case "merge-json":
|
|
3932
|
+
return runMergeJson(op, owner, ctx, result);
|
|
3933
|
+
default: {
|
|
3934
|
+
const unknown = op.op;
|
|
3935
|
+
throw new Error(
|
|
3936
|
+
`Bundle "${owner}" uses unknown op "${unknown}". Installer supports: copy, merge-json. Upgrade the installer or check the bundle's schemaVersion.`
|
|
3937
|
+
);
|
|
3791
3938
|
}
|
|
3792
3939
|
}
|
|
3793
3940
|
}
|
|
3794
|
-
function
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3941
|
+
function runCopy(op, ctx, result) {
|
|
3942
|
+
const source = resolveBundlePath(op.from, ctx);
|
|
3943
|
+
const target = resolveProjectPath(op.to, ctx);
|
|
3944
|
+
if (!(0, import_fs6.existsSync)(source)) return;
|
|
3945
|
+
if ((0, import_fs6.statSync)(source).isDirectory()) {
|
|
3946
|
+
copyDirectory(source, target);
|
|
3947
|
+
} else {
|
|
3948
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
|
|
3949
|
+
(0, import_fs6.copyFileSync)(source, target);
|
|
3950
|
+
}
|
|
3951
|
+
result.filesTouched.push(op.to);
|
|
3952
|
+
}
|
|
3953
|
+
function runMergeJson(op, owner, ctx, result) {
|
|
3954
|
+
const filePath = resolveProjectPath(op.file, ctx);
|
|
3955
|
+
const existing = readJsonOrEmpty(filePath);
|
|
3956
|
+
const stripped = stripOwnedEntries(existing, owner);
|
|
3957
|
+
const tagged = tagEntries(op.patch, owner);
|
|
3958
|
+
const merged = deepMerge2(stripped, tagged);
|
|
3959
|
+
(0, import_fs6.mkdirSync)((0, import_path6.dirname)(filePath), { recursive: true });
|
|
3960
|
+
(0, import_fs6.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3961
|
+
result.filesTouched.push(op.file);
|
|
3962
|
+
}
|
|
3963
|
+
function readJsonOrEmpty(path) {
|
|
3964
|
+
if (!(0, import_fs6.existsSync)(path)) return {};
|
|
3965
|
+
try {
|
|
3966
|
+
return JSON.parse((0, import_fs6.readFileSync)(path, "utf-8"));
|
|
3967
|
+
} catch {
|
|
3968
|
+
return {};
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
function isPlainObject2(v) {
|
|
3972
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3973
|
+
}
|
|
3974
|
+
function tagEntries(value, owner) {
|
|
3975
|
+
if (Array.isArray(value)) {
|
|
3976
|
+
return value.map(
|
|
3977
|
+
(item) => isPlainObject2(item) ? { ...item, [OWNER_KEY]: owner } : item
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
if (isPlainObject2(value)) {
|
|
3981
|
+
return Object.fromEntries(
|
|
3982
|
+
Object.entries(value).map(([k, v]) => [k, tagEntries(v, owner)])
|
|
3983
|
+
);
|
|
3984
|
+
}
|
|
3985
|
+
return value;
|
|
3986
|
+
}
|
|
3987
|
+
function stripOwnedEntries(value, owner) {
|
|
3988
|
+
if (Array.isArray(value)) {
|
|
3989
|
+
return value.filter((item) => !(isPlainObject2(item) && item[OWNER_KEY] === owner)).map((item) => stripOwnedEntries(item, owner));
|
|
3990
|
+
}
|
|
3991
|
+
if (isPlainObject2(value)) {
|
|
3992
|
+
return Object.fromEntries(
|
|
3993
|
+
Object.entries(value).map(([k, v]) => [k, stripOwnedEntries(v, owner)])
|
|
3994
|
+
);
|
|
3995
|
+
}
|
|
3996
|
+
return value;
|
|
3997
|
+
}
|
|
3998
|
+
function deepMerge2(a, b) {
|
|
3999
|
+
if (isPlainObject2(a) && isPlainObject2(b)) {
|
|
4000
|
+
const out = { ...a };
|
|
4001
|
+
for (const [k, v] of Object.entries(b)) {
|
|
4002
|
+
out[k] = k in a ? deepMerge2(a[k], v) : v;
|
|
4003
|
+
}
|
|
4004
|
+
return out;
|
|
4005
|
+
}
|
|
4006
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
4007
|
+
return [...a, ...b];
|
|
4008
|
+
}
|
|
4009
|
+
return b;
|
|
4010
|
+
}
|
|
4011
|
+
function resolveBundlePath(rel, ctx) {
|
|
4012
|
+
const base = (0, import_path6.resolve)(ctx.bundleRoot);
|
|
4013
|
+
const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
|
|
4014
|
+
if (full !== base && !full.startsWith(base + import_path6.sep)) {
|
|
4015
|
+
throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
|
|
4016
|
+
}
|
|
4017
|
+
return full;
|
|
4018
|
+
}
|
|
4019
|
+
function resolveProjectPath(rel, ctx) {
|
|
4020
|
+
const base = (0, import_path6.resolve)(ctx.projectPath);
|
|
4021
|
+
const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
|
|
4022
|
+
if (full !== base && !full.startsWith(base + import_path6.sep)) {
|
|
4023
|
+
throw new Error(`Bundle op "to" escapes project path: ${rel}`);
|
|
4024
|
+
}
|
|
4025
|
+
return full;
|
|
4026
|
+
}
|
|
4027
|
+
function assertSchemaCompatible(manifest) {
|
|
4028
|
+
if (manifest.schemaVersion !== SCHEMA_VERSION) {
|
|
4029
|
+
throw new Error(
|
|
4030
|
+
`Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4033
|
+
if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
|
|
4034
|
+
throw new Error(
|
|
4035
|
+
`Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
function assertBundleRootExists(ctx) {
|
|
4040
|
+
if (!(0, import_fs6.existsSync)(ctx.bundleRoot) || !(0, import_fs6.statSync)(ctx.bundleRoot).isDirectory()) {
|
|
4041
|
+
throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
|
|
3810
4042
|
}
|
|
3811
4043
|
}
|
|
3812
4044
|
|
|
3813
4045
|
// src/steps/resources/fetch-factory.ts
|
|
3814
|
-
var import_fs3 = require("fs");
|
|
3815
|
-
var import_path3 = require("path");
|
|
3816
|
-
var import_child_process3 = require("child_process");
|
|
3817
|
-
var MIN_FILE_SIZE2 = 1024;
|
|
3818
4046
|
function getFactoryAsset(agent) {
|
|
3819
4047
|
switch (agent) {
|
|
3820
4048
|
case "github-copilot":
|
|
@@ -3830,83 +4058,48 @@ var fetch_factory_default = defineStep({
|
|
|
3830
4058
|
execute: async (ctx) => {
|
|
3831
4059
|
const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
|
|
3832
4060
|
ctx.installed.factoryInstalled = result.success;
|
|
4061
|
+
ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
|
|
4062
|
+
if (result.instructionsSnippet) {
|
|
4063
|
+
const stash = ctx.installed.bundleInstructions ?? {};
|
|
4064
|
+
stash["factory"] = result.instructionsSnippet;
|
|
4065
|
+
ctx.installed.bundleInstructions = stash;
|
|
4066
|
+
}
|
|
3833
4067
|
if (!result.success) {
|
|
3834
4068
|
return { status: "failed", detail: result.failureReason };
|
|
3835
4069
|
}
|
|
3836
|
-
|
|
4070
|
+
const tag = result.bundleManaged ? " (bundle)" : "";
|
|
4071
|
+
return { status: "success", message: result.filesInstalled.join(", ") + tag };
|
|
3837
4072
|
}
|
|
3838
4073
|
});
|
|
3839
4074
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
3840
4075
|
const result = { success: false, filesInstalled: [] };
|
|
3841
|
-
const tarCheck = (0, import_child_process3.spawnSync)("tar", ["--version"], { stdio: "ignore" });
|
|
3842
|
-
if (tarCheck.status !== 0) {
|
|
3843
|
-
result.failureReason = "tar command not found";
|
|
3844
|
-
return result;
|
|
3845
|
-
}
|
|
3846
4076
|
const { assetName, rootFolder } = getFactoryAsset(agent);
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
}
|
|
3851
|
-
|
|
3852
|
-
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
3853
|
-
{ headers }
|
|
3854
|
-
);
|
|
3855
|
-
if (!releaseResponse.ok) {
|
|
3856
|
-
result.failureReason = `GitHub API error (${releaseResponse.status})`;
|
|
4077
|
+
let release;
|
|
4078
|
+
try {
|
|
4079
|
+
release = await fetchLatestRelease(token, repo);
|
|
4080
|
+
} catch (err) {
|
|
4081
|
+
result.failureReason = err instanceof Error ? err.message : String(err);
|
|
3857
4082
|
return result;
|
|
3858
4083
|
}
|
|
3859
|
-
const
|
|
3860
|
-
const asset = releaseData.assets?.find((a) => a.name === assetName);
|
|
4084
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
3861
4085
|
if (!asset) {
|
|
3862
4086
|
result.failureReason = `${assetName} not found in release`;
|
|
3863
4087
|
return result;
|
|
3864
4088
|
}
|
|
3865
|
-
|
|
4089
|
+
let archive = null;
|
|
3866
4090
|
try {
|
|
3867
|
-
|
|
3868
|
-
(0,
|
|
3869
|
-
const response = await fetchWithRetry(asset.url, {
|
|
3870
|
-
headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
|
|
3871
|
-
});
|
|
3872
|
-
if (!response.ok) {
|
|
3873
|
-
result.failureReason = `Download failed (${response.status})`;
|
|
3874
|
-
return result;
|
|
3875
|
-
}
|
|
3876
|
-
const archivePath = (0, import_path3.join)(tempDir, assetName);
|
|
3877
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
3878
|
-
(0, import_fs3.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
|
|
3879
|
-
const stats = (0, import_fs3.statSync)(archivePath);
|
|
3880
|
-
if (stats.size < MIN_FILE_SIZE2) {
|
|
3881
|
-
result.failureReason = "Downloaded file corrupted (too small)";
|
|
3882
|
-
return result;
|
|
3883
|
-
}
|
|
3884
|
-
const listResult = (0, import_child_process3.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
|
|
3885
|
-
if (listResult.status !== 0) {
|
|
3886
|
-
result.failureReason = "Failed to read archive contents";
|
|
3887
|
-
return result;
|
|
3888
|
-
}
|
|
3889
|
-
const resolvedTempDir = (0, import_path3.resolve)(tempDir);
|
|
3890
|
-
for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
|
|
3891
|
-
const entryPath = (0, import_path3.resolve)((0, import_path3.join)(tempDir, entry.replace(/\/$/, "")));
|
|
3892
|
-
if (!entryPath.startsWith(resolvedTempDir + import_path3.sep)) {
|
|
3893
|
-
result.failureReason = `Archive contains unsafe path: ${entry}`;
|
|
3894
|
-
return result;
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
const extractResult = (0, import_child_process3.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
|
|
3898
|
-
if (extractResult.status !== 0) {
|
|
3899
|
-
result.failureReason = "Archive extraction failed";
|
|
3900
|
-
return result;
|
|
3901
|
-
}
|
|
3902
|
-
const extractedEntries = (0, import_fs3.readdirSync)(tempDir);
|
|
4091
|
+
archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
|
|
4092
|
+
const extractedEntries = (0, import_fs7.readdirSync)(archive.extractedRoot);
|
|
3903
4093
|
const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
|
|
3904
4094
|
if (!extractedFolder) {
|
|
3905
4095
|
result.failureReason = `No ${rootFolder}/ folder in archive`;
|
|
3906
4096
|
return result;
|
|
3907
4097
|
}
|
|
3908
|
-
const extractedPath = (0,
|
|
3909
|
-
|
|
4098
|
+
const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
|
|
4099
|
+
const manifestPath = (0, import_path7.join)(extractedPath, "install.json");
|
|
4100
|
+
if ((0, import_fs7.existsSync)(manifestPath)) {
|
|
4101
|
+
await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
|
|
4102
|
+
} else if (agent === "github-copilot") {
|
|
3910
4103
|
installCopilotFactory(extractedPath, targetDir, result);
|
|
3911
4104
|
} else {
|
|
3912
4105
|
installClaudeFactory(extractedPath, targetDir, result);
|
|
@@ -3915,53 +4108,68 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3915
4108
|
} catch (error) {
|
|
3916
4109
|
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
3917
4110
|
} finally {
|
|
3918
|
-
|
|
3919
|
-
try {
|
|
3920
|
-
(0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
|
|
3921
|
-
} catch {
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
4111
|
+
archive?.cleanup();
|
|
3924
4112
|
}
|
|
3925
4113
|
return result;
|
|
3926
4114
|
}
|
|
4115
|
+
async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
|
|
4116
|
+
let manifest;
|
|
4117
|
+
try {
|
|
4118
|
+
manifest = JSON.parse((0, import_fs7.readFileSync)(manifestPath, "utf-8"));
|
|
4119
|
+
} catch (e) {
|
|
4120
|
+
throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
4121
|
+
}
|
|
4122
|
+
const target = getAgentTarget(agent);
|
|
4123
|
+
const runResult = await runBundle(manifest, {
|
|
4124
|
+
bundleRoot: extractedPath,
|
|
4125
|
+
projectPath: targetDir,
|
|
4126
|
+
agent,
|
|
4127
|
+
instructionsFile: target.instructions,
|
|
4128
|
+
skipInstructions: true
|
|
4129
|
+
// write-instructions.ts commits the snippet for correct ordering
|
|
4130
|
+
});
|
|
4131
|
+
result.bundleManaged = true;
|
|
4132
|
+
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4133
|
+
result.filesInstalled = runResult.filesTouched;
|
|
4134
|
+
}
|
|
3927
4135
|
function installClaudeFactory(extractedPath, targetDir, result) {
|
|
3928
|
-
const claudeDir = (0,
|
|
3929
|
-
const factoryDir = (0,
|
|
3930
|
-
const agentsSrc = (0,
|
|
3931
|
-
if ((0,
|
|
3932
|
-
|
|
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"));
|
|
3933
4141
|
result.filesInstalled.push(".claude/agents/");
|
|
3934
4142
|
}
|
|
3935
|
-
const hooksSrc = (0,
|
|
3936
|
-
if ((0,
|
|
3937
|
-
|
|
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"));
|
|
3938
4146
|
result.filesInstalled.push(".claude/hooks/");
|
|
3939
4147
|
}
|
|
3940
|
-
const skillsSrc = (0,
|
|
3941
|
-
if ((0,
|
|
3942
|
-
|
|
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"));
|
|
3943
4151
|
result.filesInstalled.push(".claude/skills/");
|
|
3944
4152
|
}
|
|
3945
|
-
const workflowSrc = (0,
|
|
3946
|
-
if ((0,
|
|
3947
|
-
|
|
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"));
|
|
3948
4156
|
result.filesInstalled.push("factory/workflow/");
|
|
3949
4157
|
}
|
|
3950
|
-
const utilsSrc = (0,
|
|
3951
|
-
if ((0,
|
|
3952
|
-
|
|
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"));
|
|
3953
4161
|
result.filesInstalled.push("factory/utils/");
|
|
3954
4162
|
}
|
|
3955
|
-
const docsSrc = (0,
|
|
3956
|
-
if ((0,
|
|
3957
|
-
|
|
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"));
|
|
3958
4166
|
result.filesInstalled.push("factory/docs/");
|
|
3959
4167
|
}
|
|
3960
|
-
const rootEntries = (0,
|
|
4168
|
+
const rootEntries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
|
|
3961
4169
|
for (const entry of rootEntries) {
|
|
3962
4170
|
if (entry.isFile()) {
|
|
3963
|
-
if (!(0,
|
|
3964
|
-
(0,
|
|
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));
|
|
3965
4173
|
result.filesInstalled.push(`factory/${entry.name}`);
|
|
3966
4174
|
}
|
|
3967
4175
|
}
|
|
@@ -3969,9 +4177,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
|
|
|
3969
4177
|
result.filesInstalled.push(".claude/settings.json");
|
|
3970
4178
|
}
|
|
3971
4179
|
function installCopilotFactory(extractedPath, targetDir, result) {
|
|
3972
|
-
const factoryDir = (0,
|
|
3973
|
-
|
|
3974
|
-
const entries = (0,
|
|
4180
|
+
const factoryDir = (0, import_path7.join)(targetDir, "factory");
|
|
4181
|
+
copyDirectory(extractedPath, factoryDir);
|
|
4182
|
+
const entries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
|
|
3975
4183
|
for (const entry of entries) {
|
|
3976
4184
|
if (entry.isDirectory()) {
|
|
3977
4185
|
result.filesInstalled.push(`factory/${entry.name}/`);
|
|
@@ -3981,11 +4189,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
|
|
|
3981
4189
|
}
|
|
3982
4190
|
}
|
|
3983
4191
|
function configureSettings(claudeDir) {
|
|
3984
|
-
const settingsPath = (0,
|
|
4192
|
+
const settingsPath = (0, import_path7.join)(claudeDir, "settings.json");
|
|
3985
4193
|
let settings = {};
|
|
3986
|
-
if ((0,
|
|
4194
|
+
if ((0, import_fs7.existsSync)(settingsPath)) {
|
|
3987
4195
|
try {
|
|
3988
|
-
settings = JSON.parse((0,
|
|
4196
|
+
settings = JSON.parse((0, import_fs7.readFileSync)(settingsPath, "utf8"));
|
|
3989
4197
|
} catch {
|
|
3990
4198
|
}
|
|
3991
4199
|
}
|
|
@@ -4036,26 +4244,13 @@ function configureSettings(claudeDir) {
|
|
|
4036
4244
|
if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
|
|
4037
4245
|
}
|
|
4038
4246
|
settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
|
|
4039
|
-
if (!(0,
|
|
4040
|
-
(0,
|
|
4041
|
-
}
|
|
4042
|
-
function copyDirectory2(source, target) {
|
|
4043
|
-
if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
|
|
4044
|
-
const entries = (0, import_fs3.readdirSync)(source, { withFileTypes: true });
|
|
4045
|
-
for (const entry of entries) {
|
|
4046
|
-
const sourcePath = (0, import_path3.join)(source, entry.name);
|
|
4047
|
-
const targetPath = (0, import_path3.join)(target, entry.name);
|
|
4048
|
-
if (entry.isDirectory()) {
|
|
4049
|
-
copyDirectory2(sourcePath, targetPath);
|
|
4050
|
-
} else {
|
|
4051
|
-
(0, import_fs3.copyFileSync)(sourcePath, targetPath);
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
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");
|
|
4054
4249
|
}
|
|
4055
4250
|
|
|
4056
4251
|
// src/steps/setup/write-instructions.ts
|
|
4057
|
-
var
|
|
4058
|
-
var
|
|
4252
|
+
var import_fs8 = require("fs");
|
|
4253
|
+
var import_path8 = require("path");
|
|
4059
4254
|
|
|
4060
4255
|
// src/steps/shared.ts
|
|
4061
4256
|
function getFilteredMcpConfig(ctx) {
|
|
@@ -4079,106 +4274,71 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4079
4274
|
var write_instructions_default = defineStep({
|
|
4080
4275
|
name: "write-instructions",
|
|
4081
4276
|
label: "Writing instruction file",
|
|
4082
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled,
|
|
4277
|
+
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled || Object.keys(ctx.installed.bundleInstructions ?? {}).length > 0,
|
|
4083
4278
|
execute: async (ctx) => {
|
|
4084
4279
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4085
4280
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
4086
4281
|
const agent = ctx.config.agent;
|
|
4087
4282
|
const projectPath = ctx.config.projectPath;
|
|
4088
4283
|
const factoryInstalled = ctx.installed.factoryInstalled ?? false;
|
|
4089
|
-
const
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
upsertBlock((0, import_path4.join)(githubDir, "copilot-instructions.md"), content);
|
|
4099
|
-
break;
|
|
4100
|
-
}
|
|
4101
|
-
case "cursor": {
|
|
4102
|
-
const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
|
|
4103
|
-
if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
|
|
4104
|
-
const filePath = (0, import_path4.join)(cursorDir, "instructions.mdc");
|
|
4105
|
-
if (!(0, import_fs4.existsSync)(filePath)) {
|
|
4106
|
-
(0, import_fs4.writeFileSync)(filePath, `---
|
|
4284
|
+
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4285
|
+
const includeFactorySection = factoryInstalled && !factoryBundleManaged;
|
|
4286
|
+
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
|
|
4287
|
+
const target = getAgentTarget(agent);
|
|
4288
|
+
const filePath = (0, import_path8.join)(projectPath, target.instructions);
|
|
4289
|
+
const fileDir = (0, import_path8.dirname)(filePath);
|
|
4290
|
+
if (!(0, import_fs8.existsSync)(fileDir)) (0, import_fs8.mkdirSync)(fileDir, { recursive: true });
|
|
4291
|
+
if (agent === "cursor" && !(0, import_fs8.existsSync)(filePath)) {
|
|
4292
|
+
(0, import_fs8.writeFileSync)(filePath, `---
|
|
4107
4293
|
description: AI development instructions from One-Shot Installer
|
|
4108
4294
|
alwaysApply: true
|
|
4109
4295
|
---
|
|
4110
4296
|
|
|
4111
4297
|
`, "utf-8");
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4298
|
+
}
|
|
4299
|
+
upsertMarkerBlock(filePath, MARKER_START, MARKER_END, content, {
|
|
4300
|
+
onCorrupt: (file) => {
|
|
4301
|
+
const fileName = file.split(/[/\\]/).pop() ?? file;
|
|
4302
|
+
const box = [
|
|
4303
|
+
"",
|
|
4304
|
+
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"),
|
|
4305
|
+
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
4306
|
+
red(" \u2551 \u2551"),
|
|
4307
|
+
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
4308
|
+
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
4309
|
+
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
4310
|
+
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
4311
|
+
red(" \u2551 Then re-run the installer. \u2551"),
|
|
4312
|
+
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"),
|
|
4313
|
+
""
|
|
4314
|
+
].join("\n");
|
|
4315
|
+
console.log(box);
|
|
4316
|
+
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4115
4317
|
}
|
|
4318
|
+
});
|
|
4319
|
+
const bundleSnippets = ctx.installed.bundleInstructions ?? {};
|
|
4320
|
+
for (const bundleName of Object.keys(bundleSnippets).sort()) {
|
|
4321
|
+
upsertMarkerBlock(
|
|
4322
|
+
filePath,
|
|
4323
|
+
`<!-- ${bundleName}:start -->`,
|
|
4324
|
+
`<!-- ${bundleName}:end -->`,
|
|
4325
|
+
bundleSnippets[bundleName]
|
|
4326
|
+
);
|
|
4116
4327
|
}
|
|
4117
|
-
return { status: "success", message:
|
|
4328
|
+
return { status: "success", message: target.instructions };
|
|
4118
4329
|
}
|
|
4119
4330
|
});
|
|
4120
|
-
function getInstructionFilePath(agent) {
|
|
4121
|
-
switch (agent) {
|
|
4122
|
-
case "claude-code":
|
|
4123
|
-
return "CLAUDE.md";
|
|
4124
|
-
case "github-copilot":
|
|
4125
|
-
return ".github/copilot-instructions.md";
|
|
4126
|
-
case "cursor":
|
|
4127
|
-
return ".cursor/rules/instructions.mdc";
|
|
4128
|
-
default:
|
|
4129
|
-
return "CLAUDE.md";
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
function upsertBlock(filePath, block) {
|
|
4133
|
-
const marked = `${MARKER_START}
|
|
4134
|
-
${block}
|
|
4135
|
-
${MARKER_END}`;
|
|
4136
|
-
if (!(0, import_fs4.existsSync)(filePath)) {
|
|
4137
|
-
(0, import_fs4.writeFileSync)(filePath, marked, "utf-8");
|
|
4138
|
-
return;
|
|
4139
|
-
}
|
|
4140
|
-
const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
|
|
4141
|
-
const start = existing.indexOf(MARKER_START);
|
|
4142
|
-
const end = existing.indexOf(MARKER_END);
|
|
4143
|
-
const hasStart = start !== -1;
|
|
4144
|
-
const hasEnd = end !== -1;
|
|
4145
|
-
if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
|
|
4146
|
-
const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
4147
|
-
const box = [
|
|
4148
|
-
"",
|
|
4149
|
-
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"),
|
|
4150
|
-
red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
|
|
4151
|
-
red(" \u2551 \u2551"),
|
|
4152
|
-
red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
|
|
4153
|
-
red(" \u2551 Fix: delete the lines containing \u2551"),
|
|
4154
|
-
red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
|
|
4155
|
-
red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
|
|
4156
|
-
red(" \u2551 Then re-run the installer. \u2551"),
|
|
4157
|
-
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"),
|
|
4158
|
-
""
|
|
4159
|
-
].join("\n");
|
|
4160
|
-
console.log(box);
|
|
4161
|
-
throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
|
|
4162
|
-
}
|
|
4163
|
-
if (hasStart && hasEnd) {
|
|
4164
|
-
const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
|
|
4165
|
-
(0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
|
|
4166
|
-
} else {
|
|
4167
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4168
|
-
(0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
4331
|
function collectMdFiles(dir) {
|
|
4172
|
-
if (!(0,
|
|
4173
|
-
const entries = (0,
|
|
4332
|
+
if (!(0, import_fs8.existsSync)(dir)) return [];
|
|
4333
|
+
const entries = (0, import_fs8.readdirSync)(dir, { recursive: true });
|
|
4174
4334
|
return entries.filter((entry) => {
|
|
4175
|
-
const fullPath = (0,
|
|
4176
|
-
return entry.endsWith(".md") && (0,
|
|
4335
|
+
const fullPath = (0, import_path8.join)(dir, entry);
|
|
4336
|
+
return entry.endsWith(".md") && (0, import_fs8.statSync)(fullPath).isFile();
|
|
4177
4337
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4178
4338
|
}
|
|
4179
4339
|
function extractFirstHeading(filePath) {
|
|
4180
4340
|
try {
|
|
4181
|
-
const content = (0,
|
|
4341
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
4182
4342
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4183
4343
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4184
4344
|
if (match) {
|
|
@@ -4196,16 +4356,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4196
4356
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4197
4357
|
}
|
|
4198
4358
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4199
|
-
if ((0,
|
|
4359
|
+
if ((0, import_fs8.existsSync)(contextsDir)) {
|
|
4200
4360
|
try {
|
|
4201
|
-
const entries = (0,
|
|
4361
|
+
const entries = (0, import_fs8.readdirSync)(contextsDir);
|
|
4202
4362
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4203
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
4363
|
+
if (match) return { folderName: match, folderPath: (0, import_path8.join)(contextsDir, match) };
|
|
4204
4364
|
} catch {
|
|
4205
4365
|
}
|
|
4206
4366
|
}
|
|
4207
4367
|
const fallback = domain.toUpperCase();
|
|
4208
|
-
return { folderName: fallback, folderPath: (0,
|
|
4368
|
+
return { folderName: fallback, folderPath: (0, import_path8.join)(contextsDir, fallback) };
|
|
4209
4369
|
}
|
|
4210
4370
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4211
4371
|
const lines2 = [
|
|
@@ -4217,34 +4377,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4217
4377
|
let hasAnyFiles = false;
|
|
4218
4378
|
for (const domain of domains) {
|
|
4219
4379
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4220
|
-
if (!(0,
|
|
4380
|
+
if (!(0, import_fs8.existsSync)(domainPath)) continue;
|
|
4221
4381
|
const domainFiles = [];
|
|
4222
|
-
const ctxInstructions = (0,
|
|
4223
|
-
if ((0,
|
|
4382
|
+
const ctxInstructions = (0, import_path8.join)(domainPath, "context-instructions.md");
|
|
4383
|
+
if ((0, import_fs8.existsSync)(ctxInstructions)) {
|
|
4224
4384
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4225
4385
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4226
4386
|
}
|
|
4227
|
-
const instructionsMd = (0,
|
|
4228
|
-
if ((0,
|
|
4387
|
+
const instructionsMd = (0, import_path8.join)(domainPath, "core", "instructions.md");
|
|
4388
|
+
if ((0, import_fs8.existsSync)(instructionsMd)) {
|
|
4229
4389
|
const desc = extractFirstHeading(instructionsMd);
|
|
4230
4390
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4231
4391
|
}
|
|
4232
|
-
const coreDir = (0,
|
|
4233
|
-
if ((0,
|
|
4392
|
+
const coreDir = (0, import_path8.join)(domainPath, "core");
|
|
4393
|
+
if ((0, import_fs8.existsSync)(coreDir)) {
|
|
4234
4394
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4235
4395
|
for (const file of coreFiles) {
|
|
4236
|
-
const desc = extractFirstHeading((0,
|
|
4396
|
+
const desc = extractFirstHeading((0, import_path8.join)(coreDir, file));
|
|
4237
4397
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4238
4398
|
}
|
|
4239
4399
|
}
|
|
4240
|
-
const refDir = (0,
|
|
4241
|
-
if ((0,
|
|
4400
|
+
const refDir = (0, import_path8.join)(domainPath, "reference");
|
|
4401
|
+
if ((0, import_fs8.existsSync)(refDir)) {
|
|
4242
4402
|
const refFiles = collectMdFiles(refDir);
|
|
4243
4403
|
if (refFiles.length > 0) {
|
|
4244
4404
|
domainFiles.push(``);
|
|
4245
4405
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4246
4406
|
for (const file of refFiles) {
|
|
4247
|
-
const desc = extractFirstHeading((0,
|
|
4407
|
+
const desc = extractFirstHeading((0, import_path8.join)(refDir, file));
|
|
4248
4408
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4249
4409
|
}
|
|
4250
4410
|
}
|
|
@@ -4322,7 +4482,7 @@ function buildFactorySection(agent) {
|
|
|
4322
4482
|
].join("\n");
|
|
4323
4483
|
}
|
|
4324
4484
|
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
|
|
4325
|
-
const contextsDir = (0,
|
|
4485
|
+
const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
|
|
4326
4486
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4327
4487
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4328
4488
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
@@ -4331,8 +4491,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
|
|
|
4331
4491
|
}
|
|
4332
4492
|
|
|
4333
4493
|
// src/steps/setup/write-mcp-config.ts
|
|
4334
|
-
var
|
|
4335
|
-
var
|
|
4494
|
+
var import_fs9 = require("fs");
|
|
4495
|
+
var import_path9 = require("path");
|
|
4336
4496
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4337
4497
|
var write_mcp_config_default = defineStep({
|
|
4338
4498
|
name: "write-mcp-config",
|
|
@@ -4340,23 +4500,23 @@ var write_mcp_config_default = defineStep({
|
|
|
4340
4500
|
when: (ctx) => Object.keys(ctx.config.mcpConfig).length > 0,
|
|
4341
4501
|
execute: async (ctx) => {
|
|
4342
4502
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4343
|
-
const target =
|
|
4344
|
-
const mcpJsonPath = (0,
|
|
4345
|
-
if (target.
|
|
4346
|
-
const dir = (0,
|
|
4347
|
-
if (!(0,
|
|
4503
|
+
const target = getAgentTarget(ctx.config.agent);
|
|
4504
|
+
const mcpJsonPath = (0, import_path9.join)(ctx.config.projectPath, target.mcpConfig);
|
|
4505
|
+
if (target.mcpDir) {
|
|
4506
|
+
const dir = (0, import_path9.join)(ctx.config.projectPath, target.mcpDir);
|
|
4507
|
+
if (!(0, import_fs9.existsSync)(dir)) (0, import_fs9.mkdirSync)(dir, { recursive: true });
|
|
4348
4508
|
}
|
|
4349
4509
|
let existingFile = {};
|
|
4350
|
-
if ((0,
|
|
4510
|
+
if ((0, import_fs9.existsSync)(mcpJsonPath)) {
|
|
4351
4511
|
try {
|
|
4352
|
-
existingFile = JSON.parse((0,
|
|
4512
|
+
existingFile = JSON.parse((0, import_fs9.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4353
4513
|
} catch {
|
|
4354
4514
|
const box = [
|
|
4355
4515
|
"",
|
|
4356
4516
|
red2(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
|
|
4357
4517
|
red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
|
|
4358
4518
|
red2(" \u2551 \u2551"),
|
|
4359
|
-
red2(` \u2551 ${target.
|
|
4519
|
+
red2(` \u2551 ${target.mcpConfig} could not be parsed.`.padEnd(52) + "\u2551"),
|
|
4360
4520
|
red2(" \u2551 Existing MCP servers may be lost. \u2551"),
|
|
4361
4521
|
red2(" \u2551 Check the file after installation completes. \u2551"),
|
|
4362
4522
|
red2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
|
|
@@ -4366,7 +4526,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4366
4526
|
}
|
|
4367
4527
|
}
|
|
4368
4528
|
const addedServers = [];
|
|
4369
|
-
const existingServers = existingFile[target.
|
|
4529
|
+
const existingServers = existingFile[target.mcpRootKey] ?? {};
|
|
4370
4530
|
const newServers = {};
|
|
4371
4531
|
for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
|
|
4372
4532
|
const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
|
|
@@ -4374,8 +4534,8 @@ var write_mcp_config_default = defineStep({
|
|
|
4374
4534
|
addedServers.push(serverName);
|
|
4375
4535
|
}
|
|
4376
4536
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4377
|
-
const outputFile = { ...existingFile, [target.
|
|
4378
|
-
(0,
|
|
4537
|
+
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4538
|
+
(0, import_fs9.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4379
4539
|
ctx.installed.mcpServersAdded = addedServers;
|
|
4380
4540
|
return {
|
|
4381
4541
|
status: "success",
|
|
@@ -4383,20 +4543,9 @@ var write_mcp_config_default = defineStep({
|
|
|
4383
4543
|
};
|
|
4384
4544
|
}
|
|
4385
4545
|
});
|
|
4386
|
-
function getAgentMCPTarget(agent) {
|
|
4387
|
-
switch (agent) {
|
|
4388
|
-
case "claude-code":
|
|
4389
|
-
return { filePath: ".mcp.json", rootKey: "mcpServers", dir: null };
|
|
4390
|
-
case "cursor":
|
|
4391
|
-
return { filePath: ".cursor/mcp.json", rootKey: "mcpServers", dir: ".cursor" };
|
|
4392
|
-
case "github-copilot":
|
|
4393
|
-
default:
|
|
4394
|
-
return { filePath: ".vscode/mcp.json", rootKey: "servers", dir: ".vscode" };
|
|
4395
|
-
}
|
|
4396
|
-
}
|
|
4397
4546
|
|
|
4398
4547
|
// src/steps/setup/run-mcp-install-commands.ts
|
|
4399
|
-
var
|
|
4548
|
+
var import_child_process3 = require("child_process");
|
|
4400
4549
|
var run_mcp_install_commands_default = defineStep({
|
|
4401
4550
|
name: "run-mcp-install-commands",
|
|
4402
4551
|
label: "Registering MCP servers with Claude Code",
|
|
@@ -4418,7 +4567,7 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4418
4567
|
for (const [name, cfg] of Object.entries(filtered)) {
|
|
4419
4568
|
const cmd = cfg.installCommand;
|
|
4420
4569
|
if (typeof cmd !== "string" || cmd.length === 0) continue;
|
|
4421
|
-
const result = (0,
|
|
4570
|
+
const result = (0, import_child_process3.spawnSync)(cmd, {
|
|
4422
4571
|
shell: true,
|
|
4423
4572
|
stdio: "pipe",
|
|
4424
4573
|
encoding: "utf-8",
|
|
@@ -4432,7 +4581,6 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4432
4581
|
failed.push({ name, reason });
|
|
4433
4582
|
}
|
|
4434
4583
|
}
|
|
4435
|
-
ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
|
|
4436
4584
|
if (failed.length === 0) {
|
|
4437
4585
|
return {
|
|
4438
4586
|
status: "success",
|
|
@@ -4451,16 +4599,15 @@ var run_mcp_install_commands_default = defineStep({
|
|
|
4451
4599
|
}
|
|
4452
4600
|
});
|
|
4453
4601
|
function isClaudeCliAvailable() {
|
|
4454
|
-
const check2 = (0,
|
|
4602
|
+
const check2 = (0, import_child_process3.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
|
|
4455
4603
|
return check2.status === 0;
|
|
4456
4604
|
}
|
|
4457
4605
|
|
|
4458
4606
|
// src/steps/setup/update-gitignore.ts
|
|
4459
|
-
var
|
|
4460
|
-
var import_path6 = require("path");
|
|
4607
|
+
var import_path10 = require("path");
|
|
4461
4608
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4462
4609
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4463
|
-
var
|
|
4610
|
+
var CORE_GITIGNORE_ENTRIES = [
|
|
4464
4611
|
"# One-Shot installer generated files",
|
|
4465
4612
|
".one-shot-state.json",
|
|
4466
4613
|
".mcp.json",
|
|
@@ -4472,8 +4619,9 @@ var GITIGNORE_ENTRIES = [
|
|
|
4472
4619
|
".claude/agents/",
|
|
4473
4620
|
".claude/agent-memory/",
|
|
4474
4621
|
".claude/plans/",
|
|
4475
|
-
".claude/settings.local.json"
|
|
4476
|
-
|
|
4622
|
+
".claude/settings.local.json"
|
|
4623
|
+
];
|
|
4624
|
+
var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
|
|
4477
4625
|
"# Factory runtime state (generated by workflow, not committed)",
|
|
4478
4626
|
"factory/STATE.md",
|
|
4479
4627
|
"factory/PROJECT.md",
|
|
@@ -4491,43 +4639,60 @@ var update_gitignore_default = defineStep({
|
|
|
4491
4639
|
name: "update-gitignore",
|
|
4492
4640
|
label: "Updating .gitignore",
|
|
4493
4641
|
execute: async (ctx) => {
|
|
4494
|
-
const gitignorePath = (0,
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
|
|
4501
|
-
const start = existing.indexOf(MARKER_START2);
|
|
4502
|
-
const end = existing.indexOf(MARKER_END2);
|
|
4503
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
4504
|
-
const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
|
|
4505
|
-
(0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
|
|
4506
|
-
} else {
|
|
4507
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4508
|
-
(0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
|
|
4509
|
-
}
|
|
4642
|
+
const gitignorePath = (0, import_path10.join)(ctx.config.projectPath, ".gitignore");
|
|
4643
|
+
const factoryInstalled = ctx.installed.factoryInstalled ?? false;
|
|
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"));
|
|
4510
4648
|
return { status: "success" };
|
|
4511
4649
|
}
|
|
4512
4650
|
});
|
|
4513
4651
|
|
|
4514
4652
|
// src/steps/setup/write-state.ts
|
|
4515
|
-
var
|
|
4516
|
-
var
|
|
4653
|
+
var import_fs10 = require("fs");
|
|
4654
|
+
var import_path11 = require("path");
|
|
4517
4655
|
var import_os2 = require("os");
|
|
4518
|
-
|
|
4656
|
+
|
|
4657
|
+
// package.json
|
|
4658
|
+
var package_default = {
|
|
4659
|
+
name: "one-shot-setup",
|
|
4660
|
+
version: "0.6.0",
|
|
4661
|
+
description: "One-shot installation tool for The Machine toolchain",
|
|
4662
|
+
type: "module",
|
|
4663
|
+
main: "src/index.ts",
|
|
4664
|
+
scripts: {
|
|
4665
|
+
dev: "tsx src/index.ts",
|
|
4666
|
+
build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
|
|
4667
|
+
"publish:wrapper": "cd wrapper && npm publish"
|
|
4668
|
+
},
|
|
4669
|
+
dependencies: {
|
|
4670
|
+
"@inquirer/prompts": "^5.0.0"
|
|
4671
|
+
},
|
|
4672
|
+
devDependencies: {
|
|
4673
|
+
"@types/node": "^20.11.0",
|
|
4674
|
+
esbuild: "latest",
|
|
4675
|
+
tsx: "latest"
|
|
4676
|
+
},
|
|
4677
|
+
engines: {
|
|
4678
|
+
node: ">=18.0.0"
|
|
4679
|
+
}
|
|
4680
|
+
};
|
|
4681
|
+
|
|
4682
|
+
// src/steps/setup/write-state.ts
|
|
4519
4683
|
var write_state_default = defineStep({
|
|
4520
4684
|
name: "write-state",
|
|
4521
4685
|
label: "Saving installation state",
|
|
4522
4686
|
execute: async (ctx) => {
|
|
4523
|
-
const statePath = (0,
|
|
4687
|
+
const statePath = (0, import_path11.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4524
4688
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4525
4689
|
const filteredMcpConfig = Object.fromEntries(
|
|
4526
4690
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
4527
4691
|
);
|
|
4692
|
+
const target = getAgentTarget(ctx.config.agent);
|
|
4528
4693
|
const state = {
|
|
4529
4694
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4530
|
-
installerVersion:
|
|
4695
|
+
installerVersion: package_default.version,
|
|
4531
4696
|
releaseVersion: ctx.config.releaseVersion,
|
|
4532
4697
|
contextReleaseVersion: ctx.installed.contextReleaseVersion ?? null,
|
|
4533
4698
|
projectPath: ctx.config.projectPath,
|
|
@@ -4538,65 +4703,19 @@ var write_state_default = defineStep({
|
|
|
4538
4703
|
mcpConfigs: filteredMcpConfig,
|
|
4539
4704
|
factoryInstalled: ctx.installed.factoryInstalled ?? false,
|
|
4540
4705
|
files: {
|
|
4541
|
-
instructions:
|
|
4542
|
-
mcpConfig:
|
|
4706
|
+
instructions: target.instructions,
|
|
4707
|
+
mcpConfig: target.mcpConfig,
|
|
4543
4708
|
contexts: "_ai-context/",
|
|
4544
4709
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4545
|
-
globalConfig: (0,
|
|
4710
|
+
globalConfig: (0, import_path11.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4546
4711
|
}
|
|
4547
4712
|
};
|
|
4548
|
-
(0,
|
|
4713
|
+
(0, import_fs10.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4549
4714
|
return { status: "success" };
|
|
4550
4715
|
}
|
|
4551
4716
|
});
|
|
4552
|
-
function getInstructionFilePath2(agent) {
|
|
4553
|
-
switch (agent) {
|
|
4554
|
-
case "claude-code":
|
|
4555
|
-
return "CLAUDE.md";
|
|
4556
|
-
case "github-copilot":
|
|
4557
|
-
return ".github/copilot-instructions.md";
|
|
4558
|
-
case "cursor":
|
|
4559
|
-
return ".cursor/rules/instructions.mdc";
|
|
4560
|
-
default:
|
|
4561
|
-
return "CLAUDE.md";
|
|
4562
|
-
}
|
|
4563
|
-
}
|
|
4564
|
-
function getMCPConfigPath(agent) {
|
|
4565
|
-
switch (agent) {
|
|
4566
|
-
case "claude-code":
|
|
4567
|
-
return ".mcp.json";
|
|
4568
|
-
case "cursor":
|
|
4569
|
-
return ".cursor/mcp.json";
|
|
4570
|
-
case "github-copilot":
|
|
4571
|
-
default:
|
|
4572
|
-
return ".vscode/mcp.json";
|
|
4573
|
-
}
|
|
4574
|
-
}
|
|
4575
4717
|
|
|
4576
4718
|
// src/index.ts
|
|
4577
|
-
function getInstructionFilePath3(agent) {
|
|
4578
|
-
switch (agent) {
|
|
4579
|
-
case "claude-code":
|
|
4580
|
-
return "CLAUDE.md";
|
|
4581
|
-
case "github-copilot":
|
|
4582
|
-
return ".github/copilot-instructions.md";
|
|
4583
|
-
case "cursor":
|
|
4584
|
-
return ".cursor/rules/instructions.mdc";
|
|
4585
|
-
default:
|
|
4586
|
-
return "CLAUDE.md";
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
function getMCPConfigPath2(agent) {
|
|
4590
|
-
switch (agent) {
|
|
4591
|
-
case "claude-code":
|
|
4592
|
-
return ".mcp.json";
|
|
4593
|
-
case "cursor":
|
|
4594
|
-
return ".cursor/mcp.json";
|
|
4595
|
-
case "github-copilot":
|
|
4596
|
-
default:
|
|
4597
|
-
return ".vscode/mcp.json";
|
|
4598
|
-
}
|
|
4599
|
-
}
|
|
4600
4719
|
function formatMCPCommand(server) {
|
|
4601
4720
|
if (server.url) return server.url;
|
|
4602
4721
|
if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
|
|
@@ -4606,19 +4725,19 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
|
4606
4725
|
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
4607
4726
|
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4608
4727
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4609
|
-
if (!(0,
|
|
4610
|
-
const content = (0,
|
|
4728
|
+
if (!(0, import_fs11.existsSync)(filePath)) return green("create");
|
|
4729
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
4611
4730
|
if (content.includes(markerStart)) return yellow("update");
|
|
4612
4731
|
return yellow("append");
|
|
4613
4732
|
}
|
|
4614
4733
|
function detectMCPFileMode(filePath) {
|
|
4615
|
-
if (!(0,
|
|
4734
|
+
if (!(0, import_fs11.existsSync)(filePath)) return green("create");
|
|
4616
4735
|
return yellow("merge");
|
|
4617
4736
|
}
|
|
4618
4737
|
function detectContextMode(projectPath, domain) {
|
|
4619
|
-
const contextDir = (0,
|
|
4620
|
-
const contextDirLower = (0,
|
|
4621
|
-
if ((0,
|
|
4738
|
+
const contextDir = (0, import_path12.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
4739
|
+
const contextDirLower = (0, import_path12.join)(projectPath, "_ai-context", domain);
|
|
4740
|
+
if ((0, import_fs11.existsSync)(contextDir) || (0, import_fs11.existsSync)(contextDirLower)) return yellow("overwrite");
|
|
4622
4741
|
return green("create");
|
|
4623
4742
|
}
|
|
4624
4743
|
function waitForEnter() {
|
|
@@ -4716,13 +4835,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4716
4835
|
}
|
|
4717
4836
|
const projectInput = await esm_default4({
|
|
4718
4837
|
message: "Project directory:",
|
|
4719
|
-
default: (0,
|
|
4838
|
+
default: (0, import_path12.resolve)(process.cwd())
|
|
4720
4839
|
});
|
|
4721
4840
|
return {
|
|
4722
4841
|
technologies: selectedTechnologies,
|
|
4723
4842
|
agent,
|
|
4724
4843
|
azureDevOpsOrg,
|
|
4725
|
-
projectPath: (0,
|
|
4844
|
+
projectPath: (0, import_path12.resolve)(projectInput),
|
|
4726
4845
|
baseMcpServers: options.baseMcpServers,
|
|
4727
4846
|
mcpConfig,
|
|
4728
4847
|
releaseVersion,
|
|
@@ -4732,12 +4851,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4732
4851
|
async function previewAndConfirm(config, options) {
|
|
4733
4852
|
const technologyNames = config.technologies.length > 0 ? config.technologies.map((p) => p.name.replace(/ Developer$/, "")).join(", ") : "None";
|
|
4734
4853
|
const agentDisplay = options.agents.find((a) => a.value === config.agent)?.name ?? config.agent;
|
|
4735
|
-
const
|
|
4736
|
-
const
|
|
4854
|
+
const target = getAgentTarget(config.agent);
|
|
4855
|
+
const instructionFile = target.instructions;
|
|
4856
|
+
const mcpConfigFile = target.mcpConfig;
|
|
4737
4857
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
4738
|
-
const instructionFilePath = (0,
|
|
4739
|
-
const mcpConfigFilePath = (0,
|
|
4740
|
-
const gitignorePath = (0,
|
|
4858
|
+
const instructionFilePath = (0, import_path12.join)(config.projectPath, instructionFile);
|
|
4859
|
+
const mcpConfigFilePath = (0, import_path12.join)(config.projectPath, mcpConfigFile);
|
|
4860
|
+
const gitignorePath = (0, import_path12.join)(config.projectPath, ".gitignore");
|
|
4741
4861
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
4742
4862
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
4743
4863
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -4763,7 +4883,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4763
4883
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
4764
4884
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
4765
4885
|
if (config.installFactory) {
|
|
4766
|
-
const factoryExists = (0,
|
|
4886
|
+
const factoryExists = (0, import_fs11.existsSync)((0, import_path12.join)(config.projectPath, "factory"));
|
|
4767
4887
|
const factoryMode = factoryExists ? yellow("overwrite") : green("create");
|
|
4768
4888
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
4769
4889
|
}
|
|
@@ -4782,6 +4902,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4782
4902
|
function printSummary(result, config) {
|
|
4783
4903
|
const failed = result.entries.filter((e) => e.result.status === "failed");
|
|
4784
4904
|
const succeeded = result.entries.filter((e) => e.result.status === "success");
|
|
4905
|
+
const skipped = result.entries.filter((e) => e.result.status === "skipped");
|
|
4785
4906
|
const hasErrors = failed.length > 0;
|
|
4786
4907
|
console.log("\u2500".repeat(48));
|
|
4787
4908
|
console.log(hasErrors ? " Done (with errors).\n" : " Done.\n");
|
|
@@ -4789,6 +4910,10 @@ function printSummary(result, config) {
|
|
|
4789
4910
|
const msg = entry.result.message ? ` ${entry.result.message}` : "";
|
|
4790
4911
|
console.log(` ${entry.label}${msg}`);
|
|
4791
4912
|
}
|
|
4913
|
+
for (const entry of skipped) {
|
|
4914
|
+
const detail = entry.result.detail ? ` ${entry.result.detail}` : "";
|
|
4915
|
+
console.log(` ${entry.label} (skipped)${detail}`);
|
|
4916
|
+
}
|
|
4792
4917
|
if (hasErrors) {
|
|
4793
4918
|
console.log("\n What went wrong:");
|
|
4794
4919
|
for (const entry of failed) {
|