@maintainer-pro/ai-cli 0.1.9 → 0.1.12
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/dist/index.cjs +316 -68
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +311 -68
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,7 @@ __export(index_exports, {
|
|
|
62
62
|
createToolValidator: () => createToolValidator,
|
|
63
63
|
defaultAiServerApp: () => defaultAiServerApp,
|
|
64
64
|
detectHostAppsFromFiles: () => detectHostAppsFromFiles,
|
|
65
|
+
detectHostAppsFromFolders: () => detectHostAppsFromFolders,
|
|
65
66
|
ensureAiServerApp: () => ensureAiServerApp,
|
|
66
67
|
ensureProjectDataDir: () => ensureProjectDataDir,
|
|
67
68
|
ensureSingleHost: () => ensureSingleHost,
|
|
@@ -71,8 +72,10 @@ __export(index_exports, {
|
|
|
71
72
|
getProviderPreference: () => getProviderPreference,
|
|
72
73
|
hostAppsCachePath: () => hostAppsCachePath2,
|
|
73
74
|
hostAppsFingerprint: () => hostAppsFingerprint,
|
|
75
|
+
hostAppsFingerprintForFolders: () => hostAppsFingerprintForFolders,
|
|
74
76
|
inspectAndRepairWorkspace: () => inspectAndRepairWorkspace,
|
|
75
77
|
inspectConfigOnly: () => inspectConfigOnly,
|
|
78
|
+
isAiServerApp: () => isAiServerApp,
|
|
76
79
|
isDevMode: () => isDevMode,
|
|
77
80
|
isIgnoredRelative: () => isIgnoredRelative,
|
|
78
81
|
isInsideWorkspace: () => isInsideWorkspace,
|
|
@@ -105,7 +108,9 @@ __export(index_exports, {
|
|
|
105
108
|
sanitizeProjectId: () => sanitizeProjectId,
|
|
106
109
|
saveChatAttachments: () => saveChatAttachments,
|
|
107
110
|
toNextRoute: () => toNextRoute,
|
|
111
|
+
unionHostApps: () => unionHostApps,
|
|
108
112
|
upsertManagedIgnoreFile: () => upsertManagedIgnoreFile,
|
|
113
|
+
withoutAiServerApp: () => withoutAiServerApp,
|
|
109
114
|
writeCollaboraterApps: () => writeCollaboraterApps,
|
|
110
115
|
writeHostAppsCache: () => writeHostAppsCache
|
|
111
116
|
});
|
|
@@ -2601,9 +2606,9 @@ var inspectJsonSchema = import_zod.z.object({
|
|
|
2601
2606
|
name: import_zod.z.string().max(200).optional(),
|
|
2602
2607
|
summary: import_zod.z.string().max(800).optional(),
|
|
2603
2608
|
scripts: import_zod.z.object({
|
|
2604
|
-
ui: import_zod.z.string().min(1).max(
|
|
2605
|
-
backend: import_zod.z.string().min(1).max(
|
|
2606
|
-
app: import_zod.z.string().min(1).max(
|
|
2609
|
+
ui: import_zod.z.string().min(1).max(200).optional(),
|
|
2610
|
+
backend: import_zod.z.string().min(1).max(200).optional(),
|
|
2611
|
+
app: import_zod.z.string().min(1).max(200).optional()
|
|
2607
2612
|
}).optional(),
|
|
2608
2613
|
ports: import_zod.z.object({
|
|
2609
2614
|
ui: import_zod.z.number().int().min(1).max(65535).optional(),
|
|
@@ -2631,12 +2636,12 @@ function setupSystemPrompt(input) {
|
|
|
2631
2636
|
Workspace: ${input.workspaceDir}
|
|
2632
2637
|
App name: ${input.appName || "the app"}
|
|
2633
2638
|
|
|
2634
|
-
Inspect this repository (package.json, README, lockfiles, existing env files, how the UI and API start).
|
|
2639
|
+
Inspect this repository (package.json, README, lockfiles, Maven/Gradle/sbt files, Spring/Play config, existing env files, how the UI and API start).
|
|
2635
2640
|
You MAY edit files to fix setup problems that would block local run or the chat widget (env keys, widget mount, missing config).
|
|
2636
2641
|
Do NOT start long-lived dev servers, tunnels, or install global tools.
|
|
2637
2642
|
Do NOT run \`npm run dev\` or similar.
|
|
2638
2643
|
|
|
2639
|
-
Script names you report MUST already exist in package.json. Omit chat/ai-server scripts.
|
|
2644
|
+
Script names you report MUST already exist in package.json, OR be a real JVM start command (./gradlew bootRun, mvn spring-boot:run, sbt run). Omit chat/ai-server scripts.
|
|
2640
2645
|
|
|
2641
2646
|
End with a short human summary AND one \`\`\`json fence with:
|
|
2642
2647
|
|
|
@@ -2708,7 +2713,21 @@ ${lock}`);
|
|
|
2708
2713
|
"vite.config.mjs",
|
|
2709
2714
|
"next.config.js",
|
|
2710
2715
|
"next.config.mjs",
|
|
2711
|
-
"next.config.ts"
|
|
2716
|
+
"next.config.ts",
|
|
2717
|
+
"pom.xml",
|
|
2718
|
+
"build.gradle",
|
|
2719
|
+
"build.gradle.kts",
|
|
2720
|
+
"build.sbt",
|
|
2721
|
+
"application.yml",
|
|
2722
|
+
"application.yaml",
|
|
2723
|
+
"application.properties",
|
|
2724
|
+
"application.conf",
|
|
2725
|
+
"src/main/resources/application.yml",
|
|
2726
|
+
"src/main/resources/application.yaml",
|
|
2727
|
+
"src/main/resources/application.properties",
|
|
2728
|
+
"src/main/resources/application.conf",
|
|
2729
|
+
"src/main/resources/application-dev.yml",
|
|
2730
|
+
"conf/application.conf"
|
|
2712
2731
|
]) {
|
|
2713
2732
|
const body = readSnippet(import_node_path7.default.join(folder, rel), 2500);
|
|
2714
2733
|
if (body) parts.push(`### ${rel}
|
|
@@ -2736,7 +2755,7 @@ DO NOT edit, create, or delete any files.
|
|
|
2736
2755
|
DO NOT run shell commands, install packages, or start servers.
|
|
2737
2756
|
Only infer how this project runs locally from the excerpts.
|
|
2738
2757
|
|
|
2739
|
-
Script names you report MUST appear in package.json scripts
|
|
2758
|
+
Script names you report MUST appear in package.json scripts, OR be a JVM start command such as \`./gradlew bootRun\`, \`mvn spring-boot:run\`, or \`sbt run\`. Omit chat/ai-server scripts.
|
|
2740
2759
|
|
|
2741
2760
|
Reply with a short human summary AND one \`\`\`json fence with:
|
|
2742
2761
|
|
|
@@ -2744,8 +2763,8 @@ Reply with a short human summary AND one \`\`\`json fence with:
|
|
|
2744
2763
|
"kind": "next" | "vite" | "html" | "empty" | "other",
|
|
2745
2764
|
"name": "package or folder name",
|
|
2746
2765
|
"summary": "one sentence about this project",
|
|
2747
|
-
"scripts": { "ui": "dev:client", "backend": "
|
|
2748
|
-
"ports": { "ui": 5173, "backend":
|
|
2766
|
+
"scripts": { "ui": "dev:client", "backend": "sbt run" },
|
|
2767
|
+
"ports": { "ui": 5173, "backend": 9000 },
|
|
2749
2768
|
"issues": ["uncertainties"],
|
|
2750
2769
|
"fixes": [],
|
|
2751
2770
|
"ready": true
|
|
@@ -2878,6 +2897,9 @@ function dataInput(folder, extra) {
|
|
|
2878
2897
|
function hostAppsCachePath2(folder, extra) {
|
|
2879
2898
|
return hostAppsCachePath(dataInput(folder, extra));
|
|
2880
2899
|
}
|
|
2900
|
+
function isAiServerApp(app) {
|
|
2901
|
+
return Boolean(app && (app.role === "ai-server" || app.id === AI_SERVER_APP_ID));
|
|
2902
|
+
}
|
|
2881
2903
|
function defaultAiServerApp(port = AI_SERVER_DEFAULT_PORT) {
|
|
2882
2904
|
return {
|
|
2883
2905
|
id: AI_SERVER_APP_ID,
|
|
@@ -2908,7 +2930,8 @@ function normalizeHostApp(raw) {
|
|
|
2908
2930
|
row.source
|
|
2909
2931
|
) ? row.source : "manual",
|
|
2910
2932
|
locked: row.locked === true || id === AI_SERVER_APP_ID || role === "ai-server",
|
|
2911
|
-
host: role !== "ai-server" && id !== AI_SERVER_APP_ID && row.host === true
|
|
2933
|
+
host: role !== "ai-server" && id !== AI_SERVER_APP_ID && row.host === true,
|
|
2934
|
+
folderPath: typeof row.folderPath === "string" && row.folderPath.trim() ? row.folderPath.trim().slice(0, 500) : null
|
|
2912
2935
|
};
|
|
2913
2936
|
}
|
|
2914
2937
|
function ensureSingleHost(apps) {
|
|
@@ -2938,24 +2961,17 @@ function normalizeHostApps(raw) {
|
|
|
2938
2961
|
const apps = [];
|
|
2939
2962
|
for (const item of raw) {
|
|
2940
2963
|
const app = normalizeHostApp(item);
|
|
2941
|
-
if (!app || seen.has(app.id)) continue;
|
|
2964
|
+
if (!app || isAiServerApp(app) || seen.has(app.id)) continue;
|
|
2942
2965
|
seen.add(app.id);
|
|
2943
2966
|
apps.push(app);
|
|
2944
2967
|
}
|
|
2945
2968
|
return apps;
|
|
2946
2969
|
}
|
|
2947
|
-
function
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
existing.role = "ai-server";
|
|
2953
|
-
existing.locked = true;
|
|
2954
|
-
existing.host = false;
|
|
2955
|
-
existing.name = existing.name || "AI server";
|
|
2956
|
-
return ensureSingleHost([existing, ...next.filter((app) => app !== existing)]);
|
|
2957
|
-
}
|
|
2958
|
-
return ensureSingleHost([defaultAiServerApp(preferredPort), ...next]);
|
|
2970
|
+
function withoutAiServerApp(apps) {
|
|
2971
|
+
return ensureSingleHost(normalizeHostApps(apps).filter((app) => !isAiServerApp(app)));
|
|
2972
|
+
}
|
|
2973
|
+
function ensureAiServerApp(apps, _preferredPort = AI_SERVER_DEFAULT_PORT) {
|
|
2974
|
+
return withoutAiServerApp(apps);
|
|
2959
2975
|
}
|
|
2960
2976
|
function readText(file) {
|
|
2961
2977
|
try {
|
|
@@ -3021,19 +3037,151 @@ function configPort(folder, files) {
|
|
|
3021
3037
|
}
|
|
3022
3038
|
return 0;
|
|
3023
3039
|
}
|
|
3024
|
-
|
|
3040
|
+
var JVM_CONFIG_FILES = [
|
|
3041
|
+
"pom.xml",
|
|
3042
|
+
"build.gradle",
|
|
3043
|
+
"build.gradle.kts",
|
|
3044
|
+
"settings.gradle",
|
|
3045
|
+
"settings.gradle.kts",
|
|
3046
|
+
"build.sbt",
|
|
3047
|
+
"gradle.properties",
|
|
3048
|
+
"application.yml",
|
|
3049
|
+
"application.yaml",
|
|
3050
|
+
"application.properties",
|
|
3051
|
+
"application.conf",
|
|
3052
|
+
"src/main/resources/application.yml",
|
|
3053
|
+
"src/main/resources/application.yaml",
|
|
3054
|
+
"src/main/resources/application.properties",
|
|
3055
|
+
"src/main/resources/application.conf",
|
|
3056
|
+
"src/main/resources/application-dev.yml",
|
|
3057
|
+
"src/main/resources/application-dev.yaml",
|
|
3058
|
+
"src/main/resources/application-dev.properties",
|
|
3059
|
+
"src/main/resources/application-local.yml",
|
|
3060
|
+
"conf/application.conf",
|
|
3061
|
+
"conf/application.yml"
|
|
3062
|
+
];
|
|
3063
|
+
var FINGERPRINT_FILES = [
|
|
3064
|
+
"package.json",
|
|
3065
|
+
...ENV_FILES,
|
|
3066
|
+
"vite.config.ts",
|
|
3067
|
+
"vite.config.js",
|
|
3068
|
+
"vite.config.mjs",
|
|
3069
|
+
"next.config.js",
|
|
3070
|
+
"next.config.mjs",
|
|
3071
|
+
"next.config.ts",
|
|
3072
|
+
...JVM_CONFIG_FILES
|
|
3073
|
+
];
|
|
3074
|
+
function fileExists(folder, rel) {
|
|
3075
|
+
return import_node_fs4.default.existsSync(import_node_path8.default.join(folder, rel));
|
|
3076
|
+
}
|
|
3077
|
+
function jvmPortFromText(text) {
|
|
3078
|
+
const patterns = [
|
|
3079
|
+
/(?:^|\n)\s*server\.port\s*[:=]\s*["']?(\d{2,5})/im,
|
|
3080
|
+
/(?:^|\n)\s*SERVER_PORT\s*[:=]\s*["']?(\d{2,5})/im,
|
|
3081
|
+
/(?:^|\n)\s*play\.http\.port\s*[:=]\s*["']?(\d{2,5})/im,
|
|
3082
|
+
/(?:^|\n)\s*play\.server\.http\.port\s*[:=]\s*["']?(\d{2,5})/im,
|
|
3083
|
+
/(?:^|\n)\s*http\.port\s*[:=]\s*["']?(\d{2,5})/im,
|
|
3084
|
+
/-Dserver\.port=(\d{2,5})/i,
|
|
3085
|
+
/systemProperty\s*\(?\s*["']server\.port["']\s*,\s*["']?(\d{2,5})/i,
|
|
3086
|
+
/["']play\.http\.port["']\s*->\s*["']?(\d{2,5})/i
|
|
3087
|
+
];
|
|
3088
|
+
for (const pattern of patterns) {
|
|
3089
|
+
const match = text.match(pattern);
|
|
3090
|
+
const port = parsePort(match?.[1]);
|
|
3091
|
+
if (port) return port;
|
|
3092
|
+
}
|
|
3093
|
+
const yaml = text.match(
|
|
3094
|
+
/(?:^|\n)server:\s*\n(?:[ \t].*\n)*?[ \t]+port:\s*["']?(\d{2,5})/i
|
|
3095
|
+
);
|
|
3096
|
+
return parsePort(yaml?.[1]);
|
|
3097
|
+
}
|
|
3098
|
+
function looksLike(haystack, ...needles) {
|
|
3099
|
+
const lower = haystack.toLowerCase();
|
|
3100
|
+
return needles.some((needle) => lower.includes(needle.toLowerCase()));
|
|
3101
|
+
}
|
|
3102
|
+
function wrapperCommand(folder, unixName, winName, fallback) {
|
|
3103
|
+
if (process.platform === "win32") {
|
|
3104
|
+
return fileExists(folder, winName) ? `.\\${winName}` : fallback;
|
|
3105
|
+
}
|
|
3106
|
+
return fileExists(folder, unixName) ? `./${unixName}` : fallback;
|
|
3107
|
+
}
|
|
3108
|
+
function detectJvmHostApp(folder, opts = {}) {
|
|
3109
|
+
const resolved = import_node_path8.default.resolve(folder);
|
|
3110
|
+
const texts = [];
|
|
3111
|
+
let foundMarker = false;
|
|
3112
|
+
for (const rel of JVM_CONFIG_FILES) {
|
|
3113
|
+
const text = readText(import_node_path8.default.join(resolved, rel));
|
|
3114
|
+
if (!text) continue;
|
|
3115
|
+
foundMarker = true;
|
|
3116
|
+
texts.push(text);
|
|
3117
|
+
}
|
|
3118
|
+
const hasGradle = fileExists(resolved, "build.gradle") || fileExists(resolved, "build.gradle.kts") || fileExists(resolved, "settings.gradle") || fileExists(resolved, "settings.gradle.kts") || fileExists(resolved, "gradlew") || fileExists(resolved, "gradlew.bat");
|
|
3119
|
+
const hasMaven = fileExists(resolved, "pom.xml");
|
|
3120
|
+
const hasSbt = fileExists(resolved, "build.sbt");
|
|
3121
|
+
if (!foundMarker && !hasGradle && !hasMaven && !hasSbt) return null;
|
|
3122
|
+
const blob = texts.join("\n");
|
|
3123
|
+
const { merged } = readProjectEnvLayers(resolved);
|
|
3124
|
+
const envPort = parsePort(merged.SERVER_PORT) || parsePort(merged.PLAY_HTTP_PORT) || parsePort(merged.HTTP_PORT) || parsePort(merged.PORT);
|
|
3125
|
+
let port = envPort || jvmPortFromText(blob);
|
|
3126
|
+
const isPlay = looksLike(
|
|
3127
|
+
blob,
|
|
3128
|
+
"play.http.port",
|
|
3129
|
+
"enablePlugins(Play",
|
|
3130
|
+
"com.typesafe.play",
|
|
3131
|
+
"play-scala",
|
|
3132
|
+
"play-java"
|
|
3133
|
+
);
|
|
3134
|
+
const isSpring = looksLike(
|
|
3135
|
+
blob,
|
|
3136
|
+
"spring-boot",
|
|
3137
|
+
"springframework.boot",
|
|
3138
|
+
"org.springframework"
|
|
3139
|
+
);
|
|
3140
|
+
const isQuarkus = looksLike(blob, "quarkus", "io.quarkus");
|
|
3141
|
+
if (!port) {
|
|
3142
|
+
port = isPlay ? 9e3 : 8080;
|
|
3143
|
+
}
|
|
3144
|
+
let startCommand = null;
|
|
3145
|
+
if (hasGradle) {
|
|
3146
|
+
const gradle = wrapperCommand(resolved, "gradlew", "gradlew.bat", "gradle");
|
|
3147
|
+
startCommand = isQuarkus ? `${gradle} quarkusDev` : isPlay ? `${gradle} run` : `${gradle} bootRun`;
|
|
3148
|
+
} else if (hasMaven) {
|
|
3149
|
+
const mvn = wrapperCommand(resolved, "mvnw", "mvnw.cmd", "mvn");
|
|
3150
|
+
startCommand = isQuarkus ? `${mvn} quarkus:dev` : `${mvn} spring-boot:run`;
|
|
3151
|
+
} else if (hasSbt) {
|
|
3152
|
+
startCommand = "sbt run";
|
|
3153
|
+
}
|
|
3154
|
+
const framework = isPlay ? "Play" : isSpring ? "Spring" : isQuarkus ? "Quarkus" : hasSbt ? "Scala" : "JVM";
|
|
3155
|
+
return {
|
|
3156
|
+
id: "backend",
|
|
3157
|
+
name: opts.appName ? `${opts.appName} API` : `${framework} backend`,
|
|
3158
|
+
role: "backend",
|
|
3159
|
+
port,
|
|
3160
|
+
startCommand,
|
|
3161
|
+
source: envPort ? "env" : "package",
|
|
3162
|
+
folderPath: resolved
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
function uniqueWorkspaceDirs(primary, extra) {
|
|
3166
|
+
const out = [];
|
|
3167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3168
|
+
for (const raw of [primary, ...extra || []]) {
|
|
3169
|
+
if (!raw || typeof raw !== "string") continue;
|
|
3170
|
+
const resolved = import_node_path8.default.resolve(raw.trim());
|
|
3171
|
+
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
3172
|
+
if (seen.has(key)) continue;
|
|
3173
|
+
seen.add(key);
|
|
3174
|
+
out.push(resolved);
|
|
3175
|
+
}
|
|
3176
|
+
return out;
|
|
3177
|
+
}
|
|
3178
|
+
function folderSlug(folder) {
|
|
3179
|
+
return import_node_path8.default.basename(import_node_path8.default.resolve(folder)).replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24) || "app";
|
|
3180
|
+
}
|
|
3181
|
+
function fingerprintOneFolder(folder) {
|
|
3025
3182
|
const resolved = import_node_path8.default.resolve(folder);
|
|
3026
3183
|
const parts = [];
|
|
3027
|
-
for (const rel of
|
|
3028
|
-
"package.json",
|
|
3029
|
-
...ENV_FILES,
|
|
3030
|
-
"vite.config.ts",
|
|
3031
|
-
"vite.config.js",
|
|
3032
|
-
"vite.config.mjs",
|
|
3033
|
-
"next.config.js",
|
|
3034
|
-
"next.config.mjs",
|
|
3035
|
-
"next.config.ts"
|
|
3036
|
-
]) {
|
|
3184
|
+
for (const rel of FINGERPRINT_FILES) {
|
|
3037
3185
|
const file = import_node_path8.default.join(resolved, rel);
|
|
3038
3186
|
if (!import_node_fs4.default.existsSync(file)) continue;
|
|
3039
3187
|
try {
|
|
@@ -3042,7 +3190,16 @@ function hostAppsFingerprint(folder) {
|
|
|
3042
3190
|
} catch {
|
|
3043
3191
|
}
|
|
3044
3192
|
}
|
|
3045
|
-
return
|
|
3193
|
+
return parts.join("|") || resolved;
|
|
3194
|
+
}
|
|
3195
|
+
function hostAppsFingerprint(folder) {
|
|
3196
|
+
const resolved = import_node_path8.default.resolve(folder);
|
|
3197
|
+
return (0, import_node_crypto4.createHash)("sha256").update(fingerprintOneFolder(resolved)).digest("hex").slice(0, 32);
|
|
3198
|
+
}
|
|
3199
|
+
function hostAppsFingerprintForFolders(folders) {
|
|
3200
|
+
const dirs = uniqueWorkspaceDirs(folders[0] || "", folders.slice(1));
|
|
3201
|
+
const parts = dirs.map((dir) => `${dir}:${fingerprintOneFolder(dir)}`);
|
|
3202
|
+
return (0, import_node_crypto4.createHash)("sha256").update(parts.join("||") || "empty").digest("hex").slice(0, 32);
|
|
3046
3203
|
}
|
|
3047
3204
|
function pickScript(scripts, names) {
|
|
3048
3205
|
return names.find((name) => scripts[name] && !isChatScript(name, scripts[name])) || null;
|
|
@@ -3056,14 +3213,15 @@ function detectHostAppsFromFiles(folder, opts = {}) {
|
|
|
3056
3213
|
const apps = [];
|
|
3057
3214
|
const envAi = parsePort(merged.AI_SERVER_PORT) || parsePort(merged.AI_SERVER_URL);
|
|
3058
3215
|
const aiPort = envAi || parsePort(opts.preferredAiPort, AI_SERVER_DEFAULT_PORT);
|
|
3059
|
-
apps.push({
|
|
3060
|
-
...defaultAiServerApp(aiPort),
|
|
3061
|
-
source: envAi ? "env" : "default"
|
|
3062
|
-
});
|
|
3063
3216
|
const envPorts = {
|
|
3064
|
-
PORT: layers.filter((layer) => layer.file !== ".env.example").map((layer) => ({
|
|
3217
|
+
PORT: layers.filter((layer) => layer.file !== ".env.example").map((layer) => ({
|
|
3218
|
+
file: layer.file,
|
|
3219
|
+
port: parsePort(layer.values.PORT) || parsePort(layer.values.SERVER_PORT) || parsePort(layer.values.PLAY_HTTP_PORT)
|
|
3220
|
+
})).filter((row) => row.port),
|
|
3065
3221
|
APP_URL: parsePort(merged.APP_URL || merged.NEXT_PUBLIC_APP_URL || merged.PUBLIC_URL),
|
|
3066
|
-
API_PORT: parsePort(
|
|
3222
|
+
API_PORT: parsePort(
|
|
3223
|
+
merged.API_PORT || merged.API_URL || merged.VITE_API_URL || merged.SERVER_PORT || merged.PLAY_HTTP_PORT || merged.HTTP_PORT
|
|
3224
|
+
)
|
|
3067
3225
|
};
|
|
3068
3226
|
const portValues = [...new Set(envPorts.PORT.map((row) => row.port))];
|
|
3069
3227
|
if (portValues.length > 1) {
|
|
@@ -3108,7 +3266,8 @@ function detectHostAppsFromFiles(folder, opts = {}) {
|
|
|
3108
3266
|
port,
|
|
3109
3267
|
startCommand: `npm run ${uiScript}`,
|
|
3110
3268
|
source: uiFromEnv ? "env" : scriptPort ? "package" : "package",
|
|
3111
|
-
host: true
|
|
3269
|
+
host: true,
|
|
3270
|
+
folderPath: resolved
|
|
3112
3271
|
});
|
|
3113
3272
|
} else if (uiFromEnv) {
|
|
3114
3273
|
apps.push({
|
|
@@ -3118,7 +3277,8 @@ function detectHostAppsFromFiles(folder, opts = {}) {
|
|
|
3118
3277
|
port: uiFromEnv,
|
|
3119
3278
|
startCommand: scripts.dev ? "npm run dev" : null,
|
|
3120
3279
|
source: "env",
|
|
3121
|
-
host: true
|
|
3280
|
+
host: true,
|
|
3281
|
+
folderPath: resolved
|
|
3122
3282
|
});
|
|
3123
3283
|
}
|
|
3124
3284
|
if (apiScript && apiScript !== uiScript) {
|
|
@@ -3130,19 +3290,80 @@ function detectHostAppsFromFiles(folder, opts = {}) {
|
|
|
3130
3290
|
role: "backend",
|
|
3131
3291
|
port,
|
|
3132
3292
|
startCommand: `npm run ${apiScript}`,
|
|
3133
|
-
source: envPorts.API_PORT ? "env" : "package"
|
|
3293
|
+
source: envPorts.API_PORT ? "env" : "package",
|
|
3294
|
+
folderPath: resolved
|
|
3134
3295
|
});
|
|
3135
3296
|
}
|
|
3136
|
-
const
|
|
3297
|
+
const hasHostApp = apps.some((app) => app.role !== "ai-server");
|
|
3298
|
+
const usedPorts = new Set(
|
|
3299
|
+
apps.filter((app) => app.role !== "ai-server").map((app) => app.port)
|
|
3300
|
+
);
|
|
3301
|
+
const jvm = detectJvmHostApp(resolved, { appName: opts.appName });
|
|
3302
|
+
if (jvm && !usedPorts.has(jvm.port)) {
|
|
3303
|
+
apps.push({
|
|
3304
|
+
...jvm,
|
|
3305
|
+
id: apps.some((app) => app.id === "backend") ? `backend-${folderSlug(resolved)}` : "backend",
|
|
3306
|
+
host: !hasHostApp
|
|
3307
|
+
});
|
|
3308
|
+
} else if (jvm && usedPorts.has(jvm.port) && !hasHostApp) {
|
|
3309
|
+
reasons.push(
|
|
3310
|
+
`JVM config suggests port ${jvm.port}, which is already used by another detected app`
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
const tagged = apps.map(
|
|
3314
|
+
(app) => app.role === "ai-server" || app.folderPath ? app : { ...app, folderPath: resolved }
|
|
3315
|
+
);
|
|
3316
|
+
const hostApps = tagged.filter((app) => app.role !== "ai-server");
|
|
3137
3317
|
if (pkg && Object.keys(scripts).length && hostApps.length === 0) {
|
|
3138
3318
|
reasons.push("Found package.json scripts but no host app port in env or config");
|
|
3139
3319
|
}
|
|
3140
3320
|
return {
|
|
3141
|
-
apps:
|
|
3321
|
+
apps: withoutAiServerApp(tagged),
|
|
3142
3322
|
reasons,
|
|
3143
3323
|
confused: reasons.length > 0
|
|
3144
3324
|
};
|
|
3145
3325
|
}
|
|
3326
|
+
function detectHostAppsFromFolders(folders, opts = {}) {
|
|
3327
|
+
const dirs = uniqueWorkspaceDirs(folders[0] || "", folders.slice(1));
|
|
3328
|
+
const reasons = [];
|
|
3329
|
+
let confused = false;
|
|
3330
|
+
const apps = [];
|
|
3331
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
3332
|
+
const usedPortSet = /* @__PURE__ */ new Set();
|
|
3333
|
+
const primary = dirs[0];
|
|
3334
|
+
for (const folder of dirs) {
|
|
3335
|
+
const detected = detectHostAppsFromFiles(folder, opts);
|
|
3336
|
+
confused = confused || detected.confused;
|
|
3337
|
+
const base = import_node_path8.default.basename(folder);
|
|
3338
|
+
for (const reason of detected.reasons) {
|
|
3339
|
+
reasons.push(dirs.length > 1 ? `${base}: ${reason}` : reason);
|
|
3340
|
+
}
|
|
3341
|
+
for (const app of detected.apps) {
|
|
3342
|
+
if (isAiServerApp(app)) continue;
|
|
3343
|
+
let id = app.id;
|
|
3344
|
+
if (usedIds.has(id)) id = `${app.id}-${folderSlug(folder)}`;
|
|
3345
|
+
usedIds.add(id);
|
|
3346
|
+
if (usedPortSet.has(app.port)) {
|
|
3347
|
+
reasons.push(
|
|
3348
|
+
`${base}: port ${app.port} is also used by another attached folder`
|
|
3349
|
+
);
|
|
3350
|
+
confused = true;
|
|
3351
|
+
}
|
|
3352
|
+
usedPortSet.add(app.port);
|
|
3353
|
+
apps.push({
|
|
3354
|
+
...app,
|
|
3355
|
+
id,
|
|
3356
|
+
folderPath: import_node_path8.default.resolve(folder),
|
|
3357
|
+
name: dirs.length > 1 && primary && folder !== primary && !app.name.includes(base) ? `${app.name} (${base})` : app.name
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
return {
|
|
3362
|
+
apps: withoutAiServerApp(apps),
|
|
3363
|
+
reasons,
|
|
3364
|
+
confused
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3146
3367
|
function readHostAppsCache(folder, extra) {
|
|
3147
3368
|
try {
|
|
3148
3369
|
const raw = JSON.parse(
|
|
@@ -3187,12 +3408,37 @@ function mergeDesiredHostApps(desired, detected) {
|
|
|
3187
3408
|
...local,
|
|
3188
3409
|
...app,
|
|
3189
3410
|
startCommand: app.startCommand || local?.startCommand || null,
|
|
3411
|
+
folderPath: app.folderPath || local?.folderPath || null,
|
|
3190
3412
|
locked: app.locked || app.id === AI_SERVER_APP_ID || app.role === "ai-server",
|
|
3191
3413
|
host: app.role === "ai-server" || app.id === AI_SERVER_APP_ID ? false : app.host === true || app.host !== false && local?.host === true
|
|
3192
3414
|
};
|
|
3193
3415
|
});
|
|
3194
3416
|
return ensureAiServerApp(merged);
|
|
3195
3417
|
}
|
|
3418
|
+
function unionHostApps(desired, detected) {
|
|
3419
|
+
const merged = mergeDesiredHostApps(desired, detected);
|
|
3420
|
+
const seenIds = new Set(merged.map((app) => app.id));
|
|
3421
|
+
const seenPorts = new Set(merged.map((app) => app.port));
|
|
3422
|
+
for (const app of detected) {
|
|
3423
|
+
if (app.role === "ai-server") continue;
|
|
3424
|
+
if (seenIds.has(app.id) || seenPorts.has(app.port)) continue;
|
|
3425
|
+
seenIds.add(app.id);
|
|
3426
|
+
seenPorts.add(app.port);
|
|
3427
|
+
merged.push(app);
|
|
3428
|
+
}
|
|
3429
|
+
return ensureAiServerApp(merged);
|
|
3430
|
+
}
|
|
3431
|
+
function startCommandFromScript(script) {
|
|
3432
|
+
if (!script || !script.trim()) return null;
|
|
3433
|
+
const value = script.trim();
|
|
3434
|
+
if (/\s/.test(value) || /[\\/]/.test(value) || value.includes(":")) {
|
|
3435
|
+
if (/^(npm|pnpm|yarn|npx)\s/i.test(value)) return value;
|
|
3436
|
+
if (/^(sbt|mvn|gradle|gradlew|mvnw|\.\\|\.\/)/i.test(value) || /\s/.test(value)) {
|
|
3437
|
+
return value;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
return `npm run ${value}`;
|
|
3441
|
+
}
|
|
3196
3442
|
function appsFromInspectPorts(ports, scripts, name) {
|
|
3197
3443
|
const apps = [];
|
|
3198
3444
|
if (ports.ui || scripts.ui) {
|
|
@@ -3201,7 +3447,7 @@ function appsFromInspectPorts(ports, scripts, name) {
|
|
|
3201
3447
|
name,
|
|
3202
3448
|
role: "ui",
|
|
3203
3449
|
port: parsePort(ports.ui, 5173),
|
|
3204
|
-
startCommand: scripts.ui
|
|
3450
|
+
startCommand: startCommandFromScript(scripts.ui),
|
|
3205
3451
|
source: "ai"
|
|
3206
3452
|
});
|
|
3207
3453
|
}
|
|
@@ -3210,8 +3456,8 @@ function appsFromInspectPorts(ports, scripts, name) {
|
|
|
3210
3456
|
id: "backend",
|
|
3211
3457
|
name: "Backend",
|
|
3212
3458
|
role: "backend",
|
|
3213
|
-
port: parsePort(ports.backend,
|
|
3214
|
-
startCommand: scripts.backend
|
|
3459
|
+
port: parsePort(ports.backend, 8080),
|
|
3460
|
+
startCommand: startCommandFromScript(scripts.backend),
|
|
3215
3461
|
source: "ai"
|
|
3216
3462
|
});
|
|
3217
3463
|
}
|
|
@@ -3221,7 +3467,7 @@ function appsFromInspectPorts(ports, scripts, name) {
|
|
|
3221
3467
|
name,
|
|
3222
3468
|
role: "app",
|
|
3223
3469
|
port: parsePort(ports.app, 3e3),
|
|
3224
|
-
startCommand: scripts.app
|
|
3470
|
+
startCommand: startCommandFromScript(scripts.app),
|
|
3225
3471
|
source: "ai"
|
|
3226
3472
|
});
|
|
3227
3473
|
}
|
|
@@ -3299,11 +3545,12 @@ function projectSummaryFromDetect(folder, apps, opts = {}) {
|
|
|
3299
3545
|
return `${name}: ${hostApps.map((app) => `${app.name} on ${app.port}${app.startCommand ? ` (${app.startCommand})` : ""}`).join(", ")}.`;
|
|
3300
3546
|
}
|
|
3301
3547
|
async function proposeHostAppsFromConfig(input) {
|
|
3302
|
-
const
|
|
3548
|
+
const dirs = uniqueWorkspaceDirs(input.workspaceDir, input.workspaceDirs);
|
|
3549
|
+
const folder = dirs[0] || import_node_path8.default.resolve(input.workspaceDir);
|
|
3303
3550
|
const preferredAi = parsePort(input.preferredAiPort, AI_SERVER_DEFAULT_PORT);
|
|
3304
|
-
const fingerprint =
|
|
3551
|
+
const fingerprint = hostAppsFingerprintForFolders(dirs);
|
|
3305
3552
|
const allowAi = input.allowAi !== false;
|
|
3306
|
-
const detected =
|
|
3553
|
+
const detected = detectHostAppsFromFolders(dirs, {
|
|
3307
3554
|
preferredAiPort: preferredAi,
|
|
3308
3555
|
appName: input.appName
|
|
3309
3556
|
});
|
|
@@ -3322,8 +3569,9 @@ async function proposeHostAppsFromConfig(input) {
|
|
|
3322
3569
|
appName: input.appName,
|
|
3323
3570
|
extraContext: [
|
|
3324
3571
|
"File-based detection was uncertain or incomplete.",
|
|
3572
|
+
dirs.length > 1 ? `This sandbox also has folders: ${dirs.slice(1).join(", ")}` : "",
|
|
3325
3573
|
...reasons
|
|
3326
|
-
].join("\n")
|
|
3574
|
+
].filter(Boolean).join("\n")
|
|
3327
3575
|
});
|
|
3328
3576
|
usedAi = true;
|
|
3329
3577
|
const fromAi = appsFromInspectPorts(
|
|
@@ -3332,10 +3580,7 @@ async function proposeHostAppsFromConfig(input) {
|
|
|
3332
3580
|
inspected.name || input.appName || "App"
|
|
3333
3581
|
);
|
|
3334
3582
|
if (fromAi.length) {
|
|
3335
|
-
apps =
|
|
3336
|
-
[defaultAiServerApp(preferredAi), ...fromAi],
|
|
3337
|
-
preferredAi
|
|
3338
|
-
);
|
|
3583
|
+
apps = unionHostApps(fromAi, detected.apps);
|
|
3339
3584
|
}
|
|
3340
3585
|
if (inspected.summary) projectSummary = inspected.summary;
|
|
3341
3586
|
if (inspected.issues?.length) {
|
|
@@ -3347,9 +3592,9 @@ async function proposeHostAppsFromConfig(input) {
|
|
|
3347
3592
|
);
|
|
3348
3593
|
}
|
|
3349
3594
|
}
|
|
3350
|
-
const alternatives =
|
|
3351
|
-
appName: input.appName
|
|
3352
|
-
|
|
3595
|
+
const alternatives = dirs.flatMap(
|
|
3596
|
+
(dir) => collectScriptAlternatives(dir, apps, { appName: input.appName })
|
|
3597
|
+
);
|
|
3353
3598
|
const confidence = proposalConfidence(apps, reasons, usedAi);
|
|
3354
3599
|
const needsReview = confidence !== "high" || detected.confused || apps.filter((app) => app.role !== "ai-server").length === 0;
|
|
3355
3600
|
return {
|
|
@@ -3364,11 +3609,12 @@ async function proposeHostAppsFromConfig(input) {
|
|
|
3364
3609
|
};
|
|
3365
3610
|
}
|
|
3366
3611
|
async function resolveHostApps(input) {
|
|
3367
|
-
const
|
|
3612
|
+
const dirs = uniqueWorkspaceDirs(input.workspaceDir, input.workspaceDirs);
|
|
3613
|
+
const folder = dirs[0] || import_node_path8.default.resolve(input.workspaceDir);
|
|
3368
3614
|
const loc = { sandboxId: input.sandboxId, dataDir: input.dataDir };
|
|
3369
|
-
const fingerprint =
|
|
3615
|
+
const fingerprint = hostAppsFingerprintForFolders(dirs);
|
|
3370
3616
|
const preferredAi = parsePort(input.preferredAiPort, AI_SERVER_DEFAULT_PORT);
|
|
3371
|
-
const detected =
|
|
3617
|
+
const detected = detectHostAppsFromFolders(dirs, {
|
|
3372
3618
|
preferredAiPort: preferredAi,
|
|
3373
3619
|
appName: input.appName
|
|
3374
3620
|
});
|
|
@@ -3418,10 +3664,7 @@ Return the real local ports and npm script names.`
|
|
|
3418
3664
|
inspected.scripts,
|
|
3419
3665
|
inspected.name || input.appName || "App"
|
|
3420
3666
|
);
|
|
3421
|
-
const apps2 =
|
|
3422
|
-
[defaultAiServerApp(preferredAi), ...fromAi],
|
|
3423
|
-
preferredAi
|
|
3424
|
-
);
|
|
3667
|
+
const apps2 = withoutAiServerApp(fromAi);
|
|
3425
3668
|
writeHostAppsCache(
|
|
3426
3669
|
folder,
|
|
3427
3670
|
apps2,
|
|
@@ -3504,6 +3747,7 @@ Return the real local ports and npm script names.`
|
|
|
3504
3747
|
createToolValidator,
|
|
3505
3748
|
defaultAiServerApp,
|
|
3506
3749
|
detectHostAppsFromFiles,
|
|
3750
|
+
detectHostAppsFromFolders,
|
|
3507
3751
|
ensureAiServerApp,
|
|
3508
3752
|
ensureProjectDataDir,
|
|
3509
3753
|
ensureSingleHost,
|
|
@@ -3513,8 +3757,10 @@ Return the real local ports and npm script names.`
|
|
|
3513
3757
|
getProviderPreference,
|
|
3514
3758
|
hostAppsCachePath,
|
|
3515
3759
|
hostAppsFingerprint,
|
|
3760
|
+
hostAppsFingerprintForFolders,
|
|
3516
3761
|
inspectAndRepairWorkspace,
|
|
3517
3762
|
inspectConfigOnly,
|
|
3763
|
+
isAiServerApp,
|
|
3518
3764
|
isDevMode,
|
|
3519
3765
|
isIgnoredRelative,
|
|
3520
3766
|
isInsideWorkspace,
|
|
@@ -3547,7 +3793,9 @@ Return the real local ports and npm script names.`
|
|
|
3547
3793
|
sanitizeProjectId,
|
|
3548
3794
|
saveChatAttachments,
|
|
3549
3795
|
toNextRoute,
|
|
3796
|
+
unionHostApps,
|
|
3550
3797
|
upsertManagedIgnoreFile,
|
|
3798
|
+
withoutAiServerApp,
|
|
3551
3799
|
writeCollaboraterApps,
|
|
3552
3800
|
writeHostAppsCache
|
|
3553
3801
|
});
|