@prover-coder-ai/docker-git 1.0.37 → 1.0.39
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/README.md +30 -228
- package/dist/src/docker-git/main.js +272 -138
- package/dist/src/docker-git/main.js.map +1 -1
- package/package.json +1 -1
|
@@ -971,6 +971,191 @@ const logDockerAccessInfo = (cwd, config) => ensureDockerDnsHost(cwd, config.con
|
|
|
971
971
|
Effect.zipRight(logDockerDnsAccess(config)),
|
|
972
972
|
Effect.zipRight(logContainerIpAccess(cwd, config))
|
|
973
973
|
);
|
|
974
|
+
const normalizeAccountLabel = (value, fallback) => {
|
|
975
|
+
const trimmed = value?.trim() ?? "";
|
|
976
|
+
if (trimmed.length === 0) {
|
|
977
|
+
return fallback;
|
|
978
|
+
}
|
|
979
|
+
const normalized = trimmed.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
|
|
980
|
+
const withoutLeading = trimLeftChar(normalized, "-");
|
|
981
|
+
const cleaned = trimRightChar(withoutLeading, "-");
|
|
982
|
+
return cleaned.length > 0 ? cleaned : fallback;
|
|
983
|
+
};
|
|
984
|
+
const buildDockerAuthSpec = (input) => ({
|
|
985
|
+
cwd: input.cwd,
|
|
986
|
+
image: input.image,
|
|
987
|
+
volume: { hostPath: input.hostPath, containerPath: input.containerPath },
|
|
988
|
+
...typeof input.entrypoint === "string" ? { entrypoint: input.entrypoint } : {},
|
|
989
|
+
...input.env === void 0 ? {} : { env: input.env },
|
|
990
|
+
args: input.args,
|
|
991
|
+
interactive: input.interactive
|
|
992
|
+
});
|
|
993
|
+
const JsonValueSchema = Schema.suspend(
|
|
994
|
+
() => Schema.Union(
|
|
995
|
+
Schema.Null,
|
|
996
|
+
Schema.Boolean,
|
|
997
|
+
Schema.String,
|
|
998
|
+
Schema.JsonNumber,
|
|
999
|
+
Schema.Array(JsonValueSchema),
|
|
1000
|
+
Schema.Record({ key: Schema.String, value: JsonValueSchema })
|
|
1001
|
+
)
|
|
1002
|
+
);
|
|
1003
|
+
const JsonRecordSchema = Schema.Record({
|
|
1004
|
+
key: Schema.String,
|
|
1005
|
+
value: JsonValueSchema
|
|
1006
|
+
});
|
|
1007
|
+
const JsonRecordFromStringSchema = Schema.parseJson(JsonRecordSchema);
|
|
1008
|
+
const defaultEnvContents$1 = "# docker-git env\n# KEY=value\n";
|
|
1009
|
+
const codexConfigMarker = "# docker-git codex config";
|
|
1010
|
+
const defaultCodexConfig = [
|
|
1011
|
+
"# docker-git codex config",
|
|
1012
|
+
'model = "gpt-5.4"',
|
|
1013
|
+
"model_context_window = 1050000",
|
|
1014
|
+
"model_auto_compact_token_limit = 945000",
|
|
1015
|
+
'model_reasoning_effort = "xhigh"',
|
|
1016
|
+
'plan_mode_reasoning_effort = "xhigh"',
|
|
1017
|
+
'personality = "pragmatic"',
|
|
1018
|
+
"",
|
|
1019
|
+
'approval_policy = "never"',
|
|
1020
|
+
'sandbox_mode = "danger-full-access"',
|
|
1021
|
+
'web_search = "live"',
|
|
1022
|
+
"",
|
|
1023
|
+
"[features]",
|
|
1024
|
+
"shell_snapshot = true",
|
|
1025
|
+
"multi_agent = true",
|
|
1026
|
+
"apps = true",
|
|
1027
|
+
"shell_tool = true"
|
|
1028
|
+
].join("\n");
|
|
1029
|
+
const resolvePathFromBase$1 = (path, baseDir, targetPath) => path.isAbsolute(targetPath) ? targetPath : path.resolve(baseDir, targetPath);
|
|
1030
|
+
const isPermissionDeniedSystemError = (error) => error._tag === "SystemError" && error.reason === "PermissionDenied";
|
|
1031
|
+
const skipCodexConfigPermissionDenied = (configPath, error) => isPermissionDeniedSystemError(error) ? Effect.logWarning(
|
|
1032
|
+
`Skipped Codex config sync at ${configPath}: permission denied (${error.description ?? "no details"}).`
|
|
1033
|
+
) : Effect.fail(error);
|
|
1034
|
+
const normalizeConfigText = (text) => text.replaceAll("\r\n", "\n").trim();
|
|
1035
|
+
const shouldRewriteDockerGitCodexConfig = (existing) => {
|
|
1036
|
+
const normalized = normalizeConfigText(existing);
|
|
1037
|
+
if (normalized.length === 0) {
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
if (!normalized.startsWith(codexConfigMarker)) {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
return normalized !== normalizeConfigText(defaultCodexConfig);
|
|
1044
|
+
};
|
|
1045
|
+
const shouldCopyEnv = (sourceText, targetText) => {
|
|
1046
|
+
if (sourceText.trim().length === 0) {
|
|
1047
|
+
return "skip";
|
|
1048
|
+
}
|
|
1049
|
+
if (targetText.trim().length === 0) {
|
|
1050
|
+
return "copy";
|
|
1051
|
+
}
|
|
1052
|
+
if (targetText.trim() === defaultEnvContents$1.trim() && sourceText.trim() !== defaultEnvContents$1.trim()) {
|
|
1053
|
+
return "copy";
|
|
1054
|
+
}
|
|
1055
|
+
return "skip";
|
|
1056
|
+
};
|
|
1057
|
+
const parseJsonRecord = (text) => Either.match(ParseResult.decodeUnknownEither(JsonRecordFromStringSchema)(text), {
|
|
1058
|
+
onLeft: () => Effect.succeed(null),
|
|
1059
|
+
onRight: (record) => Effect.succeed(record)
|
|
1060
|
+
});
|
|
1061
|
+
const hasClaudeOauthAccount = (record) => record !== null && typeof record["oauthAccount"] === "object" && record["oauthAccount"] !== null;
|
|
1062
|
+
const hasClaudeCredentials = (record) => record !== null && typeof record["claudeAiOauth"] === "object" && record["claudeAiOauth"] !== null;
|
|
1063
|
+
const isGithubTokenKey = (key) => key === "GITHUB_TOKEN" || key === "GH_TOKEN" || key.startsWith("GITHUB_TOKEN__");
|
|
1064
|
+
const hasNonEmptyFile = (fs, filePath) => Effect.gen(function* (_) {
|
|
1065
|
+
const exists = yield* _(fs.exists(filePath));
|
|
1066
|
+
if (!exists) {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
const info = yield* _(fs.stat(filePath));
|
|
1070
|
+
if (info.type !== "File") {
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
const text = yield* _(fs.readFileString(filePath), Effect.orElseSucceed(() => ""));
|
|
1074
|
+
return text.trim().length > 0;
|
|
1075
|
+
});
|
|
1076
|
+
const autoOptionError = (reason) => ({
|
|
1077
|
+
_tag: "InvalidOption",
|
|
1078
|
+
option: "--auto",
|
|
1079
|
+
reason
|
|
1080
|
+
});
|
|
1081
|
+
const isRegularFile$1 = (fs, filePath) => Effect.gen(function* (_) {
|
|
1082
|
+
const exists = yield* _(fs.exists(filePath));
|
|
1083
|
+
if (!exists) {
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
const info = yield* _(fs.stat(filePath));
|
|
1087
|
+
return info.type === "File";
|
|
1088
|
+
});
|
|
1089
|
+
const hasCodexAuth = (fs, rootPath, label) => {
|
|
1090
|
+
const normalized = normalizeAccountLabel(label ?? null, "default");
|
|
1091
|
+
const authPath = normalized === "default" ? `${rootPath}/auth.json` : `${rootPath}/${normalized}/auth.json`;
|
|
1092
|
+
return hasNonEmptyFile(fs, authPath);
|
|
1093
|
+
};
|
|
1094
|
+
const resolveClaudeAccountPath$1 = (rootPath, label) => {
|
|
1095
|
+
const normalized = normalizeAccountLabel(label ?? null, "default");
|
|
1096
|
+
if (normalized !== "default") {
|
|
1097
|
+
return [`${rootPath}/${normalized}`];
|
|
1098
|
+
}
|
|
1099
|
+
return [rootPath, `${rootPath}/default`];
|
|
1100
|
+
};
|
|
1101
|
+
const hasClaudeAuth = (fs, rootPath, label) => Effect.gen(function* (_) {
|
|
1102
|
+
for (const accountPath of resolveClaudeAccountPath$1(rootPath, label)) {
|
|
1103
|
+
const oauthToken = yield* _(hasNonEmptyFile(fs, `${accountPath}/.oauth-token`));
|
|
1104
|
+
if (oauthToken) {
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
const credentials = yield* _(isRegularFile$1(fs, `${accountPath}/.credentials.json`));
|
|
1108
|
+
if (credentials) {
|
|
1109
|
+
return true;
|
|
1110
|
+
}
|
|
1111
|
+
const nestedCredentials = yield* _(isRegularFile$1(fs, `${accountPath}/.claude/.credentials.json`));
|
|
1112
|
+
if (nestedCredentials) {
|
|
1113
|
+
return true;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return false;
|
|
1117
|
+
});
|
|
1118
|
+
const resolveClaudeRoot = (codexSharedAuthPath) => `${codexSharedAuthPath.slice(0, codexSharedAuthPath.lastIndexOf("/"))}/claude`;
|
|
1119
|
+
const resolveAvailableAgentAuth = (fs, config) => Effect.gen(function* (_) {
|
|
1120
|
+
const claudeAvailable = yield* _(
|
|
1121
|
+
hasClaudeAuth(fs, resolveClaudeRoot(config.codexSharedAuthPath), config.claudeAuthLabel)
|
|
1122
|
+
);
|
|
1123
|
+
const codexAvailable = yield* _(hasCodexAuth(fs, config.codexSharedAuthPath, config.codexAuthLabel));
|
|
1124
|
+
return { claudeAvailable, codexAvailable };
|
|
1125
|
+
});
|
|
1126
|
+
const resolveExplicitAutoAgentMode = (available, mode) => {
|
|
1127
|
+
if (mode === "claude") {
|
|
1128
|
+
return available.claudeAvailable ? Effect.succeed("claude") : Effect.fail(autoOptionError("Claude auth not found"));
|
|
1129
|
+
}
|
|
1130
|
+
if (mode === "codex") {
|
|
1131
|
+
return available.codexAvailable ? Effect.succeed("codex") : Effect.fail(autoOptionError("Codex auth not found"));
|
|
1132
|
+
}
|
|
1133
|
+
return Effect.sync(() => mode);
|
|
1134
|
+
};
|
|
1135
|
+
const pickRandomAutoAgentMode = (available) => {
|
|
1136
|
+
if (!available.claudeAvailable && !available.codexAvailable) {
|
|
1137
|
+
return Effect.fail(autoOptionError("no Claude or Codex auth found"));
|
|
1138
|
+
}
|
|
1139
|
+
if (available.claudeAvailable && !available.codexAvailable) {
|
|
1140
|
+
return Effect.succeed("claude");
|
|
1141
|
+
}
|
|
1142
|
+
if (!available.claudeAvailable && available.codexAvailable) {
|
|
1143
|
+
return Effect.succeed("codex");
|
|
1144
|
+
}
|
|
1145
|
+
return Effect.sync(() => process.hrtime.bigint() % 2n === 0n ? "claude" : "codex");
|
|
1146
|
+
};
|
|
1147
|
+
const resolveAutoAgentMode = (config) => Effect.gen(function* (_) {
|
|
1148
|
+
const fs = yield* _(FileSystem.FileSystem);
|
|
1149
|
+
if (config.agentAuto !== true) {
|
|
1150
|
+
return config.agentMode;
|
|
1151
|
+
}
|
|
1152
|
+
const available = yield* _(resolveAvailableAgentAuth(fs, config));
|
|
1153
|
+
const explicitMode = yield* _(resolveExplicitAutoAgentMode(available, config.agentMode));
|
|
1154
|
+
if (explicitMode !== void 0) {
|
|
1155
|
+
return explicitMode;
|
|
1156
|
+
}
|
|
1157
|
+
return yield* _(pickRandomAutoAgentMode(available));
|
|
1158
|
+
});
|
|
974
1159
|
const formatParseError$1 = (error) => Match.value(error).pipe(
|
|
975
1160
|
Match.when({ _tag: "UnknownCommand" }, ({ command }) => `Unknown command: ${command}`),
|
|
976
1161
|
Match.when({ _tag: "UnknownOption" }, ({ option }) => `Unknown option: ${option}`),
|
|
@@ -1071,25 +1256,6 @@ const renderError = (error) => {
|
|
|
1071
1256
|
}
|
|
1072
1257
|
return renderNonParseError(error);
|
|
1073
1258
|
};
|
|
1074
|
-
const normalizeAccountLabel = (value, fallback) => {
|
|
1075
|
-
const trimmed = value?.trim() ?? "";
|
|
1076
|
-
if (trimmed.length === 0) {
|
|
1077
|
-
return fallback;
|
|
1078
|
-
}
|
|
1079
|
-
const normalized = trimmed.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
|
|
1080
|
-
const withoutLeading = trimLeftChar(normalized, "-");
|
|
1081
|
-
const cleaned = trimRightChar(withoutLeading, "-");
|
|
1082
|
-
return cleaned.length > 0 ? cleaned : fallback;
|
|
1083
|
-
};
|
|
1084
|
-
const buildDockerAuthSpec = (input) => ({
|
|
1085
|
-
cwd: input.cwd,
|
|
1086
|
-
image: input.image,
|
|
1087
|
-
volume: { hostPath: input.hostPath, containerPath: input.containerPath },
|
|
1088
|
-
...typeof input.entrypoint === "string" ? { entrypoint: input.entrypoint } : {},
|
|
1089
|
-
...input.env === void 0 ? {} : { env: input.env },
|
|
1090
|
-
args: input.args,
|
|
1091
|
-
interactive: input.interactive
|
|
1092
|
-
});
|
|
1093
1259
|
const splitLines = (input) => input.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
|
|
1094
1260
|
const joinLines = (lines) => lines.join("\n");
|
|
1095
1261
|
const normalizeEnvText = (input) => {
|
|
@@ -1181,23 +1347,23 @@ const upsertEnvKey = (input, key, value) => {
|
|
|
1181
1347
|
return normalizeEnvText(joinLines([...cleaned, `${trimmedKey}=${value}`]));
|
|
1182
1348
|
};
|
|
1183
1349
|
const removeEnvKey = (input, key) => upsertEnvKey(input, key, "");
|
|
1184
|
-
const defaultEnvContents
|
|
1350
|
+
const defaultEnvContents = "# docker-git env\n# KEY=value\n";
|
|
1185
1351
|
const ensureEnvFile$1 = (fs, path, envPath) => Effect.gen(function* (_) {
|
|
1186
1352
|
const exists = yield* _(fs.exists(envPath));
|
|
1187
1353
|
if (exists) {
|
|
1188
1354
|
return;
|
|
1189
1355
|
}
|
|
1190
1356
|
yield* _(fs.makeDirectory(path.dirname(envPath), { recursive: true }));
|
|
1191
|
-
yield* _(fs.writeFileString(envPath, defaultEnvContents
|
|
1357
|
+
yield* _(fs.writeFileString(envPath, defaultEnvContents));
|
|
1192
1358
|
});
|
|
1193
1359
|
const readEnvText = (fs, envPath) => Effect.gen(function* (_) {
|
|
1194
1360
|
const exists = yield* _(fs.exists(envPath));
|
|
1195
1361
|
if (!exists) {
|
|
1196
|
-
return defaultEnvContents
|
|
1362
|
+
return defaultEnvContents;
|
|
1197
1363
|
}
|
|
1198
1364
|
const info = yield* _(fs.stat(envPath));
|
|
1199
1365
|
if (info.type !== "File") {
|
|
1200
|
-
return defaultEnvContents
|
|
1366
|
+
return defaultEnvContents;
|
|
1201
1367
|
}
|
|
1202
1368
|
return yield* _(fs.readFileString(envPath));
|
|
1203
1369
|
});
|
|
@@ -4135,7 +4301,7 @@ RUN oh-my-opencode --version
|
|
|
4135
4301
|
RUN npm install -g @anthropic-ai/claude-code@latest
|
|
4136
4302
|
RUN claude --version`;
|
|
4137
4303
|
const renderDockerfileOpenCode = () => `# Tooling: OpenCode (binary)
|
|
4138
|
-
RUN curl -fsSL https://opencode.ai/install
|
|
4304
|
+
RUN set -eu; for attempt in 1 2 3 4 5; do if curl -fsSL --retry 5 --retry-all-errors --retry-delay 2 https://opencode.ai/install | HOME=/usr/local bash -s -- --no-modify-path; then exit 0; fi; echo "opencode install attempt \${attempt} failed; retrying..." >&2; sleep $((attempt * 2)); done; echo "opencode install failed after retries" >&2; exit 1
|
|
4139
4305
|
RUN ln -sf /usr/local/.opencode/bin/opencode /usr/local/bin/opencode
|
|
4140
4306
|
RUN opencode --version`;
|
|
4141
4307
|
const gitleaksVersion = "8.28.0";
|
|
@@ -5025,77 +5191,6 @@ const copyDirIfEmpty = (fs, path, sourceDir, targetDir, label) => Effect.gen(fun
|
|
|
5025
5191
|
yield* _(copyDirRecursive(fs, path, sourceDir, targetDir));
|
|
5026
5192
|
yield* _(Effect.log(`Copied ${label} from ${sourceDir} to ${targetDir}`));
|
|
5027
5193
|
});
|
|
5028
|
-
const JsonValueSchema = Schema.suspend(
|
|
5029
|
-
() => Schema.Union(
|
|
5030
|
-
Schema.Null,
|
|
5031
|
-
Schema.Boolean,
|
|
5032
|
-
Schema.String,
|
|
5033
|
-
Schema.JsonNumber,
|
|
5034
|
-
Schema.Array(JsonValueSchema),
|
|
5035
|
-
Schema.Record({ key: Schema.String, value: JsonValueSchema })
|
|
5036
|
-
)
|
|
5037
|
-
);
|
|
5038
|
-
const JsonRecordSchema = Schema.Record({
|
|
5039
|
-
key: Schema.String,
|
|
5040
|
-
value: JsonValueSchema
|
|
5041
|
-
});
|
|
5042
|
-
const JsonRecordFromStringSchema = Schema.parseJson(JsonRecordSchema);
|
|
5043
|
-
const defaultEnvContents = "# docker-git env\n# KEY=value\n";
|
|
5044
|
-
const codexConfigMarker = "# docker-git codex config";
|
|
5045
|
-
const defaultCodexConfig = [
|
|
5046
|
-
"# docker-git codex config",
|
|
5047
|
-
'model = "gpt-5.4"',
|
|
5048
|
-
"model_context_window = 1050000",
|
|
5049
|
-
"model_auto_compact_token_limit = 945000",
|
|
5050
|
-
'model_reasoning_effort = "xhigh"',
|
|
5051
|
-
'plan_mode_reasoning_effort = "xhigh"',
|
|
5052
|
-
'personality = "pragmatic"',
|
|
5053
|
-
"",
|
|
5054
|
-
'approval_policy = "never"',
|
|
5055
|
-
'sandbox_mode = "danger-full-access"',
|
|
5056
|
-
'web_search = "live"',
|
|
5057
|
-
"",
|
|
5058
|
-
"[features]",
|
|
5059
|
-
"shell_snapshot = true",
|
|
5060
|
-
"multi_agent = true",
|
|
5061
|
-
"apps = true",
|
|
5062
|
-
"shell_tool = true"
|
|
5063
|
-
].join("\n");
|
|
5064
|
-
const resolvePathFromBase$1 = (path, baseDir, targetPath) => path.isAbsolute(targetPath) ? targetPath : path.resolve(baseDir, targetPath);
|
|
5065
|
-
const isPermissionDeniedSystemError = (error) => error._tag === "SystemError" && error.reason === "PermissionDenied";
|
|
5066
|
-
const skipCodexConfigPermissionDenied = (configPath, error) => isPermissionDeniedSystemError(error) ? Effect.logWarning(
|
|
5067
|
-
`Skipped Codex config sync at ${configPath}: permission denied (${error.description ?? "no details"}).`
|
|
5068
|
-
) : Effect.fail(error);
|
|
5069
|
-
const normalizeConfigText = (text) => text.replaceAll("\r\n", "\n").trim();
|
|
5070
|
-
const shouldRewriteDockerGitCodexConfig = (existing) => {
|
|
5071
|
-
const normalized = normalizeConfigText(existing);
|
|
5072
|
-
if (normalized.length === 0) {
|
|
5073
|
-
return true;
|
|
5074
|
-
}
|
|
5075
|
-
if (!normalized.startsWith(codexConfigMarker)) {
|
|
5076
|
-
return false;
|
|
5077
|
-
}
|
|
5078
|
-
return normalized !== normalizeConfigText(defaultCodexConfig);
|
|
5079
|
-
};
|
|
5080
|
-
const shouldCopyEnv = (sourceText, targetText) => {
|
|
5081
|
-
if (sourceText.trim().length === 0) {
|
|
5082
|
-
return "skip";
|
|
5083
|
-
}
|
|
5084
|
-
if (targetText.trim().length === 0) {
|
|
5085
|
-
return "copy";
|
|
5086
|
-
}
|
|
5087
|
-
if (targetText.trim() === defaultEnvContents.trim() && sourceText.trim() !== defaultEnvContents.trim()) {
|
|
5088
|
-
return "copy";
|
|
5089
|
-
}
|
|
5090
|
-
return "skip";
|
|
5091
|
-
};
|
|
5092
|
-
const parseJsonRecord = (text) => Either.match(ParseResult.decodeUnknownEither(JsonRecordFromStringSchema)(text), {
|
|
5093
|
-
onLeft: () => Effect.succeed(null),
|
|
5094
|
-
onRight: (record) => Effect.succeed(record)
|
|
5095
|
-
});
|
|
5096
|
-
const hasClaudeOauthAccount = (record) => record !== null && typeof record["oauthAccount"] === "object" && record["oauthAccount"] !== null;
|
|
5097
|
-
const hasClaudeCredentials = (record) => record !== null && typeof record["claudeAiOauth"] === "object" && record["claudeAiOauth"] !== null;
|
|
5098
|
-
const isGithubTokenKey = (key) => key === "GITHUB_TOKEN" || key === "GH_TOKEN" || key.startsWith("GITHUB_TOKEN__");
|
|
5099
5194
|
const syncClaudeJsonFile = (fs, path, spec) => Effect.gen(function* (_) {
|
|
5100
5195
|
const sourceExists = yield* _(fs.exists(spec.sourcePath));
|
|
5101
5196
|
if (!sourceExists) {
|
|
@@ -5146,18 +5241,6 @@ const syncClaudeCredentialsJson = (fs, path, sourcePath, targetPath) => syncClau
|
|
|
5146
5241
|
seedLabel: "Claude credentials",
|
|
5147
5242
|
updateLabel: "Claude credentials"
|
|
5148
5243
|
});
|
|
5149
|
-
const hasNonEmptyFile = (fs, filePath) => Effect.gen(function* (_) {
|
|
5150
|
-
const exists = yield* _(fs.exists(filePath));
|
|
5151
|
-
if (!exists) {
|
|
5152
|
-
return false;
|
|
5153
|
-
}
|
|
5154
|
-
const info = yield* _(fs.stat(filePath));
|
|
5155
|
-
if (info.type !== "File") {
|
|
5156
|
-
return false;
|
|
5157
|
-
}
|
|
5158
|
-
const text = yield* _(fs.readFileString(filePath), Effect.orElseSucceed(() => ""));
|
|
5159
|
-
return text.trim().length > 0;
|
|
5160
|
-
});
|
|
5161
5244
|
const ensureClaudeAuthSeedFromHome = (baseDir, claudeAuthPath) => withFsPathContext(
|
|
5162
5245
|
({ fs, path }) => Effect.gen(function* (_) {
|
|
5163
5246
|
const homeDir = (process.env["HOME"] ?? "").trim();
|
|
@@ -6080,6 +6163,20 @@ const maybeOpenSsh = (command, hasAgent, waitForAgent, projectConfig) => Effect.
|
|
|
6080
6163
|
const remoteCommand = resolveInteractiveRemoteCommand(projectConfig, interactiveAgent);
|
|
6081
6164
|
yield* _(openSshBestEffort(projectConfig, remoteCommand));
|
|
6082
6165
|
}).pipe(Effect.asVoid);
|
|
6166
|
+
const resolveFinalAgentConfig = (resolvedConfig) => Effect.gen(function* (_) {
|
|
6167
|
+
const resolvedAgentMode = yield* _(resolveAutoAgentMode(resolvedConfig));
|
|
6168
|
+
if ((resolvedConfig.agentAuto ?? false) && resolvedConfig.agentMode === void 0 && resolvedAgentMode !== void 0) {
|
|
6169
|
+
yield* _(Effect.log(`Auto agent selected: ${resolvedAgentMode}`));
|
|
6170
|
+
}
|
|
6171
|
+
return resolvedAgentMode === void 0 ? resolvedConfig : { ...resolvedConfig, agentMode: resolvedAgentMode };
|
|
6172
|
+
});
|
|
6173
|
+
const maybeCleanupAfterAgent = (waitForAgent, resolvedOutDir) => Effect.gen(function* (_) {
|
|
6174
|
+
if (!waitForAgent) {
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
6177
|
+
yield* _(Effect.log("Agent finished. Cleaning up container..."));
|
|
6178
|
+
yield* _(runDockerDownCleanup(resolvedOutDir));
|
|
6179
|
+
});
|
|
6083
6180
|
const runCreateProject = (path, command) => Effect.gen(function* (_) {
|
|
6084
6181
|
if (command.runUp) {
|
|
6085
6182
|
yield* _(ensureDockerDaemonAccess(process.cwd()));
|
|
@@ -6087,7 +6184,8 @@ const runCreateProject = (path, command) => Effect.gen(function* (_) {
|
|
|
6087
6184
|
const ctx = makeCreateContext(path, process.cwd());
|
|
6088
6185
|
const resolvedOutDir = path.resolve(ctx.resolveRootPath(command.outDir));
|
|
6089
6186
|
const resolvedConfig = yield* _(resolveCreateConfig(command, ctx, resolvedOutDir));
|
|
6090
|
-
const
|
|
6187
|
+
const finalConfig = yield* _(resolveFinalAgentConfig(resolvedConfig));
|
|
6188
|
+
const { globalConfig, projectConfig } = buildProjectConfigs(path, ctx.baseDir, resolvedOutDir, finalConfig);
|
|
6091
6189
|
yield* _(migrateProjectOrchLayout(ctx.baseDir, globalConfig, ctx.resolveRootPath));
|
|
6092
6190
|
const createdFiles = yield* _(
|
|
6093
6191
|
prepareProjectFiles(resolvedOutDir, ctx.baseDir, globalConfig, projectConfig, {
|
|
@@ -6096,8 +6194,8 @@ const runCreateProject = (path, command) => Effect.gen(function* (_) {
|
|
|
6096
6194
|
})
|
|
6097
6195
|
);
|
|
6098
6196
|
yield* _(logCreatedProject(resolvedOutDir, createdFiles));
|
|
6099
|
-
const hasAgent =
|
|
6100
|
-
const waitForAgent = hasAgent && (
|
|
6197
|
+
const hasAgent = finalConfig.agentMode !== void 0;
|
|
6198
|
+
const waitForAgent = hasAgent && (finalConfig.agentAuto ?? false);
|
|
6101
6199
|
yield* _(
|
|
6102
6200
|
runDockerUpIfNeeded(resolvedOutDir, projectConfig, {
|
|
6103
6201
|
runUp: command.runUp,
|
|
@@ -6110,10 +6208,7 @@ const runCreateProject = (path, command) => Effect.gen(function* (_) {
|
|
|
6110
6208
|
if (command.runUp) {
|
|
6111
6209
|
yield* _(logDockerAccessInfo(resolvedOutDir, projectConfig));
|
|
6112
6210
|
}
|
|
6113
|
-
|
|
6114
|
-
yield* _(Effect.log("Agent finished. Cleaning up container..."));
|
|
6115
|
-
yield* _(runDockerDownCleanup(resolvedOutDir));
|
|
6116
|
-
}
|
|
6211
|
+
yield* _(maybeCleanupAfterAgent(waitForAgent, resolvedOutDir));
|
|
6117
6212
|
yield* _(autoSyncState(`chore(state): update ${formatStateSyncLabel(projectConfig.repoUrl)}`));
|
|
6118
6213
|
yield* _(maybeOpenSsh(command, hasAgent, waitForAgent, projectConfig));
|
|
6119
6214
|
}).pipe(Effect.asVoid);
|
|
@@ -7768,7 +7863,8 @@ const valueOptionSpecs = [
|
|
|
7768
7863
|
{ flag: "-m", key: "message" },
|
|
7769
7864
|
{ flag: "--out-dir", key: "outDir" },
|
|
7770
7865
|
{ flag: "--project-dir", key: "projectDir" },
|
|
7771
|
-
{ flag: "--lines", key: "lines" }
|
|
7866
|
+
{ flag: "--lines", key: "lines" },
|
|
7867
|
+
{ flag: "--auto", key: "agentAutoMode" }
|
|
7772
7868
|
];
|
|
7773
7869
|
const valueOptionSpecByFlag = new Map(
|
|
7774
7870
|
valueOptionSpecs.map((spec) => [spec.flag, spec])
|
|
@@ -7786,9 +7882,7 @@ const booleanFlagUpdaters = {
|
|
|
7786
7882
|
"--no-wipe": (raw) => ({ ...raw, wipe: false }),
|
|
7787
7883
|
"--web": (raw) => ({ ...raw, authWeb: true }),
|
|
7788
7884
|
"--include-default": (raw) => ({ ...raw, includeDefault: true }),
|
|
7789
|
-
"--
|
|
7790
|
-
"--codex": (raw) => ({ ...raw, agentCodex: true }),
|
|
7791
|
-
"--auto": (raw) => ({ ...raw, agentAuto: true })
|
|
7885
|
+
"--auto": (raw) => ({ ...raw, agentAutoMode: "auto" })
|
|
7792
7886
|
};
|
|
7793
7887
|
const valueFlagUpdaters = {
|
|
7794
7888
|
repoUrl: (raw, value) => ({ ...raw, repoUrl: value }),
|
|
@@ -7818,7 +7912,8 @@ const valueFlagUpdaters = {
|
|
|
7818
7912
|
message: (raw, value) => ({ ...raw, message: value }),
|
|
7819
7913
|
outDir: (raw, value) => ({ ...raw, outDir: value }),
|
|
7820
7914
|
projectDir: (raw, value) => ({ ...raw, projectDir: value }),
|
|
7821
|
-
lines: (raw, value) => ({ ...raw, lines: value })
|
|
7915
|
+
lines: (raw, value) => ({ ...raw, lines: value }),
|
|
7916
|
+
agentAutoMode: (raw, value) => ({ ...raw, agentAutoMode: value.trim().toLowerCase() })
|
|
7822
7917
|
};
|
|
7823
7918
|
const applyCommandBooleanFlag = (raw, token) => {
|
|
7824
7919
|
const updater = booleanFlagUpdaters[token];
|
|
@@ -7841,25 +7936,55 @@ const parseInlineValueToken = (raw, token) => {
|
|
|
7841
7936
|
const inlineValue = token.slice(equalIndex + 1);
|
|
7842
7937
|
return applyCommandValueFlag(raw, flag, inlineValue);
|
|
7843
7938
|
};
|
|
7844
|
-
const
|
|
7845
|
-
|
|
7939
|
+
const legacyAgentFlagError = (token) => {
|
|
7940
|
+
if (token === "--claude") {
|
|
7941
|
+
return {
|
|
7942
|
+
_tag: "InvalidOption",
|
|
7943
|
+
option: token,
|
|
7944
|
+
reason: "use --auto=claude"
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
if (token === "--codex") {
|
|
7948
|
+
return {
|
|
7949
|
+
_tag: "InvalidOption",
|
|
7950
|
+
option: token,
|
|
7951
|
+
reason: "use --auto=codex"
|
|
7952
|
+
};
|
|
7953
|
+
}
|
|
7954
|
+
return null;
|
|
7955
|
+
};
|
|
7956
|
+
const toParseStep = (parsed, nextIndex) => Either.isLeft(parsed) ? { _tag: "error", error: parsed.left } : { _tag: "ok", raw: parsed.right, nextIndex };
|
|
7957
|
+
const parseValueOptionStep = (raw, token, value, index) => {
|
|
7958
|
+
if (value === void 0) {
|
|
7959
|
+
return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } };
|
|
7960
|
+
}
|
|
7961
|
+
return toParseStep(applyCommandValueFlag(raw, token, value), index + 2);
|
|
7962
|
+
};
|
|
7963
|
+
const parseSpecialFlagStep = (raw, token, index) => {
|
|
7846
7964
|
const inlineApplied = parseInlineValueToken(raw, token);
|
|
7847
7965
|
if (inlineApplied !== null) {
|
|
7848
|
-
return
|
|
7966
|
+
return toParseStep(inlineApplied, index + 1);
|
|
7849
7967
|
}
|
|
7850
7968
|
const booleanApplied = applyCommandBooleanFlag(raw, token);
|
|
7851
7969
|
if (booleanApplied !== null) {
|
|
7852
7970
|
return { _tag: "ok", raw: booleanApplied, nextIndex: index + 1 };
|
|
7853
7971
|
}
|
|
7972
|
+
const deprecatedAgentFlag = legacyAgentFlagError(token);
|
|
7973
|
+
if (deprecatedAgentFlag !== null) {
|
|
7974
|
+
return { _tag: "error", error: deprecatedAgentFlag };
|
|
7975
|
+
}
|
|
7976
|
+
return null;
|
|
7977
|
+
};
|
|
7978
|
+
const parseRawOptionsStep = (args, index, raw) => {
|
|
7979
|
+
const token = args[index] ?? "";
|
|
7980
|
+
const specialStep = parseSpecialFlagStep(raw, token, index);
|
|
7981
|
+
if (specialStep !== null) {
|
|
7982
|
+
return specialStep;
|
|
7983
|
+
}
|
|
7854
7984
|
if (!token.startsWith("-")) {
|
|
7855
7985
|
return { _tag: "error", error: { _tag: "UnexpectedArgument", value: token } };
|
|
7856
7986
|
}
|
|
7857
|
-
|
|
7858
|
-
if (value === void 0) {
|
|
7859
|
-
return { _tag: "error", error: { _tag: "MissingOptionValue", option: token } };
|
|
7860
|
-
}
|
|
7861
|
-
const nextRaw = applyCommandValueFlag(raw, token, value);
|
|
7862
|
-
return Either.isLeft(nextRaw) ? { _tag: "error", error: nextRaw.left } : { _tag: "ok", raw: nextRaw.right, nextIndex: index + 2 };
|
|
7987
|
+
return parseValueOptionStep(raw, token, args[index + 1], index);
|
|
7863
7988
|
};
|
|
7864
7989
|
const parseRawOptions = (args) => {
|
|
7865
7990
|
let index = 0;
|
|
@@ -8009,6 +8134,23 @@ const parseAuth = (args) => {
|
|
|
8009
8134
|
const rest = args.slice(2);
|
|
8010
8135
|
return Either.flatMap(parseRawOptions(rest), (raw) => buildAuthCommand(provider, action, resolveAuthOptions(raw)));
|
|
8011
8136
|
};
|
|
8137
|
+
const resolveAutoAgentFlags = (raw) => {
|
|
8138
|
+
const requested = raw.agentAutoMode;
|
|
8139
|
+
if (requested === void 0) {
|
|
8140
|
+
return Either.right({ agentMode: void 0, agentAuto: false });
|
|
8141
|
+
}
|
|
8142
|
+
if (requested === "auto") {
|
|
8143
|
+
return Either.right({ agentMode: void 0, agentAuto: true });
|
|
8144
|
+
}
|
|
8145
|
+
if (requested === "claude" || requested === "codex") {
|
|
8146
|
+
return Either.right({ agentMode: requested, agentAuto: true });
|
|
8147
|
+
}
|
|
8148
|
+
return Either.left({
|
|
8149
|
+
_tag: "InvalidOption",
|
|
8150
|
+
option: "--auto",
|
|
8151
|
+
reason: "expected one of: claude, codex"
|
|
8152
|
+
});
|
|
8153
|
+
};
|
|
8012
8154
|
const parsePort = (value) => {
|
|
8013
8155
|
const parsed = Number(value);
|
|
8014
8156
|
if (!Number.isInteger(parsed)) {
|
|
@@ -8134,11 +8276,6 @@ const resolveCreateBehavior = (raw) => ({
|
|
|
8134
8276
|
forceEnv: raw.forceEnv ?? false,
|
|
8135
8277
|
enableMcpPlaywright: raw.enableMcpPlaywright ?? false
|
|
8136
8278
|
});
|
|
8137
|
-
const resolveAgentMode = (raw) => {
|
|
8138
|
-
if (raw.agentClaude) return "claude";
|
|
8139
|
-
if (raw.agentCodex) return "codex";
|
|
8140
|
-
return void 0;
|
|
8141
|
-
};
|
|
8142
8279
|
const buildTemplateConfig = ({
|
|
8143
8280
|
agentAuto,
|
|
8144
8281
|
agentMode,
|
|
@@ -8189,8 +8326,7 @@ const buildCreateCommand = (raw) => Either.gen(function* (_) {
|
|
|
8189
8326
|
const dockerSharedNetworkName = yield* _(
|
|
8190
8327
|
nonEmpty("--shared-network", raw.dockerSharedNetworkName, defaultTemplateConfig.dockerSharedNetworkName)
|
|
8191
8328
|
);
|
|
8192
|
-
const agentMode =
|
|
8193
|
-
const agentAuto = raw.agentAuto ?? false;
|
|
8329
|
+
const { agentAuto, agentMode } = yield* _(resolveAutoAgentFlags(raw));
|
|
8194
8330
|
return {
|
|
8195
8331
|
_tag: "Create",
|
|
8196
8332
|
outDir: paths.outDir,
|
|
@@ -8514,9 +8650,7 @@ Options:
|
|
|
8514
8650
|
--up | --no-up Run docker compose up after init (default: --up)
|
|
8515
8651
|
--ssh | --no-ssh Auto-open SSH after create/clone (default: clone=--ssh, create=--no-ssh)
|
|
8516
8652
|
--mcp-playwright | --no-mcp-playwright Enable Playwright MCP + Chromium sidecar (default: --no-mcp-playwright)
|
|
8517
|
-
--claude
|
|
8518
|
-
--codex Start Codex agent inside container after clone
|
|
8519
|
-
--auto Auto-execute: agent completes the task, creates PR and pushes (requires --claude or --codex)
|
|
8653
|
+
--auto[=claude|codex] Auto-execute an agent; without value picks by auth, random if both are available
|
|
8520
8654
|
--force Overwrite existing files and wipe compose volumes (docker compose down -v)
|
|
8521
8655
|
--force-env Reset project env defaults only (keep workspace volume/data)
|
|
8522
8656
|
-h, --help Show this help
|