@mytegroupinc/myte-core 0.0.40 → 0.0.42

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.
Files changed (2) hide show
  1. package/mytecody-cli.js +297 -10
  2. package/package.json +1 -1
package/mytecody-cli.js CHANGED
@@ -168,6 +168,10 @@ function currentBridgePath() {
168
168
  return path.join(currentInstallRoot(), "lib", "mytecody-async-responses-bridge.js");
169
169
  }
170
170
 
171
+ function currentControllerPath() {
172
+ return path.join(currentInstallRoot(), "lib", "mytecody-controller.js");
173
+ }
174
+
171
175
  function codexHome() {
172
176
  return path.join(installRoot(), "engine-home");
173
177
  }
@@ -193,7 +197,12 @@ function installedClientCommand() {
193
197
  }
194
198
 
195
199
  function installedClientUsable() {
196
- return Boolean(installedClientCommand() && fs.existsSync(currentBridgePath()) && readCurrentClientManifest());
200
+ return Boolean(
201
+ installedClientCommand() &&
202
+ fs.existsSync(currentBridgePath()) &&
203
+ fs.existsSync(currentControllerPath()) &&
204
+ readCurrentClientManifest(),
205
+ );
197
206
  }
198
207
 
199
208
  function loadSignedBridge() {
@@ -208,6 +217,18 @@ function loadSignedBridge() {
208
217
  return bridge;
209
218
  }
210
219
 
220
+ function loadSignedController() {
221
+ const controllerPath = currentControllerPath();
222
+ if (!fs.existsSync(controllerPath)) {
223
+ throw new Error("signed MyteCody controller asset is missing; run `mytecody update`.");
224
+ }
225
+ const controller = require(controllerPath);
226
+ if (!controller || typeof controller.runMyteCodyController !== "function") {
227
+ throw new Error("signed MyteCody controller asset is invalid.");
228
+ }
229
+ return controller;
230
+ }
231
+
211
232
  function releaseAssetsForPlatform(manifest, artifact) {
212
233
  const assets = [];
213
234
  const collect = (value, source) => {
@@ -373,9 +394,11 @@ function commonStatus(args, envPath) {
373
394
  install_root: installRoot(),
374
395
  engine_path: currentEnginePath(),
375
396
  bridge_path: currentBridgePath(),
397
+ controller_path: currentControllerPath(),
376
398
  client_manifest: currentClientManifestPath(),
377
399
  client_installed: Boolean(current && installedClientCommand()),
378
400
  bridge_installed: fs.existsSync(currentBridgePath()),
401
+ controller_installed: fs.existsSync(currentControllerPath()),
379
402
  client_version: current && current.version ? current.version : null,
380
403
  },
381
404
  };
@@ -724,6 +747,63 @@ function installArtifactBytes(bytes, manifest, artifact) {
724
747
  return installedManifest;
725
748
  }
726
749
 
750
+ function reusableInstalledArtifact(artifact) {
751
+ const enginePath = currentEnginePath();
752
+ const current = readCurrentClientManifest();
753
+ if (!current || !fs.existsSync(enginePath)) return null;
754
+
755
+ const currentArtifact = current.artifact || {};
756
+ if (artifact && artifact.sha256) {
757
+ if (String(currentArtifact.sha256 || "").toLowerCase() !== String(artifact.sha256 || "").toLowerCase()) {
758
+ return null;
759
+ }
760
+ }
761
+
762
+ const engineBytes = fs.readFileSync(enginePath);
763
+ const installedSha = sha256Hex(engineBytes);
764
+ const expectedInstalledSha =
765
+ artifact && (artifact.installed_sha256 || artifact.executable_sha256 || artifact.uncompressed_sha256);
766
+ if (expectedInstalledSha && installedSha.toLowerCase() !== String(expectedInstalledSha).toLowerCase()) {
767
+ return null;
768
+ }
769
+ if (
770
+ currentArtifact.installed_sha256 &&
771
+ installedSha.toLowerCase() !== String(currentArtifact.installed_sha256).toLowerCase()
772
+ ) {
773
+ return null;
774
+ }
775
+
776
+ return {
777
+ enginePath,
778
+ engineBytes,
779
+ artifactSizeBytes: Number(currentArtifact.size_bytes || artifact?.size_bytes || 0),
780
+ installedSha,
781
+ };
782
+ }
783
+
784
+ function installManifestForReusableArtifact(reusable, manifest, artifact) {
785
+ const installedManifest = {
786
+ schema_version: manifest.schema_version || 1,
787
+ channel: manifest.channel || DEFAULT_CHANNEL,
788
+ version: manifest.version || "unknown",
789
+ installed_at: new Date().toISOString(),
790
+ launcher_version: PACKAGE_VERSION,
791
+ platform: platformKey(),
792
+ executable: reusable.enginePath,
793
+ artifact: {
794
+ url: artifact.url,
795
+ sha256: artifact.sha256,
796
+ format: artifactFormat(artifact),
797
+ size_bytes: reusable.artifactSizeBytes,
798
+ installed_sha256: reusable.installedSha,
799
+ installed_size_bytes: reusable.engineBytes.length,
800
+ },
801
+ };
802
+ fs.mkdirSync(path.dirname(currentClientManifestPath()), { recursive: true });
803
+ fs.writeFileSync(currentClientManifestPath(), JSON.stringify(installedManifest, null, 2), "utf8");
804
+ return installedManifest;
805
+ }
806
+
727
807
  async function installReleaseAssets(manifest, artifact, { progress } = {}) {
728
808
  const assets = releaseAssetsForPlatform(manifest, artifact);
729
809
  const installed = [];
@@ -1005,6 +1085,157 @@ function codexLaunchArgs(rawArgs, args = {}, providerBaseUrl = codyInferenceBase
1005
1085
  return [...providerArgs, ...rawArgs];
1006
1086
  }
1007
1087
 
1088
+ function execArgsAndStdin(rawArgs) {
1089
+ if (!rawArgs.length) return { args: [], stdin: null };
1090
+ const valueOptions = new Set([
1091
+ "-a",
1092
+ "--ask-for-approval",
1093
+ "-C",
1094
+ "--cd",
1095
+ "-c",
1096
+ "--config",
1097
+ "-i",
1098
+ "--image",
1099
+ "-m",
1100
+ "--model",
1101
+ "-o",
1102
+ "--output-last-message",
1103
+ "-p",
1104
+ "--profile",
1105
+ "-s",
1106
+ "--sandbox",
1107
+ "--color",
1108
+ "--local-provider",
1109
+ "--output-schema",
1110
+ ]);
1111
+ const forwarded = [];
1112
+ let index = 0;
1113
+ while (index < rawArgs.length) {
1114
+ const arg = rawArgs[index];
1115
+ if (arg === "--") {
1116
+ index += 1;
1117
+ break;
1118
+ }
1119
+ if (arg === "-") break;
1120
+ if (!arg.startsWith("-")) break;
1121
+ forwarded.push(arg);
1122
+ index += 1;
1123
+ if (valueOptions.has(arg) && index < rawArgs.length) {
1124
+ forwarded.push(rawArgs[index]);
1125
+ index += 1;
1126
+ }
1127
+ }
1128
+ const promptParts = rawArgs.slice(index);
1129
+ if (!promptParts.length) return { args: forwarded, stdin: null };
1130
+ if (promptParts.length === 1 && promptParts[0] === "-") {
1131
+ return { args: [...forwarded, "-"], stdin: fs.readFileSync(0, "utf8") };
1132
+ }
1133
+ const prompt = promptParts.join(" ");
1134
+ if (prompt.includes("\n") || prompt.includes("\r") || promptParts.length > 1) {
1135
+ return { args: [...forwarded, "-"], stdin: prompt };
1136
+ }
1137
+ return { args: [...forwarded, prompt], stdin: null };
1138
+ }
1139
+
1140
+ function stripControllerArgs(rawArgs) {
1141
+ const kept = [];
1142
+ let enabled = process.env.MYTE_CODY_CONTROLLER !== "0";
1143
+ for (let i = 0; i < rawArgs.length; i += 1) {
1144
+ const arg = rawArgs[i];
1145
+ if (arg === "--controller") {
1146
+ const next = rawArgs[i + 1];
1147
+ if (next !== undefined && !next.startsWith("-")) {
1148
+ enabled = !["0", "false", "off", "raw"].includes(String(next).toLowerCase());
1149
+ i += 1;
1150
+ } else {
1151
+ enabled = true;
1152
+ }
1153
+ continue;
1154
+ }
1155
+ if (arg.startsWith("--controller=")) {
1156
+ const value = arg.slice("--controller=".length);
1157
+ enabled = !["0", "false", "off", "raw"].includes(String(value).toLowerCase());
1158
+ continue;
1159
+ }
1160
+ if (arg === "--no-controller") {
1161
+ enabled = false;
1162
+ continue;
1163
+ }
1164
+ kept.push(arg);
1165
+ }
1166
+ return { enabled, args: kept };
1167
+ }
1168
+
1169
+ function controllerPromptFromExecArgs(rawArgs) {
1170
+ const execInput = execArgsAndStdin(rawArgs);
1171
+ if (execInput.stdin != null) return { prompt: execInput.stdin, forwardedArgs: [] };
1172
+ const prompt = execInput.args.length ? execInput.args[execInput.args.length - 1] : "";
1173
+ if (!prompt || prompt.startsWith("-")) {
1174
+ return { prompt: "", forwardedArgs: execInput.args };
1175
+ }
1176
+ return { prompt, forwardedArgs: execInput.args.slice(0, -1) };
1177
+ }
1178
+
1179
+ function runEngineExecWorker({ command, args, providerBaseUrl, token, prompt, timeoutMs }) {
1180
+ return new Promise((resolve) => {
1181
+ const started = Date.now();
1182
+ const env = {
1183
+ ...process.env,
1184
+ CODEX_HOME: codexHome(),
1185
+ MYTE_CODY_AUTH_TOKEN: token,
1186
+ MYTE_CODY_BRAND: "1",
1187
+ MYTE_CODY_CONTROLLER: "0",
1188
+ MYTE_CODY_BRIDGE_BASE_URL: providerBaseUrl,
1189
+ };
1190
+ const launchArgs = [...command.args, ...codexLaunchArgs(["exec", "--json", "-"], args, providerBaseUrl)];
1191
+ const child = spawn(command.cmd, launchArgs, {
1192
+ cwd: process.cwd(),
1193
+ env,
1194
+ stdio: ["pipe", "pipe", "pipe"],
1195
+ shell: process.platform === "win32" && command.cmd === "codex",
1196
+ });
1197
+ let stdout = "";
1198
+ let stderr = "";
1199
+ let settled = false;
1200
+ const timer = setTimeout(() => {
1201
+ if (settled) return;
1202
+ try {
1203
+ child.kill();
1204
+ } catch {}
1205
+ }, timeoutMs || 180000);
1206
+ child.stdout.on("data", (chunk) => {
1207
+ stdout += chunk.toString();
1208
+ });
1209
+ child.stderr.on("data", (chunk) => {
1210
+ stderr += chunk.toString();
1211
+ });
1212
+ child.on("error", (error) => {
1213
+ settled = true;
1214
+ clearTimeout(timer);
1215
+ resolve({
1216
+ status: 1,
1217
+ stdout,
1218
+ stderr,
1219
+ error: error.message || String(error),
1220
+ durationMs: Date.now() - started,
1221
+ });
1222
+ });
1223
+ child.on("exit", (code, signal) => {
1224
+ settled = true;
1225
+ clearTimeout(timer);
1226
+ resolve({
1227
+ status: code == null ? 1 : code,
1228
+ signal: signal || null,
1229
+ stdout,
1230
+ stderr,
1231
+ error: null,
1232
+ durationMs: Date.now() - started,
1233
+ });
1234
+ });
1235
+ child.stdin.end(prompt);
1236
+ });
1237
+ }
1238
+
1008
1239
  async function runCodex(rawArgs, args = {}, envPath = null) {
1009
1240
  const token = getAuthToken();
1010
1241
  if (!token) {
@@ -1063,7 +1294,48 @@ async function runCodex(rawArgs, args = {}, envPath = null) {
1063
1294
  return 1;
1064
1295
  }
1065
1296
 
1066
- const launchArgs = [...command.args, ...codexLaunchArgs(rawArgs, args, bridge.baseUrl)];
1297
+ const normalizedRawArgs = rawArgs[0] === "exec" ? ["exec", ...stripControllerArgs(rawArgs.slice(1)).args] : rawArgs;
1298
+ const controllerState = rawArgs[0] === "exec" ? stripControllerArgs(rawArgs.slice(1)) : { enabled: false, args: [] };
1299
+ if (rawArgs[0] === "exec" && controllerState.enabled) {
1300
+ try {
1301
+ progress("opening MyteCody controller");
1302
+ const signedController = loadSignedController();
1303
+ const promptInfo = controllerPromptFromExecArgs(controllerState.args);
1304
+ if (!promptInfo.prompt.trim()) {
1305
+ await splash.stop();
1306
+ await bridge.close();
1307
+ console.error("MyteCody controller exec requires a prompt or stdin via exec -.");
1308
+ return 1;
1309
+ }
1310
+ await splash.stop();
1311
+ if (packageNotice) statusLine(packageNotice);
1312
+ const summary = await signedController.runMyteCodyController({
1313
+ prompt: promptInfo.prompt,
1314
+ workspace: process.cwd(),
1315
+ artifactRoot: path.join(installRoot(), "controller-runs"),
1316
+ runWorker: (workerPrompt, workerOptions = {}) =>
1317
+ runEngineExecWorker({
1318
+ command,
1319
+ args,
1320
+ providerBaseUrl: bridge.baseUrl,
1321
+ token,
1322
+ prompt: workerPrompt,
1323
+ timeoutMs: workerOptions.timeoutMs || 180000,
1324
+ }),
1325
+ });
1326
+ console.error(`[MYTE CODY] controller run: ${summary.artifact_dir}`);
1327
+ console.error(`[MYTE CODY] controller status: ${summary.status}`);
1328
+ await bridge.close();
1329
+ return summary.status === "pass" ? 0 : 1;
1330
+ } catch (error) {
1331
+ await splash.stop();
1332
+ await bridge.close();
1333
+ console.error(`MyteCody controller failed: ${error && error.message ? error.message : error}`);
1334
+ return 1;
1335
+ }
1336
+ }
1337
+
1338
+ const launchArgs = [...command.args, ...codexLaunchArgs(normalizedRawArgs, args, bridge.baseUrl)];
1067
1339
  const env = {
1068
1340
  ...process.env,
1069
1341
  CODEX_HOME: codexHome(),
@@ -1102,7 +1374,10 @@ async function runDoctor(args, envPath) {
1102
1374
  payload.gateway.probe = await probeGateway(args);
1103
1375
  }
1104
1376
  payload.ready_for_coding =
1105
- payload.auth.present && payload.release.client_installed && payload.release.bridge_installed;
1377
+ payload.auth.present &&
1378
+ payload.release.client_installed &&
1379
+ payload.release.bridge_installed &&
1380
+ payload.release.controller_installed;
1106
1381
  if (payload.gateway.probe) {
1107
1382
  payload.ready_for_coding = Boolean(payload.ready_for_coding && payload.gateway.probe.ok);
1108
1383
  }
@@ -1182,12 +1457,23 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
1182
1457
  if (!artifactMetadata.ok) {
1183
1458
  throw new Error(`MyteCody release artifact metadata is ${artifactMetadata.status}.`);
1184
1459
  }
1185
- const bytes = await readArtifactBytes(artifact, { progress });
1186
- const digest = sha256Hex(bytes);
1187
- if (digest.toLowerCase() !== String(artifact.sha256 || "").toLowerCase()) {
1188
- throw new Error(`Artifact SHA-256 mismatch: expected ${artifact.sha256}, got ${digest}`);
1460
+ let installed;
1461
+ let artifactDigest = String(artifact.sha256 || "");
1462
+ let artifactSizeBytes = Number(artifact.size_bytes || 0);
1463
+ const reusable = reusableInstalledArtifact(artifact);
1464
+ if (reusable) {
1465
+ if (progress) progress("reusing installed MyteCody engine");
1466
+ installed = installManifestForReusableArtifact(reusable, manifest, artifact);
1467
+ artifactSizeBytes = reusable.artifactSizeBytes;
1468
+ } else {
1469
+ const bytes = await readArtifactBytes(artifact, { progress });
1470
+ artifactDigest = sha256Hex(bytes);
1471
+ artifactSizeBytes = bytes.length;
1472
+ if (artifactDigest.toLowerCase() !== String(artifact.sha256 || "").toLowerCase()) {
1473
+ throw new Error(`Artifact SHA-256 mismatch: expected ${artifact.sha256}, got ${artifactDigest}`);
1474
+ }
1475
+ installed = installArtifactBytes(bytes, manifest, artifact);
1189
1476
  }
1190
- const installed = installArtifactBytes(bytes, manifest, artifact);
1191
1477
  const installedAssets = await installReleaseAssets(manifest, artifact, { progress });
1192
1478
  if (installedAssets.length) {
1193
1479
  installed.assets = installedAssets;
@@ -1197,8 +1483,8 @@ async function buildUpdatePayload(args, envPath, { dryRun = false, progress = nu
1197
1483
  ok: true,
1198
1484
  version: installed.version,
1199
1485
  executable: installed.executable,
1200
- sha256: digest,
1201
- size_bytes: bytes.length,
1486
+ sha256: artifactDigest,
1487
+ size_bytes: artifactSizeBytes,
1202
1488
  installed_sha256: installed.artifact.installed_sha256,
1203
1489
  installed_size_bytes: installed.artifact.installed_size_bytes,
1204
1490
  format: installed.artifact.format,
@@ -1356,6 +1642,7 @@ module.exports = {
1356
1642
  codyInferenceBase,
1357
1643
  codyGatewayUrl,
1358
1644
  currentBridgePath,
1645
+ currentControllerPath,
1359
1646
  currentClientManifestPath,
1360
1647
  currentEnginePath,
1361
1648
  ensureBrandedEngineInstalled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "Myte CLI core implementation.",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",