dlw-machine-setup 0.5.18 → 0.6.0
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 +434 -233
- package/package.json +1 -1
package/bin/installer.js
CHANGED
|
@@ -2753,13 +2753,13 @@ var PromisePolyfill = class extends Promise {
|
|
|
2753
2753
|
// Available starting from Node 22
|
|
2754
2754
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
|
|
2755
2755
|
static withResolver() {
|
|
2756
|
-
let
|
|
2756
|
+
let resolve5;
|
|
2757
2757
|
let reject;
|
|
2758
2758
|
const promise = new Promise((res, rej) => {
|
|
2759
|
-
|
|
2759
|
+
resolve5 = res;
|
|
2760
2760
|
reject = rej;
|
|
2761
2761
|
});
|
|
2762
|
-
return { promise, resolve:
|
|
2762
|
+
return { promise, resolve: resolve5, reject };
|
|
2763
2763
|
}
|
|
2764
2764
|
};
|
|
2765
2765
|
|
|
@@ -2776,7 +2776,7 @@ function createPrompt(view) {
|
|
|
2776
2776
|
output
|
|
2777
2777
|
});
|
|
2778
2778
|
const screen = new ScreenManager(rl);
|
|
2779
|
-
const { promise, resolve:
|
|
2779
|
+
const { promise, resolve: resolve5, reject } = PromisePolyfill.withResolver();
|
|
2780
2780
|
const cancel = () => reject(new CancelPromptError());
|
|
2781
2781
|
if (signal) {
|
|
2782
2782
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
|
@@ -2800,7 +2800,7 @@ function createPrompt(view) {
|
|
|
2800
2800
|
cycle(() => {
|
|
2801
2801
|
try {
|
|
2802
2802
|
const nextView = view(config, (value) => {
|
|
2803
|
-
setImmediate(() =>
|
|
2803
|
+
setImmediate(() => resolve5(value));
|
|
2804
2804
|
});
|
|
2805
2805
|
const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
|
|
2806
2806
|
screen.render(content, bottomContent);
|
|
@@ -3244,9 +3244,9 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
|
|
|
3244
3244
|
});
|
|
3245
3245
|
|
|
3246
3246
|
// src/index.ts
|
|
3247
|
-
var
|
|
3247
|
+
var import_fs9 = require("fs");
|
|
3248
3248
|
var import_readline = require("readline");
|
|
3249
|
-
var
|
|
3249
|
+
var import_path9 = require("path");
|
|
3250
3250
|
|
|
3251
3251
|
// src/utils/fetch.ts
|
|
3252
3252
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -3283,7 +3283,7 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
|
|
|
3283
3283
|
}
|
|
3284
3284
|
if (attempt < maxRetries) {
|
|
3285
3285
|
const delay = retryDelayMs * Math.pow(2, attempt - 1);
|
|
3286
|
-
await new Promise((
|
|
3286
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
3287
3287
|
}
|
|
3288
3288
|
}
|
|
3289
3289
|
throw lastError || new Error("Failed after maximum retries");
|
|
@@ -3451,7 +3451,7 @@ async function pollForToken(deviceCode, interval) {
|
|
|
3451
3451
|
const maxAttempts = 60;
|
|
3452
3452
|
let attempts = 0;
|
|
3453
3453
|
while (attempts < maxAttempts) {
|
|
3454
|
-
await new Promise((
|
|
3454
|
+
await new Promise((resolve5) => setTimeout(resolve5, interval * 1e3));
|
|
3455
3455
|
const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
|
|
3456
3456
|
method: "POST",
|
|
3457
3457
|
headers: {
|
|
@@ -3577,6 +3577,31 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
|
|
|
3577
3577
|
return config;
|
|
3578
3578
|
}
|
|
3579
3579
|
|
|
3580
|
+
// src/utils/agent-targets.ts
|
|
3581
|
+
var AGENT_TARGETS = {
|
|
3582
|
+
"claude-code": {
|
|
3583
|
+
instructions: "CLAUDE.md",
|
|
3584
|
+
mcpConfig: ".mcp.json",
|
|
3585
|
+
mcpRootKey: "mcpServers",
|
|
3586
|
+
mcpDir: null
|
|
3587
|
+
},
|
|
3588
|
+
"github-copilot": {
|
|
3589
|
+
instructions: ".github/copilot-instructions.md",
|
|
3590
|
+
mcpConfig: ".vscode/mcp.json",
|
|
3591
|
+
mcpRootKey: "servers",
|
|
3592
|
+
mcpDir: ".vscode"
|
|
3593
|
+
},
|
|
3594
|
+
"cursor": {
|
|
3595
|
+
instructions: ".cursor/rules/instructions.mdc",
|
|
3596
|
+
mcpConfig: ".cursor/mcp.json",
|
|
3597
|
+
mcpRootKey: "mcpServers",
|
|
3598
|
+
mcpDir: ".cursor"
|
|
3599
|
+
}
|
|
3600
|
+
};
|
|
3601
|
+
function getAgentTarget(agent) {
|
|
3602
|
+
return AGENT_TARGETS[agent] ?? AGENT_TARGETS["claude-code"];
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3580
3605
|
// src/utils/mod.ts
|
|
3581
3606
|
async function loadWizardOptions(token, repo) {
|
|
3582
3607
|
const remote = await fetchWizardOptions(token, repo);
|
|
@@ -3811,9 +3836,204 @@ function getReadableError(status) {
|
|
|
3811
3836
|
}
|
|
3812
3837
|
|
|
3813
3838
|
// src/steps/resources/fetch-factory.ts
|
|
3839
|
+
var import_fs4 = require("fs");
|
|
3840
|
+
var import_path4 = require("path");
|
|
3841
|
+
var import_child_process3 = require("child_process");
|
|
3842
|
+
|
|
3843
|
+
// src/bundles/run-bundle.ts
|
|
3814
3844
|
var import_fs3 = require("fs");
|
|
3815
3845
|
var import_path3 = require("path");
|
|
3816
|
-
|
|
3846
|
+
|
|
3847
|
+
// src/bundles/types.ts
|
|
3848
|
+
var SCHEMA_VERSION = 1;
|
|
3849
|
+
|
|
3850
|
+
// src/bundles/run-bundle.ts
|
|
3851
|
+
var OWNER_KEY = "_bundleOwner";
|
|
3852
|
+
async function runBundle(manifest, ctx) {
|
|
3853
|
+
assertSchemaCompatible(manifest);
|
|
3854
|
+
assertBundleRootExists(ctx);
|
|
3855
|
+
const result = {
|
|
3856
|
+
name: manifest.name,
|
|
3857
|
+
opsExecuted: 0,
|
|
3858
|
+
filesTouched: []
|
|
3859
|
+
};
|
|
3860
|
+
const ops = manifest.targets?.[ctx.agent] ?? [];
|
|
3861
|
+
for (const op of ops) {
|
|
3862
|
+
executeOp(op, manifest.name, ctx, result);
|
|
3863
|
+
result.opsExecuted++;
|
|
3864
|
+
}
|
|
3865
|
+
if (manifest.gitignore?.length) {
|
|
3866
|
+
upsertMarkerBlock(
|
|
3867
|
+
(0, import_path3.join)(ctx.projectPath, ".gitignore"),
|
|
3868
|
+
`# ${manifest.name}:start`,
|
|
3869
|
+
`# ${manifest.name}:end`,
|
|
3870
|
+
manifest.gitignore.join("\n")
|
|
3871
|
+
);
|
|
3872
|
+
result.filesTouched.push(".gitignore");
|
|
3873
|
+
}
|
|
3874
|
+
const snippet = manifest.instructions?.[ctx.agent];
|
|
3875
|
+
if (snippet) {
|
|
3876
|
+
result.instructionsSnippet = snippet;
|
|
3877
|
+
if (!ctx.skipInstructions) {
|
|
3878
|
+
upsertMarkerBlock(
|
|
3879
|
+
(0, import_path3.join)(ctx.projectPath, ctx.instructionsFile),
|
|
3880
|
+
`<!-- ${manifest.name}:start -->`,
|
|
3881
|
+
`<!-- ${manifest.name}:end -->`,
|
|
3882
|
+
snippet
|
|
3883
|
+
);
|
|
3884
|
+
result.filesTouched.push(ctx.instructionsFile);
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
return result;
|
|
3888
|
+
}
|
|
3889
|
+
function executeOp(op, owner, ctx, result) {
|
|
3890
|
+
switch (op.op) {
|
|
3891
|
+
case "copy":
|
|
3892
|
+
return runCopy(op, ctx, result);
|
|
3893
|
+
case "merge-json":
|
|
3894
|
+
return runMergeJson(op, owner, ctx, result);
|
|
3895
|
+
default: {
|
|
3896
|
+
const unknown = op.op;
|
|
3897
|
+
throw new Error(
|
|
3898
|
+
`Bundle "${owner}" uses unknown op "${unknown}". Installer supports: copy, merge-json. Upgrade the installer or check the bundle's schemaVersion.`
|
|
3899
|
+
);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
function runCopy(op, ctx, result) {
|
|
3904
|
+
const source = resolveBundlePath(op.from, ctx);
|
|
3905
|
+
const target = resolveProjectPath(op.to, ctx);
|
|
3906
|
+
if (!(0, import_fs3.existsSync)(source)) return;
|
|
3907
|
+
if ((0, import_fs3.statSync)(source).isDirectory()) {
|
|
3908
|
+
copyDirectory2(source, target);
|
|
3909
|
+
} else {
|
|
3910
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(target), { recursive: true });
|
|
3911
|
+
(0, import_fs3.copyFileSync)(source, target);
|
|
3912
|
+
}
|
|
3913
|
+
result.filesTouched.push(op.to);
|
|
3914
|
+
}
|
|
3915
|
+
function runMergeJson(op, owner, ctx, result) {
|
|
3916
|
+
const filePath = resolveProjectPath(op.file, ctx);
|
|
3917
|
+
const existing = readJsonOrEmpty(filePath);
|
|
3918
|
+
const stripped = stripOwnedEntries(existing, owner);
|
|
3919
|
+
const tagged = tagEntries(op.patch, owner);
|
|
3920
|
+
const merged = deepMerge2(stripped, tagged);
|
|
3921
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
3922
|
+
(0, import_fs3.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
3923
|
+
result.filesTouched.push(op.file);
|
|
3924
|
+
}
|
|
3925
|
+
function readJsonOrEmpty(path) {
|
|
3926
|
+
if (!(0, import_fs3.existsSync)(path)) return {};
|
|
3927
|
+
try {
|
|
3928
|
+
return JSON.parse((0, import_fs3.readFileSync)(path, "utf-8"));
|
|
3929
|
+
} catch {
|
|
3930
|
+
return {};
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
function isPlainObject2(v) {
|
|
3934
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3935
|
+
}
|
|
3936
|
+
function tagEntries(value, owner) {
|
|
3937
|
+
if (Array.isArray(value)) {
|
|
3938
|
+
return value.map(
|
|
3939
|
+
(item) => isPlainObject2(item) ? { ...item, [OWNER_KEY]: owner } : item
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
if (isPlainObject2(value)) {
|
|
3943
|
+
return Object.fromEntries(
|
|
3944
|
+
Object.entries(value).map(([k, v]) => [k, tagEntries(v, owner)])
|
|
3945
|
+
);
|
|
3946
|
+
}
|
|
3947
|
+
return value;
|
|
3948
|
+
}
|
|
3949
|
+
function stripOwnedEntries(value, owner) {
|
|
3950
|
+
if (Array.isArray(value)) {
|
|
3951
|
+
return value.filter((item) => !(isPlainObject2(item) && item[OWNER_KEY] === owner)).map((item) => stripOwnedEntries(item, owner));
|
|
3952
|
+
}
|
|
3953
|
+
if (isPlainObject2(value)) {
|
|
3954
|
+
return Object.fromEntries(
|
|
3955
|
+
Object.entries(value).map(([k, v]) => [k, stripOwnedEntries(v, owner)])
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
return value;
|
|
3959
|
+
}
|
|
3960
|
+
function deepMerge2(a, b) {
|
|
3961
|
+
if (isPlainObject2(a) && isPlainObject2(b)) {
|
|
3962
|
+
const out = { ...a };
|
|
3963
|
+
for (const [k, v] of Object.entries(b)) {
|
|
3964
|
+
out[k] = k in a ? deepMerge2(a[k], v) : v;
|
|
3965
|
+
}
|
|
3966
|
+
return out;
|
|
3967
|
+
}
|
|
3968
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
3969
|
+
return [...a, ...b];
|
|
3970
|
+
}
|
|
3971
|
+
return b;
|
|
3972
|
+
}
|
|
3973
|
+
function upsertMarkerBlock(filePath, startMarker, endMarker, body) {
|
|
3974
|
+
const block = `${startMarker}
|
|
3975
|
+
${body}
|
|
3976
|
+
${endMarker}`;
|
|
3977
|
+
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
|
|
3978
|
+
if (!(0, import_fs3.existsSync)(filePath)) {
|
|
3979
|
+
(0, import_fs3.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
const existing = (0, import_fs3.readFileSync)(filePath, "utf-8");
|
|
3983
|
+
const s = existing.indexOf(startMarker);
|
|
3984
|
+
const e = existing.indexOf(endMarker);
|
|
3985
|
+
if (s !== -1 && e !== -1 && e > s) {
|
|
3986
|
+
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
3987
|
+
(0, import_fs3.writeFileSync)(filePath, updated, "utf-8");
|
|
3988
|
+
} else {
|
|
3989
|
+
const joiner = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
3990
|
+
(0, import_fs3.writeFileSync)(filePath, existing + joiner + block + "\n", "utf-8");
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function resolveBundlePath(rel, ctx) {
|
|
3994
|
+
const base = (0, import_path3.resolve)(ctx.bundleRoot);
|
|
3995
|
+
const full = (0, import_path3.resolve)((0, import_path3.join)(base, rel));
|
|
3996
|
+
if (full !== base && !full.startsWith(base + import_path3.sep)) {
|
|
3997
|
+
throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
|
|
3998
|
+
}
|
|
3999
|
+
return full;
|
|
4000
|
+
}
|
|
4001
|
+
function resolveProjectPath(rel, ctx) {
|
|
4002
|
+
const base = (0, import_path3.resolve)(ctx.projectPath);
|
|
4003
|
+
const full = (0, import_path3.resolve)((0, import_path3.join)(base, rel));
|
|
4004
|
+
if (full !== base && !full.startsWith(base + import_path3.sep)) {
|
|
4005
|
+
throw new Error(`Bundle op "to" escapes project path: ${rel}`);
|
|
4006
|
+
}
|
|
4007
|
+
return full;
|
|
4008
|
+
}
|
|
4009
|
+
function assertSchemaCompatible(manifest) {
|
|
4010
|
+
if (manifest.schemaVersion !== SCHEMA_VERSION) {
|
|
4011
|
+
throw new Error(
|
|
4012
|
+
`Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
|
|
4016
|
+
throw new Error(
|
|
4017
|
+
`Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
function assertBundleRootExists(ctx) {
|
|
4022
|
+
if (!(0, import_fs3.existsSync)(ctx.bundleRoot) || !(0, import_fs3.statSync)(ctx.bundleRoot).isDirectory()) {
|
|
4023
|
+
throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
function copyDirectory2(source, target) {
|
|
4027
|
+
if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
|
|
4028
|
+
for (const entry of (0, import_fs3.readdirSync)(source, { withFileTypes: true })) {
|
|
4029
|
+
const s = (0, import_path3.join)(source, entry.name);
|
|
4030
|
+
const t = (0, import_path3.join)(target, entry.name);
|
|
4031
|
+
if (entry.isDirectory()) copyDirectory2(s, t);
|
|
4032
|
+
else (0, import_fs3.copyFileSync)(s, t);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
|
|
4036
|
+
// src/steps/resources/fetch-factory.ts
|
|
3817
4037
|
var MIN_FILE_SIZE2 = 1024;
|
|
3818
4038
|
function getFactoryAsset(agent) {
|
|
3819
4039
|
switch (agent) {
|
|
@@ -3830,10 +4050,17 @@ var fetch_factory_default = defineStep({
|
|
|
3830
4050
|
execute: async (ctx) => {
|
|
3831
4051
|
const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
|
|
3832
4052
|
ctx.installed.factoryInstalled = result.success;
|
|
4053
|
+
ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
|
|
4054
|
+
if (result.instructionsSnippet) {
|
|
4055
|
+
const stash = ctx.installed.bundleInstructions ?? {};
|
|
4056
|
+
stash["factory"] = result.instructionsSnippet;
|
|
4057
|
+
ctx.installed.bundleInstructions = stash;
|
|
4058
|
+
}
|
|
3833
4059
|
if (!result.success) {
|
|
3834
4060
|
return { status: "failed", detail: result.failureReason };
|
|
3835
4061
|
}
|
|
3836
|
-
|
|
4062
|
+
const tag = result.bundleManaged ? " (bundle)" : "";
|
|
4063
|
+
return { status: "success", message: result.filesInstalled.join(", ") + tag };
|
|
3837
4064
|
}
|
|
3838
4065
|
});
|
|
3839
4066
|
async function fetchFactory(token, repo, targetDir, agent) {
|
|
@@ -3862,10 +4089,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3862
4089
|
result.failureReason = `${assetName} not found in release`;
|
|
3863
4090
|
return result;
|
|
3864
4091
|
}
|
|
3865
|
-
const tempDir = (0,
|
|
4092
|
+
const tempDir = (0, import_path4.join)(targetDir, ".temp-factory-download");
|
|
3866
4093
|
try {
|
|
3867
|
-
if ((0,
|
|
3868
|
-
(0,
|
|
4094
|
+
if ((0, import_fs4.existsSync)(tempDir)) (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
4095
|
+
(0, import_fs4.mkdirSync)(tempDir, { recursive: true });
|
|
3869
4096
|
const response = await fetchWithRetry(asset.url, {
|
|
3870
4097
|
headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
|
|
3871
4098
|
});
|
|
@@ -3873,10 +4100,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3873
4100
|
result.failureReason = `Download failed (${response.status})`;
|
|
3874
4101
|
return result;
|
|
3875
4102
|
}
|
|
3876
|
-
const archivePath = (0,
|
|
4103
|
+
const archivePath = (0, import_path4.join)(tempDir, assetName);
|
|
3877
4104
|
const arrayBuffer = await response.arrayBuffer();
|
|
3878
|
-
(0,
|
|
3879
|
-
const stats = (0,
|
|
4105
|
+
(0, import_fs4.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
|
|
4106
|
+
const stats = (0, import_fs4.statSync)(archivePath);
|
|
3880
4107
|
if (stats.size < MIN_FILE_SIZE2) {
|
|
3881
4108
|
result.failureReason = "Downloaded file corrupted (too small)";
|
|
3882
4109
|
return result;
|
|
@@ -3886,10 +4113,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3886
4113
|
result.failureReason = "Failed to read archive contents";
|
|
3887
4114
|
return result;
|
|
3888
4115
|
}
|
|
3889
|
-
const resolvedTempDir = (0,
|
|
4116
|
+
const resolvedTempDir = (0, import_path4.resolve)(tempDir);
|
|
3890
4117
|
for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
|
|
3891
|
-
const entryPath = (0,
|
|
3892
|
-
if (!entryPath.startsWith(resolvedTempDir +
|
|
4118
|
+
const entryPath = (0, import_path4.resolve)((0, import_path4.join)(tempDir, entry.replace(/\/$/, "")));
|
|
4119
|
+
if (!entryPath.startsWith(resolvedTempDir + import_path4.sep)) {
|
|
3893
4120
|
result.failureReason = `Archive contains unsafe path: ${entry}`;
|
|
3894
4121
|
return result;
|
|
3895
4122
|
}
|
|
@@ -3899,14 +4126,17 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3899
4126
|
result.failureReason = "Archive extraction failed";
|
|
3900
4127
|
return result;
|
|
3901
4128
|
}
|
|
3902
|
-
const extractedEntries = (0,
|
|
4129
|
+
const extractedEntries = (0, import_fs4.readdirSync)(tempDir);
|
|
3903
4130
|
const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
|
|
3904
4131
|
if (!extractedFolder) {
|
|
3905
4132
|
result.failureReason = `No ${rootFolder}/ folder in archive`;
|
|
3906
4133
|
return result;
|
|
3907
4134
|
}
|
|
3908
|
-
const extractedPath = (0,
|
|
3909
|
-
|
|
4135
|
+
const extractedPath = (0, import_path4.join)(tempDir, extractedFolder);
|
|
4136
|
+
const manifestPath = (0, import_path4.join)(extractedPath, "install.json");
|
|
4137
|
+
if ((0, import_fs4.existsSync)(manifestPath)) {
|
|
4138
|
+
await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
|
|
4139
|
+
} else if (agent === "github-copilot") {
|
|
3910
4140
|
installCopilotFactory(extractedPath, targetDir, result);
|
|
3911
4141
|
} else {
|
|
3912
4142
|
installClaudeFactory(extractedPath, targetDir, result);
|
|
@@ -3915,53 +4145,73 @@ async function fetchFactory(token, repo, targetDir, agent) {
|
|
|
3915
4145
|
} catch (error) {
|
|
3916
4146
|
result.failureReason = error instanceof Error ? error.message : String(error);
|
|
3917
4147
|
} finally {
|
|
3918
|
-
if ((0,
|
|
4148
|
+
if ((0, import_fs4.existsSync)(tempDir)) {
|
|
3919
4149
|
try {
|
|
3920
|
-
(0,
|
|
4150
|
+
(0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
|
|
3921
4151
|
} catch {
|
|
3922
4152
|
}
|
|
3923
4153
|
}
|
|
3924
4154
|
}
|
|
3925
4155
|
return result;
|
|
3926
4156
|
}
|
|
4157
|
+
async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
|
|
4158
|
+
let manifest;
|
|
4159
|
+
try {
|
|
4160
|
+
manifest = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
|
|
4161
|
+
} catch (e) {
|
|
4162
|
+
throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
4163
|
+
}
|
|
4164
|
+
const target = getAgentTarget(agent);
|
|
4165
|
+
const runResult = await runBundle(manifest, {
|
|
4166
|
+
bundleRoot: extractedPath,
|
|
4167
|
+
projectPath: targetDir,
|
|
4168
|
+
agent,
|
|
4169
|
+
instructionsFile: target.instructions,
|
|
4170
|
+
skipInstructions: true
|
|
4171
|
+
// write-instructions.ts commits the snippet for correct ordering
|
|
4172
|
+
});
|
|
4173
|
+
result.bundleManaged = true;
|
|
4174
|
+
result.instructionsSnippet = runResult.instructionsSnippet;
|
|
4175
|
+
result.filesInstalled = runResult.filesTouched;
|
|
4176
|
+
}
|
|
3927
4177
|
function installClaudeFactory(extractedPath, targetDir, result) {
|
|
3928
|
-
const claudeDir = (0,
|
|
3929
|
-
const factoryDir = (0,
|
|
3930
|
-
const agentsSrc = (0,
|
|
3931
|
-
if ((0,
|
|
3932
|
-
|
|
4178
|
+
const claudeDir = (0, import_path4.join)(targetDir, ".claude");
|
|
4179
|
+
const factoryDir = (0, import_path4.join)(targetDir, "factory");
|
|
4180
|
+
const agentsSrc = (0, import_path4.join)(extractedPath, "agents");
|
|
4181
|
+
if ((0, import_fs4.existsSync)(agentsSrc)) {
|
|
4182
|
+
copyDirectory3(agentsSrc, (0, import_path4.join)(claudeDir, "agents"));
|
|
3933
4183
|
result.filesInstalled.push(".claude/agents/");
|
|
3934
4184
|
}
|
|
3935
|
-
const hooksSrc = (0,
|
|
3936
|
-
if ((0,
|
|
3937
|
-
|
|
4185
|
+
const hooksSrc = (0, import_path4.join)(extractedPath, "hooks");
|
|
4186
|
+
if ((0, import_fs4.existsSync)(hooksSrc)) {
|
|
4187
|
+
copyDirectory3(hooksSrc, (0, import_path4.join)(claudeDir, "hooks"));
|
|
3938
4188
|
result.filesInstalled.push(".claude/hooks/");
|
|
3939
4189
|
}
|
|
3940
|
-
const skillsSrc = (0,
|
|
3941
|
-
if ((0,
|
|
3942
|
-
|
|
4190
|
+
const skillsSrc = (0, import_path4.join)(extractedPath, "skills");
|
|
4191
|
+
if ((0, import_fs4.existsSync)(skillsSrc)) {
|
|
4192
|
+
copyDirectory3(skillsSrc, (0, import_path4.join)(claudeDir, "skills"));
|
|
3943
4193
|
result.filesInstalled.push(".claude/skills/");
|
|
3944
4194
|
}
|
|
3945
|
-
const workflowSrc = (0,
|
|
3946
|
-
if ((0,
|
|
3947
|
-
|
|
4195
|
+
const workflowSrc = (0, import_path4.join)(extractedPath, "workflow");
|
|
4196
|
+
if ((0, import_fs4.existsSync)(workflowSrc)) {
|
|
4197
|
+
copyDirectory3(workflowSrc, (0, import_path4.join)(factoryDir, "workflow"));
|
|
3948
4198
|
result.filesInstalled.push("factory/workflow/");
|
|
3949
4199
|
}
|
|
3950
|
-
const utilsSrc = (0,
|
|
3951
|
-
if ((0,
|
|
3952
|
-
|
|
4200
|
+
const utilsSrc = (0, import_path4.join)(extractedPath, "utils");
|
|
4201
|
+
if ((0, import_fs4.existsSync)(utilsSrc)) {
|
|
4202
|
+
copyDirectory3(utilsSrc, (0, import_path4.join)(factoryDir, "utils"));
|
|
3953
4203
|
result.filesInstalled.push("factory/utils/");
|
|
3954
4204
|
}
|
|
3955
|
-
const docsSrc = (0,
|
|
3956
|
-
if ((0,
|
|
3957
|
-
|
|
4205
|
+
const docsSrc = (0, import_path4.join)(extractedPath, "docs");
|
|
4206
|
+
if ((0, import_fs4.existsSync)(docsSrc)) {
|
|
4207
|
+
copyDirectory3(docsSrc, (0, import_path4.join)(factoryDir, "docs"));
|
|
3958
4208
|
result.filesInstalled.push("factory/docs/");
|
|
3959
4209
|
}
|
|
3960
|
-
const rootEntries = (0,
|
|
4210
|
+
const rootEntries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
|
|
3961
4211
|
for (const entry of rootEntries) {
|
|
3962
4212
|
if (entry.isFile()) {
|
|
3963
|
-
if (!(0,
|
|
3964
|
-
(0,
|
|
4213
|
+
if (!(0, import_fs4.existsSync)(factoryDir)) (0, import_fs4.mkdirSync)(factoryDir, { recursive: true });
|
|
4214
|
+
(0, import_fs4.copyFileSync)((0, import_path4.join)(extractedPath, entry.name), (0, import_path4.join)(factoryDir, entry.name));
|
|
3965
4215
|
result.filesInstalled.push(`factory/${entry.name}`);
|
|
3966
4216
|
}
|
|
3967
4217
|
}
|
|
@@ -3969,9 +4219,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
|
|
|
3969
4219
|
result.filesInstalled.push(".claude/settings.json");
|
|
3970
4220
|
}
|
|
3971
4221
|
function installCopilotFactory(extractedPath, targetDir, result) {
|
|
3972
|
-
const factoryDir = (0,
|
|
3973
|
-
|
|
3974
|
-
const entries = (0,
|
|
4222
|
+
const factoryDir = (0, import_path4.join)(targetDir, "factory");
|
|
4223
|
+
copyDirectory3(extractedPath, factoryDir);
|
|
4224
|
+
const entries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
|
|
3975
4225
|
for (const entry of entries) {
|
|
3976
4226
|
if (entry.isDirectory()) {
|
|
3977
4227
|
result.filesInstalled.push(`factory/${entry.name}/`);
|
|
@@ -3981,11 +4231,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
|
|
|
3981
4231
|
}
|
|
3982
4232
|
}
|
|
3983
4233
|
function configureSettings(claudeDir) {
|
|
3984
|
-
const settingsPath = (0,
|
|
4234
|
+
const settingsPath = (0, import_path4.join)(claudeDir, "settings.json");
|
|
3985
4235
|
let settings = {};
|
|
3986
|
-
if ((0,
|
|
4236
|
+
if ((0, import_fs4.existsSync)(settingsPath)) {
|
|
3987
4237
|
try {
|
|
3988
|
-
settings = JSON.parse((0,
|
|
4238
|
+
settings = JSON.parse((0, import_fs4.readFileSync)(settingsPath, "utf8"));
|
|
3989
4239
|
} catch {
|
|
3990
4240
|
}
|
|
3991
4241
|
}
|
|
@@ -4036,26 +4286,26 @@ function configureSettings(claudeDir) {
|
|
|
4036
4286
|
if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
|
|
4037
4287
|
}
|
|
4038
4288
|
settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
|
|
4039
|
-
if (!(0,
|
|
4040
|
-
(0,
|
|
4289
|
+
if (!(0, import_fs4.existsSync)(claudeDir)) (0, import_fs4.mkdirSync)(claudeDir, { recursive: true });
|
|
4290
|
+
(0, import_fs4.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4041
4291
|
}
|
|
4042
|
-
function
|
|
4043
|
-
if (!(0,
|
|
4044
|
-
const entries = (0,
|
|
4292
|
+
function copyDirectory3(source, target) {
|
|
4293
|
+
if (!(0, import_fs4.existsSync)(target)) (0, import_fs4.mkdirSync)(target, { recursive: true });
|
|
4294
|
+
const entries = (0, import_fs4.readdirSync)(source, { withFileTypes: true });
|
|
4045
4295
|
for (const entry of entries) {
|
|
4046
|
-
const sourcePath = (0,
|
|
4047
|
-
const targetPath = (0,
|
|
4296
|
+
const sourcePath = (0, import_path4.join)(source, entry.name);
|
|
4297
|
+
const targetPath = (0, import_path4.join)(target, entry.name);
|
|
4048
4298
|
if (entry.isDirectory()) {
|
|
4049
|
-
|
|
4299
|
+
copyDirectory3(sourcePath, targetPath);
|
|
4050
4300
|
} else {
|
|
4051
|
-
(0,
|
|
4301
|
+
(0, import_fs4.copyFileSync)(sourcePath, targetPath);
|
|
4052
4302
|
}
|
|
4053
4303
|
}
|
|
4054
4304
|
}
|
|
4055
4305
|
|
|
4056
4306
|
// src/steps/setup/write-instructions.ts
|
|
4057
|
-
var
|
|
4058
|
-
var
|
|
4307
|
+
var import_fs5 = require("fs");
|
|
4308
|
+
var import_path5 = require("path");
|
|
4059
4309
|
|
|
4060
4310
|
// src/steps/shared.ts
|
|
4061
4311
|
function getFilteredMcpConfig(ctx) {
|
|
@@ -4079,65 +4329,45 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
|
4079
4329
|
var write_instructions_default = defineStep({
|
|
4080
4330
|
name: "write-instructions",
|
|
4081
4331
|
label: "Writing instruction file",
|
|
4082
|
-
when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled,
|
|
4332
|
+
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
4333
|
execute: async (ctx) => {
|
|
4084
4334
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4085
4335
|
const domains = ctx.installed.domainsInstalled ?? [];
|
|
4086
4336
|
const agent = ctx.config.agent;
|
|
4087
4337
|
const projectPath = ctx.config.projectPath;
|
|
4088
4338
|
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, `---
|
|
4339
|
+
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4340
|
+
const includeFactorySection = factoryInstalled && !factoryBundleManaged;
|
|
4341
|
+
const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
|
|
4342
|
+
const target = getAgentTarget(agent);
|
|
4343
|
+
const filePath = (0, import_path5.join)(projectPath, target.instructions);
|
|
4344
|
+
const fileDir = (0, import_path5.dirname)(filePath);
|
|
4345
|
+
if (!(0, import_fs5.existsSync)(fileDir)) (0, import_fs5.mkdirSync)(fileDir, { recursive: true });
|
|
4346
|
+
if (agent === "cursor" && !(0, import_fs5.existsSync)(filePath)) {
|
|
4347
|
+
(0, import_fs5.writeFileSync)(filePath, `---
|
|
4107
4348
|
description: AI development instructions from One-Shot Installer
|
|
4108
4349
|
alwaysApply: true
|
|
4109
4350
|
---
|
|
4110
4351
|
|
|
4111
4352
|
`, "utf-8");
|
|
4112
|
-
}
|
|
4113
|
-
upsertBlock(filePath, content);
|
|
4114
|
-
break;
|
|
4115
|
-
}
|
|
4116
4353
|
}
|
|
4117
|
-
|
|
4354
|
+
upsertBlock(filePath, content);
|
|
4355
|
+
const bundleSnippets = ctx.installed.bundleInstructions ?? {};
|
|
4356
|
+
for (const bundleName of Object.keys(bundleSnippets).sort()) {
|
|
4357
|
+
upsertBundleBlock(filePath, bundleName, bundleSnippets[bundleName]);
|
|
4358
|
+
}
|
|
4359
|
+
return { status: "success", message: target.instructions };
|
|
4118
4360
|
}
|
|
4119
4361
|
});
|
|
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
4362
|
function upsertBlock(filePath, block) {
|
|
4133
4363
|
const marked = `${MARKER_START}
|
|
4134
4364
|
${block}
|
|
4135
4365
|
${MARKER_END}`;
|
|
4136
|
-
if (!(0,
|
|
4137
|
-
(0,
|
|
4366
|
+
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
4367
|
+
(0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
|
|
4138
4368
|
return;
|
|
4139
4369
|
}
|
|
4140
|
-
const existing = (0,
|
|
4370
|
+
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
4141
4371
|
const start = existing.indexOf(MARKER_START);
|
|
4142
4372
|
const end = existing.indexOf(MARKER_END);
|
|
4143
4373
|
const hasStart = start !== -1;
|
|
@@ -4162,23 +4392,44 @@ ${MARKER_END}`;
|
|
|
4162
4392
|
}
|
|
4163
4393
|
if (hasStart && hasEnd) {
|
|
4164
4394
|
const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
|
|
4165
|
-
(0,
|
|
4395
|
+
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
4166
4396
|
} else {
|
|
4167
4397
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4168
|
-
(0,
|
|
4398
|
+
(0, import_fs5.writeFileSync)(filePath, existing + separator + marked, "utf-8");
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
function upsertBundleBlock(filePath, bundleName, snippet) {
|
|
4402
|
+
const startMarker = `<!-- ${bundleName}:start -->`;
|
|
4403
|
+
const endMarker = `<!-- ${bundleName}:end -->`;
|
|
4404
|
+
const block = `${startMarker}
|
|
4405
|
+
${snippet}
|
|
4406
|
+
${endMarker}`;
|
|
4407
|
+
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
4408
|
+
(0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
|
|
4409
|
+
return;
|
|
4410
|
+
}
|
|
4411
|
+
const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
4412
|
+
const s = existing.indexOf(startMarker);
|
|
4413
|
+
const e = existing.indexOf(endMarker);
|
|
4414
|
+
if (s !== -1 && e !== -1 && e > s) {
|
|
4415
|
+
const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
|
|
4416
|
+
(0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
|
|
4417
|
+
} else {
|
|
4418
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4419
|
+
(0, import_fs5.writeFileSync)(filePath, existing + separator + block + "\n", "utf-8");
|
|
4169
4420
|
}
|
|
4170
4421
|
}
|
|
4171
4422
|
function collectMdFiles(dir) {
|
|
4172
|
-
if (!(0,
|
|
4173
|
-
const entries = (0,
|
|
4423
|
+
if (!(0, import_fs5.existsSync)(dir)) return [];
|
|
4424
|
+
const entries = (0, import_fs5.readdirSync)(dir, { recursive: true });
|
|
4174
4425
|
return entries.filter((entry) => {
|
|
4175
|
-
const fullPath = (0,
|
|
4176
|
-
return entry.endsWith(".md") && (0,
|
|
4426
|
+
const fullPath = (0, import_path5.join)(dir, entry);
|
|
4427
|
+
return entry.endsWith(".md") && (0, import_fs5.statSync)(fullPath).isFile();
|
|
4177
4428
|
}).map((entry) => entry.replace(/\\/g, "/")).sort();
|
|
4178
4429
|
}
|
|
4179
4430
|
function extractFirstHeading(filePath) {
|
|
4180
4431
|
try {
|
|
4181
|
-
const content = (0,
|
|
4432
|
+
const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
4182
4433
|
const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
4183
4434
|
const match = withoutFrontmatter.match(/^#\s+(.+)/m);
|
|
4184
4435
|
if (match) {
|
|
@@ -4196,16 +4447,16 @@ function formatPathRef(contextPath, description, agent) {
|
|
|
4196
4447
|
return `- \`${contextPath}\` \u2014 ${description}`;
|
|
4197
4448
|
}
|
|
4198
4449
|
function resolveDomainFolder(domain, contextsDir) {
|
|
4199
|
-
if ((0,
|
|
4450
|
+
if ((0, import_fs5.existsSync)(contextsDir)) {
|
|
4200
4451
|
try {
|
|
4201
|
-
const entries = (0,
|
|
4452
|
+
const entries = (0, import_fs5.readdirSync)(contextsDir);
|
|
4202
4453
|
const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
|
|
4203
|
-
if (match) return { folderName: match, folderPath: (0,
|
|
4454
|
+
if (match) return { folderName: match, folderPath: (0, import_path5.join)(contextsDir, match) };
|
|
4204
4455
|
} catch {
|
|
4205
4456
|
}
|
|
4206
4457
|
}
|
|
4207
4458
|
const fallback = domain.toUpperCase();
|
|
4208
|
-
return { folderName: fallback, folderPath: (0,
|
|
4459
|
+
return { folderName: fallback, folderPath: (0, import_path5.join)(contextsDir, fallback) };
|
|
4209
4460
|
}
|
|
4210
4461
|
function buildContextRefsSection(domains, agent, contextsDir) {
|
|
4211
4462
|
const lines2 = [
|
|
@@ -4217,34 +4468,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
|
|
|
4217
4468
|
let hasAnyFiles = false;
|
|
4218
4469
|
for (const domain of domains) {
|
|
4219
4470
|
const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
|
|
4220
|
-
if (!(0,
|
|
4471
|
+
if (!(0, import_fs5.existsSync)(domainPath)) continue;
|
|
4221
4472
|
const domainFiles = [];
|
|
4222
|
-
const ctxInstructions = (0,
|
|
4223
|
-
if ((0,
|
|
4473
|
+
const ctxInstructions = (0, import_path5.join)(domainPath, "context-instructions.md");
|
|
4474
|
+
if ((0, import_fs5.existsSync)(ctxInstructions)) {
|
|
4224
4475
|
const desc = extractFirstHeading(ctxInstructions);
|
|
4225
4476
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
|
|
4226
4477
|
}
|
|
4227
|
-
const instructionsMd = (0,
|
|
4228
|
-
if ((0,
|
|
4478
|
+
const instructionsMd = (0, import_path5.join)(domainPath, "core", "instructions.md");
|
|
4479
|
+
if ((0, import_fs5.existsSync)(instructionsMd)) {
|
|
4229
4480
|
const desc = extractFirstHeading(instructionsMd);
|
|
4230
4481
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
|
|
4231
4482
|
}
|
|
4232
|
-
const coreDir = (0,
|
|
4233
|
-
if ((0,
|
|
4483
|
+
const coreDir = (0, import_path5.join)(domainPath, "core");
|
|
4484
|
+
if ((0, import_fs5.existsSync)(coreDir)) {
|
|
4234
4485
|
const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
|
|
4235
4486
|
for (const file of coreFiles) {
|
|
4236
|
-
const desc = extractFirstHeading((0,
|
|
4487
|
+
const desc = extractFirstHeading((0, import_path5.join)(coreDir, file));
|
|
4237
4488
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
|
|
4238
4489
|
}
|
|
4239
4490
|
}
|
|
4240
|
-
const refDir = (0,
|
|
4241
|
-
if ((0,
|
|
4491
|
+
const refDir = (0, import_path5.join)(domainPath, "reference");
|
|
4492
|
+
if ((0, import_fs5.existsSync)(refDir)) {
|
|
4242
4493
|
const refFiles = collectMdFiles(refDir);
|
|
4243
4494
|
if (refFiles.length > 0) {
|
|
4244
4495
|
domainFiles.push(``);
|
|
4245
4496
|
domainFiles.push(`**Reference & cheat sheets:**`);
|
|
4246
4497
|
for (const file of refFiles) {
|
|
4247
|
-
const desc = extractFirstHeading((0,
|
|
4498
|
+
const desc = extractFirstHeading((0, import_path5.join)(refDir, file));
|
|
4248
4499
|
domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
|
|
4249
4500
|
}
|
|
4250
4501
|
}
|
|
@@ -4322,7 +4573,7 @@ function buildFactorySection(agent) {
|
|
|
4322
4573
|
].join("\n");
|
|
4323
4574
|
}
|
|
4324
4575
|
function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
|
|
4325
|
-
const contextsDir = (0,
|
|
4576
|
+
const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
|
|
4326
4577
|
const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
|
|
4327
4578
|
lines2.push(buildContextRefsSection(domains, agent, contextsDir));
|
|
4328
4579
|
if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
|
|
@@ -4331,8 +4582,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
|
|
|
4331
4582
|
}
|
|
4332
4583
|
|
|
4333
4584
|
// src/steps/setup/write-mcp-config.ts
|
|
4334
|
-
var
|
|
4335
|
-
var
|
|
4585
|
+
var import_fs6 = require("fs");
|
|
4586
|
+
var import_path6 = require("path");
|
|
4336
4587
|
var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
|
|
4337
4588
|
var write_mcp_config_default = defineStep({
|
|
4338
4589
|
name: "write-mcp-config",
|
|
@@ -4340,23 +4591,23 @@ var write_mcp_config_default = defineStep({
|
|
|
4340
4591
|
when: (ctx) => Object.keys(ctx.config.mcpConfig).length > 0,
|
|
4341
4592
|
execute: async (ctx) => {
|
|
4342
4593
|
const filteredMcpConfig = getFilteredMcpConfig(ctx);
|
|
4343
|
-
const target =
|
|
4344
|
-
const mcpJsonPath = (0,
|
|
4345
|
-
if (target.
|
|
4346
|
-
const dir = (0,
|
|
4347
|
-
if (!(0,
|
|
4594
|
+
const target = getAgentTarget(ctx.config.agent);
|
|
4595
|
+
const mcpJsonPath = (0, import_path6.join)(ctx.config.projectPath, target.mcpConfig);
|
|
4596
|
+
if (target.mcpDir) {
|
|
4597
|
+
const dir = (0, import_path6.join)(ctx.config.projectPath, target.mcpDir);
|
|
4598
|
+
if (!(0, import_fs6.existsSync)(dir)) (0, import_fs6.mkdirSync)(dir, { recursive: true });
|
|
4348
4599
|
}
|
|
4349
4600
|
let existingFile = {};
|
|
4350
|
-
if ((0,
|
|
4601
|
+
if ((0, import_fs6.existsSync)(mcpJsonPath)) {
|
|
4351
4602
|
try {
|
|
4352
|
-
existingFile = JSON.parse((0,
|
|
4603
|
+
existingFile = JSON.parse((0, import_fs6.readFileSync)(mcpJsonPath, "utf-8"));
|
|
4353
4604
|
} catch {
|
|
4354
4605
|
const box = [
|
|
4355
4606
|
"",
|
|
4356
4607
|
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
4608
|
red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
|
|
4358
4609
|
red2(" \u2551 \u2551"),
|
|
4359
|
-
red2(` \u2551 ${target.
|
|
4610
|
+
red2(` \u2551 ${target.mcpConfig} could not be parsed.`.padEnd(52) + "\u2551"),
|
|
4360
4611
|
red2(" \u2551 Existing MCP servers may be lost. \u2551"),
|
|
4361
4612
|
red2(" \u2551 Check the file after installation completes. \u2551"),
|
|
4362
4613
|
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 +4617,7 @@ var write_mcp_config_default = defineStep({
|
|
|
4366
4617
|
}
|
|
4367
4618
|
}
|
|
4368
4619
|
const addedServers = [];
|
|
4369
|
-
const existingServers = existingFile[target.
|
|
4620
|
+
const existingServers = existingFile[target.mcpRootKey] ?? {};
|
|
4370
4621
|
const newServers = {};
|
|
4371
4622
|
for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
|
|
4372
4623
|
const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
|
|
@@ -4374,8 +4625,8 @@ var write_mcp_config_default = defineStep({
|
|
|
4374
4625
|
addedServers.push(serverName);
|
|
4375
4626
|
}
|
|
4376
4627
|
const mergedServers = { ...existingServers, ...newServers };
|
|
4377
|
-
const outputFile = { ...existingFile, [target.
|
|
4378
|
-
(0,
|
|
4628
|
+
const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
|
|
4629
|
+
(0, import_fs6.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
|
|
4379
4630
|
ctx.installed.mcpServersAdded = addedServers;
|
|
4380
4631
|
return {
|
|
4381
4632
|
status: "success",
|
|
@@ -4383,17 +4634,6 @@ var write_mcp_config_default = defineStep({
|
|
|
4383
4634
|
};
|
|
4384
4635
|
}
|
|
4385
4636
|
});
|
|
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
4637
|
|
|
4398
4638
|
// src/steps/setup/run-mcp-install-commands.ts
|
|
4399
4639
|
var import_child_process4 = require("child_process");
|
|
@@ -4456,11 +4696,11 @@ function isClaudeCliAvailable() {
|
|
|
4456
4696
|
}
|
|
4457
4697
|
|
|
4458
4698
|
// src/steps/setup/update-gitignore.ts
|
|
4459
|
-
var
|
|
4460
|
-
var
|
|
4699
|
+
var import_fs7 = require("fs");
|
|
4700
|
+
var import_path7 = require("path");
|
|
4461
4701
|
var MARKER_START2 = "# one-shot-installer:start";
|
|
4462
4702
|
var MARKER_END2 = "# one-shot-installer:end";
|
|
4463
|
-
var
|
|
4703
|
+
var CORE_GITIGNORE_ENTRIES = [
|
|
4464
4704
|
"# One-Shot installer generated files",
|
|
4465
4705
|
".one-shot-state.json",
|
|
4466
4706
|
".mcp.json",
|
|
@@ -4472,8 +4712,9 @@ var GITIGNORE_ENTRIES = [
|
|
|
4472
4712
|
".claude/agents/",
|
|
4473
4713
|
".claude/agent-memory/",
|
|
4474
4714
|
".claude/plans/",
|
|
4475
|
-
".claude/settings.local.json"
|
|
4476
|
-
|
|
4715
|
+
".claude/settings.local.json"
|
|
4716
|
+
];
|
|
4717
|
+
var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
|
|
4477
4718
|
"# Factory runtime state (generated by workflow, not committed)",
|
|
4478
4719
|
"factory/STATE.md",
|
|
4479
4720
|
"factory/PROJECT.md",
|
|
@@ -4491,40 +4732,45 @@ var update_gitignore_default = defineStep({
|
|
|
4491
4732
|
name: "update-gitignore",
|
|
4492
4733
|
label: "Updating .gitignore",
|
|
4493
4734
|
execute: async (ctx) => {
|
|
4494
|
-
const gitignorePath = (0,
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
|
|
4735
|
+
const gitignorePath = (0, import_path7.join)(ctx.config.projectPath, ".gitignore");
|
|
4736
|
+
const factoryInstalled = ctx.installed.factoryInstalled ?? false;
|
|
4737
|
+
const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
|
|
4738
|
+
const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
|
|
4739
|
+
const entries = includeLegacyFactory ? [...CORE_GITIGNORE_ENTRIES, "", ...LEGACY_FACTORY_GITIGNORE_ENTRIES] : CORE_GITIGNORE_ENTRIES;
|
|
4740
|
+
const block = [MARKER_START2, ...entries, MARKER_END2].join("\n");
|
|
4741
|
+
if (!(0, import_fs7.existsSync)(gitignorePath)) {
|
|
4742
|
+
(0, import_fs7.writeFileSync)(gitignorePath, block + "\n", "utf-8");
|
|
4498
4743
|
return { status: "success" };
|
|
4499
4744
|
}
|
|
4500
|
-
const existing = (0,
|
|
4745
|
+
const existing = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
|
|
4501
4746
|
const start = existing.indexOf(MARKER_START2);
|
|
4502
4747
|
const end = existing.indexOf(MARKER_END2);
|
|
4503
4748
|
if (start !== -1 && end !== -1 && end > start) {
|
|
4504
4749
|
const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
|
|
4505
|
-
(0,
|
|
4750
|
+
(0, import_fs7.writeFileSync)(gitignorePath, updated, "utf-8");
|
|
4506
4751
|
} else {
|
|
4507
4752
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
4508
|
-
(0,
|
|
4753
|
+
(0, import_fs7.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
|
|
4509
4754
|
}
|
|
4510
4755
|
return { status: "success" };
|
|
4511
4756
|
}
|
|
4512
4757
|
});
|
|
4513
4758
|
|
|
4514
4759
|
// src/steps/setup/write-state.ts
|
|
4515
|
-
var
|
|
4516
|
-
var
|
|
4760
|
+
var import_fs8 = require("fs");
|
|
4761
|
+
var import_path8 = require("path");
|
|
4517
4762
|
var import_os2 = require("os");
|
|
4518
|
-
var INSTALLER_VERSION = "0.
|
|
4763
|
+
var INSTALLER_VERSION = "0.6.0";
|
|
4519
4764
|
var write_state_default = defineStep({
|
|
4520
4765
|
name: "write-state",
|
|
4521
4766
|
label: "Saving installation state",
|
|
4522
4767
|
execute: async (ctx) => {
|
|
4523
|
-
const statePath = (0,
|
|
4768
|
+
const statePath = (0, import_path8.join)(ctx.config.projectPath, ".one-shot-state.json");
|
|
4524
4769
|
const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
|
|
4525
4770
|
const filteredMcpConfig = Object.fromEntries(
|
|
4526
4771
|
Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
|
|
4527
4772
|
);
|
|
4773
|
+
const target = getAgentTarget(ctx.config.agent);
|
|
4528
4774
|
const state = {
|
|
4529
4775
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4530
4776
|
installerVersion: INSTALLER_VERSION,
|
|
@@ -4538,65 +4784,19 @@ var write_state_default = defineStep({
|
|
|
4538
4784
|
mcpConfigs: filteredMcpConfig,
|
|
4539
4785
|
factoryInstalled: ctx.installed.factoryInstalled ?? false,
|
|
4540
4786
|
files: {
|
|
4541
|
-
instructions:
|
|
4542
|
-
mcpConfig:
|
|
4787
|
+
instructions: target.instructions,
|
|
4788
|
+
mcpConfig: target.mcpConfig,
|
|
4543
4789
|
contexts: "_ai-context/",
|
|
4544
4790
|
factory: ctx.installed.factoryInstalled ? "factory/" : null,
|
|
4545
|
-
globalConfig: (0,
|
|
4791
|
+
globalConfig: (0, import_path8.join)((0, import_os2.homedir)(), ".one-shot-installer")
|
|
4546
4792
|
}
|
|
4547
4793
|
};
|
|
4548
|
-
(0,
|
|
4794
|
+
(0, import_fs8.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4549
4795
|
return { status: "success" };
|
|
4550
4796
|
}
|
|
4551
4797
|
});
|
|
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
4798
|
|
|
4576
4799
|
// 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
4800
|
function formatMCPCommand(server) {
|
|
4601
4801
|
if (server.url) return server.url;
|
|
4602
4802
|
if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
|
|
@@ -4606,27 +4806,27 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
|
|
|
4606
4806
|
var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
|
|
4607
4807
|
var green = (text) => `\x1B[32m${text}\x1B[0m`;
|
|
4608
4808
|
function detectMarkerFileMode(filePath, markerStart) {
|
|
4609
|
-
if (!(0,
|
|
4610
|
-
const content = (0,
|
|
4809
|
+
if (!(0, import_fs9.existsSync)(filePath)) return green("create");
|
|
4810
|
+
const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
4611
4811
|
if (content.includes(markerStart)) return yellow("update");
|
|
4612
4812
|
return yellow("append");
|
|
4613
4813
|
}
|
|
4614
4814
|
function detectMCPFileMode(filePath) {
|
|
4615
|
-
if (!(0,
|
|
4815
|
+
if (!(0, import_fs9.existsSync)(filePath)) return green("create");
|
|
4616
4816
|
return yellow("merge");
|
|
4617
4817
|
}
|
|
4618
4818
|
function detectContextMode(projectPath, domain) {
|
|
4619
|
-
const contextDir = (0,
|
|
4620
|
-
const contextDirLower = (0,
|
|
4621
|
-
if ((0,
|
|
4819
|
+
const contextDir = (0, import_path9.join)(projectPath, "_ai-context", domain.toUpperCase());
|
|
4820
|
+
const contextDirLower = (0, import_path9.join)(projectPath, "_ai-context", domain);
|
|
4821
|
+
if ((0, import_fs9.existsSync)(contextDir) || (0, import_fs9.existsSync)(contextDirLower)) return yellow("overwrite");
|
|
4622
4822
|
return green("create");
|
|
4623
4823
|
}
|
|
4624
4824
|
function waitForEnter() {
|
|
4625
4825
|
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
4626
|
-
return new Promise((
|
|
4826
|
+
return new Promise((resolve5) => {
|
|
4627
4827
|
rl.question(" Press Enter to close...", () => {
|
|
4628
4828
|
rl.close();
|
|
4629
|
-
|
|
4829
|
+
resolve5();
|
|
4630
4830
|
});
|
|
4631
4831
|
});
|
|
4632
4832
|
}
|
|
@@ -4716,13 +4916,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4716
4916
|
}
|
|
4717
4917
|
const projectInput = await esm_default4({
|
|
4718
4918
|
message: "Project directory:",
|
|
4719
|
-
default: (0,
|
|
4919
|
+
default: (0, import_path9.resolve)(process.cwd())
|
|
4720
4920
|
});
|
|
4721
4921
|
return {
|
|
4722
4922
|
technologies: selectedTechnologies,
|
|
4723
4923
|
agent,
|
|
4724
4924
|
azureDevOpsOrg,
|
|
4725
|
-
projectPath: (0,
|
|
4925
|
+
projectPath: (0, import_path9.resolve)(projectInput),
|
|
4726
4926
|
baseMcpServers: options.baseMcpServers,
|
|
4727
4927
|
mcpConfig,
|
|
4728
4928
|
releaseVersion,
|
|
@@ -4732,12 +4932,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
|
|
|
4732
4932
|
async function previewAndConfirm(config, options) {
|
|
4733
4933
|
const technologyNames = config.technologies.length > 0 ? config.technologies.map((p) => p.name.replace(/ Developer$/, "")).join(", ") : "None";
|
|
4734
4934
|
const agentDisplay = options.agents.find((a) => a.value === config.agent)?.name ?? config.agent;
|
|
4735
|
-
const
|
|
4736
|
-
const
|
|
4935
|
+
const target = getAgentTarget(config.agent);
|
|
4936
|
+
const instructionFile = target.instructions;
|
|
4937
|
+
const mcpConfigFile = target.mcpConfig;
|
|
4737
4938
|
const serverEntries = Object.entries(config.mcpConfig);
|
|
4738
|
-
const instructionFilePath = (0,
|
|
4739
|
-
const mcpConfigFilePath = (0,
|
|
4740
|
-
const gitignorePath = (0,
|
|
4939
|
+
const instructionFilePath = (0, import_path9.join)(config.projectPath, instructionFile);
|
|
4940
|
+
const mcpConfigFilePath = (0, import_path9.join)(config.projectPath, mcpConfigFile);
|
|
4941
|
+
const gitignorePath = (0, import_path9.join)(config.projectPath, ".gitignore");
|
|
4741
4942
|
const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
|
|
4742
4943
|
const mcpMode = detectMCPFileMode(mcpConfigFilePath);
|
|
4743
4944
|
const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
|
|
@@ -4763,7 +4964,7 @@ async function previewAndConfirm(config, options) {
|
|
|
4763
4964
|
console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
|
|
4764
4965
|
console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
|
|
4765
4966
|
if (config.installFactory) {
|
|
4766
|
-
const factoryExists = (0,
|
|
4967
|
+
const factoryExists = (0, import_fs9.existsSync)((0, import_path9.join)(config.projectPath, "factory"));
|
|
4767
4968
|
const factoryMode = factoryExists ? yellow("overwrite") : green("create");
|
|
4768
4969
|
console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
|
|
4769
4970
|
}
|