@tuned-tensor/cli 0.26.2 → 0.28.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/dist/index.js CHANGED
@@ -49,6 +49,7 @@ import {
49
49
  readHardwareSnapshot,
50
50
  readJsonlTail,
51
51
  readSpecHistory,
52
+ renameAtomicFile,
52
53
  resolveProjectConfig,
53
54
  runGpuProcess,
54
55
  runLocalPipeline,
@@ -64,7 +65,7 @@ import {
64
65
  warningsFromSnapshot,
65
66
  withAppendLock,
66
67
  writePrivateJsonAtomic
67
- } from "./chunk-VQWJ2XKR.js";
68
+ } from "./chunk-7CO5DBCY.js";
68
69
  import {
69
70
  DECISION_MODELS,
70
71
  DEFAULT_ARTIFACT_ROOT,
@@ -96,10 +97,10 @@ import {
96
97
  specLockPath,
97
98
  withDecisionPythonEnvironment,
98
99
  withFoundationPythonEnvironment
99
- } from "./chunk-2AIP63QO.js";
100
+ } from "./chunk-FSVTMX4L.js";
100
101
 
101
102
  // src/cli.ts
102
- import { spawn as spawn6 } from "child_process";
103
+ import { spawn as spawn7 } from "child_process";
103
104
  import { constants as osConstants2 } from "os";
104
105
  import { Command } from "commander";
105
106
  import chalk15 from "chalk";
@@ -661,7 +662,38 @@ import { randomUUID as randomUUID3 } from "crypto";
661
662
  import { closeSync, openSync } from "fs";
662
663
  import { mkdir, open, readdir, readFile as readFile2, rm as rm3, writeFile as writeFile3 } from "fs/promises";
663
664
  import { hostname, userInfo } from "os";
664
- import { basename as basename2, dirname as dirname3, join as join3 } from "path";
665
+ import { basename as basename2, dirname as dirname3, join as join4 } from "path";
666
+
667
+ // src/local-runtime/windows-child-signals.ts
668
+ import { spawn } from "child_process";
669
+ import { join as join3 } from "path";
670
+ function windowsChildSignals(child) {
671
+ let timer;
672
+ const dispose = () => {
673
+ if (timer) clearTimeout(timer);
674
+ };
675
+ child.once("close", dispose);
676
+ return {
677
+ dispose,
678
+ forward(signal) {
679
+ if (process.platform !== "win32" || !child.connected) return false;
680
+ child.send({ ttSignal: signal }, () => {
681
+ });
682
+ timer ??= setTimeout(() => {
683
+ if (child.pid) {
684
+ spawn(
685
+ join3(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"),
686
+ ["/PID", String(child.pid), "/T", "/F"],
687
+ { windowsHide: true, stdio: "ignore" }
688
+ ).on("error", () => child.kill());
689
+ }
690
+ }, 5e3);
691
+ return true;
692
+ }
693
+ };
694
+ }
695
+
696
+ // src/local-runtime/background-runs.ts
665
697
  var ACTIVE = /* @__PURE__ */ new Set(["starting", "running", "cancel_requested"]);
666
698
  var DEFAULT_MAX_PARALLEL_RUNS = 1;
667
699
  var MAX_PARALLEL_RUNS_LIMIT = 8;
@@ -675,7 +707,7 @@ var BackgroundRunError = class extends Error {
675
707
  }
676
708
  };
677
709
  function backgroundRunsRoot(env = process.env) {
678
- return join3(getTunedTensorHome(env), "background-runs");
710
+ return join4(getTunedTensorHome(env), "background-runs");
679
711
  }
680
712
  function maxParallelRuns(env = process.env) {
681
713
  const raw = env.TT_MAX_PARALLEL_RUNS?.trim();
@@ -736,17 +768,17 @@ function plainLogText(text3) {
736
768
  }
737
769
  function createBackgroundRunStore(root = backgroundRunsRoot()) {
738
770
  const paths = (id) => {
739
- const dir = join3(root, id);
771
+ const dir = join4(root, id);
740
772
  return {
741
773
  dir,
742
- record: join3(dir, "record.json"),
743
- spec: join3(dir, "spec.json"),
744
- events: join3(dir, "events.jsonl"),
745
- log: join3(dir, "output.log"),
746
- result: join3(dir, "result.json")
774
+ record: join4(dir, "record.json"),
775
+ spec: join4(dir, "spec.json"),
776
+ events: join4(dir, "events.jsonl"),
777
+ log: join4(dir, "output.log"),
778
+ result: join4(dir, "result.json")
747
779
  };
748
780
  };
749
- const lockPath = (id) => join3(paths(id).dir, ".lock");
781
+ const lockPath = (id) => join4(paths(id).dir, ".lock");
750
782
  async function readRecord(id) {
751
783
  return JSON.parse(await readFile2(paths(id).record, "utf8"));
752
784
  }
@@ -845,7 +877,7 @@ function createBackgroundRunStore(root = backgroundRunsRoot()) {
845
877
  async create(input, options = {}) {
846
878
  const limit = options.maxParallel ?? DEFAULT_MAX_PARALLEL_RUNS;
847
879
  await mkdir(root, { recursive: true, mode: 448 });
848
- return await withAppendLock(join3(root, ".launch.lock"), async () => {
880
+ return await withAppendLock(join4(root, ".launch.lock"), async () => {
849
881
  const active = (await list()).filter(isActiveBackgroundRun);
850
882
  if (active.length >= limit) {
851
883
  throw new BackgroundRunError(
@@ -896,7 +928,7 @@ function createBackgroundRunStore(root = backgroundRunsRoot()) {
896
928
  details: { reason }
897
929
  });
898
930
  const pid = record6.process?.supervisor_pid;
899
- if (wasRunning && pid !== void 0 && record6.process?.host === hostname()) {
931
+ if (process.platform !== "win32" && wasRunning && pid !== void 0 && record6.process?.host === hostname()) {
900
932
  try {
901
933
  process.kill(pid, "SIGTERM");
902
934
  } catch {
@@ -1050,9 +1082,9 @@ async function launchBackgroundSupervisor(args2) {
1050
1082
  stdio: "ignore"
1051
1083
  });
1052
1084
  if (child.pid === void 0) {
1053
- await new Promise((resolve13, reject) => {
1085
+ await new Promise((resolve14, reject) => {
1054
1086
  child.once("error", reject);
1055
- child.once("spawn", () => resolve13());
1087
+ child.once("spawn", () => resolve14());
1056
1088
  });
1057
1089
  }
1058
1090
  } catch (error) {
@@ -1069,8 +1101,8 @@ async function launchBackgroundSupervisor(args2) {
1069
1101
  });
1070
1102
  }
1071
1103
  function workspaceSnapshotPath(run) {
1072
- const specFile = join3(run.choices.workspace_root, run.choices.spec_path);
1073
- return join3(dirname3(specFile), `.${basename2(specFile)}.tt-agent-${run.id}.json`);
1104
+ const specFile = join4(run.choices.workspace_root, run.choices.spec_path);
1105
+ return join4(dirname3(specFile), `.${basename2(specFile)}.tt-agent-${run.id}.json`);
1074
1106
  }
1075
1107
  async function superviseBackgroundRun(options) {
1076
1108
  const { store } = options;
@@ -1095,37 +1127,43 @@ async function superviseBackgroundRun(options) {
1095
1127
  return await finishEarly("failed", `Could not prepare the run: ${error.message}`);
1096
1128
  }
1097
1129
  let child;
1130
+ let windowsSignals;
1131
+ let cancellationPoll;
1098
1132
  const state = { exitCode: null, exitSignal: null };
1099
1133
  const stop = (signal) => {
1100
1134
  state.stopSignal ??= signal;
1101
1135
  if (!child?.pid) return;
1102
1136
  try {
1103
1137
  if (process.platform !== "win32") process.kill(-child.pid, "SIGTERM");
1104
- else child.kill("SIGTERM");
1138
+ else if (!windowsSignals?.forward("SIGTERM")) child.kill("SIGTERM");
1105
1139
  } catch {
1106
1140
  }
1107
1141
  };
1108
- const onSignal = (signal) => stop(signal);
1109
- process.on("SIGTERM", onSignal);
1110
- process.on("SIGINT", onSignal);
1111
- if (process.platform !== "win32") process.on("SIGHUP", onSignal);
1142
+ const onSigterm = () => stop("SIGTERM");
1143
+ const onSigint = () => stop("SIGINT");
1144
+ const onSighup = () => stop("SIGHUP");
1145
+ process.on("SIGTERM", onSigterm);
1146
+ process.on("SIGINT", onSigint);
1147
+ if (process.platform !== "win32") process.on("SIGHUP", onSighup);
1112
1148
  try {
1113
1149
  const spawnImpl = options.spawn ?? nodeSpawn;
1114
1150
  child = spawnImpl(options.execPath ?? process.execPath, [options.entrypoint, "--json", "pipeline", "run", "--spec", snapshot], {
1115
1151
  cwd: run.choices.workspace_root,
1116
- env: options.env ?? process.env,
1152
+ env: process.platform === "win32" ? { ...options.env ?? process.env, TT_PARENT_IPC: "1" } : options.env ?? process.env,
1117
1153
  detached: process.platform !== "win32",
1118
- stdio: ["ignore", resultFd, logFd]
1154
+ stdio: ["ignore", resultFd, logFd, ...process.platform === "win32" ? ["ipc"] : []],
1155
+ windowsHide: true
1119
1156
  });
1120
- const closed = new Promise((resolve13) => {
1157
+ windowsSignals = windowsChildSignals(child);
1158
+ const closed = new Promise((resolve14) => {
1121
1159
  child.once("error", (error) => {
1122
1160
  state.spawnError = error;
1123
- resolve13();
1161
+ resolve14();
1124
1162
  });
1125
1163
  child.once("close", (code2, signal) => {
1126
1164
  state.exitCode = code2;
1127
1165
  state.exitSignal = signal;
1128
- resolve13();
1166
+ resolve14();
1129
1167
  });
1130
1168
  });
1131
1169
  if (child.pid !== void 0) {
@@ -1135,12 +1173,27 @@ async function superviseBackgroundRun(options) {
1135
1173
  current.process = { ...current.process, host: hostname(), supervisor_pid: process.pid, pid: child.pid };
1136
1174
  }, { type: "started", message: `Pipeline process ${child.pid} started.` });
1137
1175
  if ((await store.get(run.id)).status === "cancel_requested") stop("SIGTERM");
1176
+ if (process.platform === "win32") {
1177
+ let polling = false;
1178
+ cancellationPoll = setInterval(() => {
1179
+ if (polling || state.stopSignal) return;
1180
+ polling = true;
1181
+ void store.get(run.id).then((current) => {
1182
+ if (current.status === "cancel_requested") stop("SIGTERM");
1183
+ }).catch(() => {
1184
+ }).finally(() => {
1185
+ polling = false;
1186
+ });
1187
+ }, 250);
1188
+ }
1138
1189
  }
1139
1190
  await closed;
1140
1191
  } finally {
1141
- process.off("SIGTERM", onSignal);
1142
- process.off("SIGINT", onSignal);
1143
- if (process.platform !== "win32") process.off("SIGHUP", onSignal);
1192
+ if (cancellationPoll) clearInterval(cancellationPoll);
1193
+ windowsSignals?.dispose();
1194
+ process.off("SIGTERM", onSigterm);
1195
+ process.off("SIGINT", onSigint);
1196
+ if (process.platform !== "win32") process.off("SIGHUP", onSighup);
1144
1197
  closeSync(logFd);
1145
1198
  closeSync(resultFd);
1146
1199
  await rm3(snapshot, { force: true });
@@ -1325,7 +1378,7 @@ function createBackgroundRunExecutor(args2) {
1325
1378
  // src/agent-labeling.ts
1326
1379
  import { createHash as createHash4 } from "crypto";
1327
1380
  import { existsSync as existsSync3, lstatSync as lstatSync2, readFileSync as readFileSync4 } from "fs";
1328
- import { isAbsolute as isAbsolute2, join as join5, relative as relative4 } from "path";
1381
+ import { isAbsolute as isAbsolute2, join as join6, relative as relative4 } from "path";
1329
1382
  import { Type } from "typebox";
1330
1383
 
1331
1384
  // src/commands/label.ts
@@ -1345,7 +1398,7 @@ import {
1345
1398
  readFileSync,
1346
1399
  writeFileSync
1347
1400
  } from "fs";
1348
- import { join as join4 } from "path";
1401
+ import { join as join5 } from "path";
1349
1402
  var AGENT_THINKING_LEVELS = [
1350
1403
  "off",
1351
1404
  "minimal",
@@ -1371,7 +1424,7 @@ function validateAccessToken(value) {
1371
1424
  }
1372
1425
  var CONFIG_FILE_NAME = "config.json";
1373
1426
  function getConfigPath() {
1374
- return join4(getConfigDir(), CONFIG_FILE_NAME);
1427
+ return join5(getConfigDir(), CONFIG_FILE_NAME);
1375
1428
  }
1376
1429
  function readConfig() {
1377
1430
  const path = getConfigPath();
@@ -1705,7 +1758,7 @@ function formatCents(cents) {
1705
1758
  return `$${(cents / 100).toFixed(2)}`;
1706
1759
  }
1707
1760
  function sleep(ms) {
1708
- return new Promise((resolve13) => setTimeout(resolve13, ms));
1761
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
1709
1762
  }
1710
1763
  function isTransientError(error) {
1711
1764
  if (error instanceof ApiError) return error.status === 429 || error.status >= 500;
@@ -2215,10 +2268,10 @@ function registerLabelCommands(parent) {
2215
2268
  console.log(`Follow progress with \`tt cloud label watch ${shortId(job.id)}\`.`);
2216
2269
  return;
2217
2270
  }
2218
- const finished = await watchJob(job.id, opts);
2219
- if (isJsonMode()) return printJson(finished);
2271
+ const finished2 = await watchJob(job.id, opts);
2272
+ if (isJsonMode()) return printJson(finished2);
2220
2273
  printSuccess(
2221
- `Labeling ${finished.status === "awaiting_review" ? "complete" : finished.status}: ${finished.labeled_count}/${finished.row_count} rows labeled.`
2274
+ `Labeling ${finished2.status === "awaiting_review" ? "complete" : finished2.status}: ${finished2.labeled_count}/${finished2.row_count} rows labeled.`
2222
2275
  );
2223
2276
  console.log(
2224
2277
  `Review with \`tt cloud label rows ${shortId(job.id)} --status labeled\`, then \`tt cloud label download ${shortId(job.id)}\` to write local training files.`
@@ -2453,7 +2506,7 @@ async function workspaceFile(workspaceRoot, file) {
2453
2506
  }
2454
2507
  let current = root;
2455
2508
  for (const part of parts) {
2456
- current = join5(current, part);
2509
+ current = join6(current, part);
2457
2510
  let info;
2458
2511
  try {
2459
2512
  info = lstatSync2(current);
@@ -2629,7 +2682,7 @@ async function revalidateLabelDownload(workspaceRoot, args2) {
2629
2682
  if (isAbsolute2(String(args2.out_dir)) || parts.some((part) => !part || part === "." || part === "..")) {
2630
2683
  throw new Error("Download directory must be inside the current workspace.");
2631
2684
  }
2632
- const outDir = join5(spec.root, ...parts);
2685
+ const outDir = join6(spec.root, ...parts);
2633
2686
  assertLabelDownloadPaths(outDir, args2.overwrite === true);
2634
2687
  return {
2635
2688
  workspaceRoot,
@@ -3063,7 +3116,7 @@ async function rejectPreparedAction(action, persist) {
3063
3116
  // src/agent-model.ts
3064
3117
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
3065
3118
  import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3066
- import { dirname as dirname5, join as join6 } from "path";
3119
+ import { dirname as dirname5, join as join7 } from "path";
3067
3120
  function missingProviderAuthMessage() {
3068
3121
  return "No API key is saved for the selected model. Run /login, or /model to pick a different one.";
3069
3122
  }
@@ -3162,10 +3215,10 @@ function resolveAgentModelDefinition(runtime, selection) {
3162
3215
  return { model, thinking: selection.thinking };
3163
3216
  }
3164
3217
  function getAgentAuthPath() {
3165
- return join6(getAgentConfigDir(), "auth.json");
3218
+ return join7(getAgentConfigDir(), "auth.json");
3166
3219
  }
3167
3220
  function getAgentModelsPath() {
3168
- return join6(getAgentConfigDir(), "models.json");
3221
+ return join7(getAgentConfigDir(), "models.json");
3169
3222
  }
3170
3223
  async function createPiModelRuntime(env = process.env) {
3171
3224
  const agentDir = getAgentConfigDir();
@@ -3577,7 +3630,7 @@ async function fetchHuggingFaceSearch(kind, query, limit, filters, options) {
3577
3630
  let lastError = new Error("Hugging Face search failed.");
3578
3631
  for (let attempt = 0; attempt < 2; attempt += 1) {
3579
3632
  if (attempt > 0) {
3580
- await new Promise((resolve13) => setTimeout(resolve13, options.huggingFaceRetryDelayMs ?? 750));
3633
+ await new Promise((resolve14) => setTimeout(resolve14, options.huggingFaceRetryDelayMs ?? 750));
3581
3634
  }
3582
3635
  try {
3583
3636
  const payload = await fetchHuggingFaceJson(url, options);
@@ -3958,7 +4011,7 @@ function createTunedTensorTools(api, options = {}) {
3958
4011
  dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
3959
4012
  run: "tt pipeline run --spec tunedtensor.json"
3960
4013
  },
3961
- note: "Runs locally on CPU, Apple MPS or CUDA; runtime.gpu (AWS) is not used. The tuned model directory loads in Python with laya.load(<model_dir>).",
4014
+ note: "Runs locally on CPU, Apple MPS or CUDA; runtime.gpu (AWS) is not used. The tuned model directory loads in Python with laya.load(<model_dir>). For paid serverless CPU hosting, the user can run tt cloud endpoints create --model <model_dir> --name <name>; see docs/local-runtime/hosting.md. This uploads weights to the TT account, returns an authenticated inference URL and scoped key, and charges prepaid credits per successful request.",
3962
4015
  ...host ? { host } : {}
3963
4016
  };
3964
4017
  }
@@ -4142,11 +4195,21 @@ function createTunedTensorTools(api, options = {}) {
4142
4195
  }, { additionalProperties: false }),
4143
4196
  async (p) => {
4144
4197
  const current = await inspectLocalSpec(workspaceRoot, p.spec_path);
4198
+ const spec = record4(current.document);
4199
+ const inline = current.validation.valid && spec && (spec.engine ?? "adapter") === "adapter" && !spec.dataset_prebuilt && Array.isArray(spec.examples) ? spec.examples : void 0;
4200
+ const split = inline ? splitSpecExamples(inline, 0) : void 0;
4145
4201
  return {
4146
4202
  spec_path: current.displayPath,
4147
4203
  sha256: current.sha256,
4148
4204
  spec: current.document ?? current.source,
4149
- validation: current.validation
4205
+ validation: current.validation,
4206
+ ...split ? { inline_data_split: {
4207
+ method: "deterministic_holdout",
4208
+ training_examples: split.train.length,
4209
+ heldout_examples: split.holdout.length,
4210
+ real_training_minimum_examples: 2,
4211
+ note: "Inline adapter data is split automatically. A small held-out set is not evidence of model quality."
4212
+ } } : {}
4150
4213
  };
4151
4214
  }
4152
4215
  ),
@@ -4485,6 +4548,8 @@ function createTunedTensorTools(api, options = {}) {
4485
4548
  // src/local-agent-client.ts
4486
4549
  var MAX_TOOL_CALLS_PER_TURN = 12;
4487
4550
  var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
4551
+ Use get_local_spec's inline_data_split counts when describing adapter training and evaluation data. An automatically held-out set is distinct from training, but a small set is not evidence of model quality.
4552
+ Downloads may also write TT manifests, reports and logs. Summarize the proposed effects without claiming the cache and lock are the only writes.
4488
4553
  The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes; use add_examples to append examples. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new one with prepare_create_local_spec; it is created as tunedtensor.json in the current workspace. For an initial draft, default to three concise representative examples unless the user requests otherwise; more examples can be added later. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file. For direct review, point users to /spec (a readable overview), /spec <section> such as /spec examples, /spec show for the JSON, and /spec diff, /spec validate and /spec history. General educational or inspection questions do not require a spec mutation.
4489
4554
  Local workflow commands need no TT access token. For local inspection commands such as runs, models, or doctor, tell the user to run the matching TT command in this shell (for example \`runs list\` or \`doctor\`); those commands execute outside the agent. User-owned AWS GPU training uses \`tt pipeline run\` with runtime.gpu configuration in tunedtensor.json, without a TT token. \`tt cloud\` is for TT account records, not GPU execution; hosted training start/estimate are retired. AWS credentials authorize instance lookup; separate SSH access is required. The instance must already be running, and the user manages quota, capacity, costs and shutdown. Keep the laptop running until outputs return. Model-provider choice is independent of GPU placement.
4490
4555
  When the user requests commands to inspect a run or serve a result, provide the direct CLI handoff: \`tt runs list\` to find a run, \`tt runs get <run-id>\` for its details, \`tt runs report <run-id>\` for its evaluation report, \`tt models list\` to find the resulting model ID, and \`tt serve <model-id>\` to serve that model. Replace placeholders with actual IDs from those commands; never invent results or IDs. Until the user supplies command output, keep literal <run-id> and <model-id> placeholders; do not fabricate example IDs or prefixes. Local run IDs are UUIDs and adapter model IDs are local-<run-id>. Do not refresh hardware or propose spec edits merely to supply those commands; perform those actions only when requested.
@@ -4602,11 +4667,11 @@ function createLocalAgentClient(options) {
4602
4667
  const runs = options.backgroundRuns?.store;
4603
4668
  if (!runs) return void 0;
4604
4669
  try {
4605
- const finished = (await runs.list()).filter((run) => run.requested_by.thread_id === threadId && !isActiveBackgroundRun(run) && !run.reported_at);
4606
- if (finished.length === 0) return void 0;
4607
- await runs.markReported(finished.map((run) => run.id));
4670
+ const finished2 = (await runs.list()).filter((run) => run.requested_by.thread_id === threadId && !isActiveBackgroundRun(run) && !run.reported_at);
4671
+ if (finished2.length === 0) return void 0;
4672
+ await runs.markReported(finished2.map((run) => run.id));
4608
4673
  return `[Background run outcomes, recorded by tt]
4609
- ${finished.map((run) => JSON.stringify(describeBackgroundRun(run))).join("\n")}
4674
+ ${finished2.map((run) => JSON.stringify(describeBackgroundRun(run))).join("\n")}
4610
4675
  These runs finished since your last turn. Mention them briefly when relevant; use get_background_run for logs.`;
4611
4676
  } catch {
4612
4677
  return void 0;
@@ -4851,13 +4916,12 @@ import {
4851
4916
  mkdir as mkdir2,
4852
4917
  readFile as readFile3,
4853
4918
  readdir as readdir2,
4854
- rename,
4855
4919
  stat,
4856
4920
  unlink,
4857
4921
  writeFile as writeFile4
4858
4922
  } from "fs/promises";
4859
4923
  import { randomUUID as randomUUID6 } from "crypto";
4860
- import { join as join7 } from "path";
4924
+ import { join as join8 } from "path";
4861
4925
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4862
4926
  var DEFAULT_MAX_THREAD_BYTES = 1e6;
4863
4927
  var DEFAULT_MAX_MESSAGES = 200;
@@ -4888,8 +4952,8 @@ function scrub(value, secrets) {
4888
4952
  var LocalAgentStore = class {
4889
4953
  constructor(rootDir, options = {}) {
4890
4954
  this.rootDir = rootDir;
4891
- this.threadsDir = join7(rootDir, "threads");
4892
- this.claimsDir = join7(rootDir, "action-claims");
4955
+ this.threadsDir = join8(rootDir, "threads");
4956
+ this.claimsDir = join8(rootDir, "action-claims");
4893
4957
  this.secrets = options.secretValues?.filter(Boolean) ?? [];
4894
4958
  this.secretValueProvider = options.secretValueProvider;
4895
4959
  this.maxThreadBytes = options.maxThreadBytes ?? DEFAULT_MAX_THREAD_BYTES;
@@ -4927,7 +4991,7 @@ var LocalAgentStore = class {
4927
4991
  }
4928
4992
  path(id) {
4929
4993
  assertThreadId(id);
4930
- return join7(this.threadsDir, `${id}.json`);
4994
+ return join8(this.threadsDir, `${id}.json`);
4931
4995
  }
4932
4996
  serializeWithinQuota(state) {
4933
4997
  const active = state.actions.filter(
@@ -4966,11 +5030,11 @@ var LocalAgentStore = class {
4966
5030
  return serialized;
4967
5031
  }
4968
5032
  async pruneDirectory(directory, suffix, maximum, preserve) {
4969
- const entries = await Promise.all((await readdir2(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(join7(directory, name))).mtimeMs })));
5033
+ const entries = await Promise.all((await readdir2(directory)).filter((name) => suffix.test(name) && name !== preserve).map(async (name) => ({ name, modified: (await stat(join8(directory, name))).mtimeMs })));
4970
5034
  entries.sort((a, b) => a.modified - b.modified);
4971
5035
  const keepOther = Math.max(0, maximum - (preserve ? 1 : 0));
4972
5036
  for (const entry of entries.slice(0, Math.max(0, entries.length - keepOther))) {
4973
- await unlink(join7(directory, entry.name));
5037
+ await unlink(join8(directory, entry.name));
4974
5038
  }
4975
5039
  }
4976
5040
  async pruneThreads(preserve) {
@@ -4990,19 +5054,19 @@ var LocalAgentStore = class {
4990
5054
  }
4991
5055
  const candidates = await Promise.all(names.filter((name) => !protectedNames.has(name)).map(async (name) => ({
4992
5056
  name,
4993
- modified: (await stat(join7(this.threadsDir, name))).mtimeMs
5057
+ modified: (await stat(join8(this.threadsDir, name))).mtimeMs
4994
5058
  })));
4995
5059
  candidates.sort((a, b) => a.modified - b.modified);
4996
5060
  const keep = Math.max(0, this.maxThreads - protectedNames.size);
4997
5061
  for (const entry of candidates.slice(0, Math.max(0, candidates.length - keep))) {
4998
- await unlink(join7(this.threadsDir, entry.name));
5062
+ await unlink(join8(this.threadsDir, entry.name));
4999
5063
  }
5000
5064
  }
5001
5065
  async save(state) {
5002
5066
  assertThreadId(state.thread.id);
5003
5067
  await this.ensureDirectories();
5004
5068
  const destination = this.path(state.thread.id);
5005
- const temporary = join7(
5069
+ const temporary = join8(
5006
5070
  this.threadsDir,
5007
5071
  `.${state.thread.id}.${randomUUID6()}.tmp`
5008
5072
  );
@@ -5015,14 +5079,13 @@ var LocalAgentStore = class {
5015
5079
  }
5016
5080
  await writeFile4(temporary, serialized, { mode: 384, flag: "wx" });
5017
5081
  await chmod(temporary, 384);
5018
- await rename(temporary, destination);
5019
- await chmod(destination, 384);
5082
+ await renameAtomicFile(temporary, destination);
5020
5083
  await this.pruneThreads(`${state.thread.id}.json`);
5021
5084
  }
5022
5085
  async claimAction(actionId) {
5023
5086
  assertThreadId(actionId);
5024
5087
  await this.ensureDirectories();
5025
- const path = join7(this.claimsDir, `${actionId}.claim`);
5088
+ const path = join8(this.claimsDir, `${actionId}.claim`);
5026
5089
  try {
5027
5090
  await writeFile4(path, `${(/* @__PURE__ */ new Date()).toISOString()}
5028
5091
  `, {
@@ -6331,7 +6394,7 @@ function createCommandCompleter(getMode = () => "local") {
6331
6394
 
6332
6395
  // src/shell-context.ts
6333
6396
  import { readFile as readFile4, readdir as readdir3, stat as stat2 } from "fs/promises";
6334
- import { basename as basename5, dirname as dirname6, join as join8, resolve as resolve3 } from "path";
6397
+ import { basename as basename5, dirname as dirname6, join as join9, resolve as resolve3 } from "path";
6335
6398
  async function readJsonObject(path) {
6336
6399
  try {
6337
6400
  const parsed = JSON.parse(await readFile4(path, "utf8"));
@@ -6358,7 +6421,7 @@ function expandPath(value, baseDirectory, env) {
6358
6421
  return expandUserPath(value, env, baseDirectory);
6359
6422
  }
6360
6423
  function configPath(env) {
6361
- return join8(getConfigDir(env), "config.json");
6424
+ return join9(getConfigDir(env), "config.json");
6362
6425
  }
6363
6426
  function agentSelectionFrom(env, configAgent, hasToken) {
6364
6427
  const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
@@ -6376,14 +6439,14 @@ async function adjacentFile(path) {
6376
6439
  }
6377
6440
  }
6378
6441
  async function readActiveModelId(storeRoot) {
6379
- const result2 = await readJsonObject(join8(storeRoot, "active-model.json"));
6442
+ const result2 = await readJsonObject(join9(storeRoot, "active-model.json"));
6380
6443
  if (!result2.found || result2.invalid) return void 0;
6381
6444
  const modelId = result2.value?.model_id;
6382
6445
  if (modelId === null) return "base";
6383
6446
  return typeof modelId === "string" && modelId ? modelId : void 0;
6384
6447
  }
6385
6448
  async function readLatestRun(storeRoot) {
6386
- const runsRoot = join8(storeRoot, "runs");
6449
+ const runsRoot = join9(storeRoot, "runs");
6387
6450
  let names;
6388
6451
  try {
6389
6452
  names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -6391,7 +6454,7 @@ async function readLatestRun(storeRoot) {
6391
6454
  return void 0;
6392
6455
  }
6393
6456
  const states = await Promise.all(names.map(async (name) => {
6394
- const result2 = await readJsonObject(join8(runsRoot, name, "state.json"));
6457
+ const result2 = await readJsonObject(join9(runsRoot, name, "state.json"));
6395
6458
  if (!result2.found || result2.invalid || !result2.value) return void 0;
6396
6459
  const id = stringField(result2.value, "id") ?? name;
6397
6460
  const state = {
@@ -6428,7 +6491,7 @@ async function discoverShellContext(options = {}) {
6428
6491
  const env = options.env ?? process.env;
6429
6492
  const cwd = resolve3(options.cwd ?? process.cwd());
6430
6493
  const warnings = [];
6431
- const specPath = join8(cwd, "tunedtensor.json");
6494
+ const specPath = join9(cwd, "tunedtensor.json");
6432
6495
  const specJson = await readJsonObject(specPath);
6433
6496
  let spec;
6434
6497
  if (specJson.found) {
@@ -6453,7 +6516,7 @@ async function discoverShellContext(options = {}) {
6453
6516
  };
6454
6517
  }
6455
6518
  }
6456
- const localConfigPath = join8(cwd, "local-runner.json");
6519
+ const localConfigPath = join9(cwd, "local-runner.json");
6457
6520
  const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
6458
6521
  const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
6459
6522
  if (localConfigJson.invalid) {
@@ -6787,12 +6850,12 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
6787
6850
  terminal.setRawMode(true);
6788
6851
  input.resume();
6789
6852
  output.write(`${message} ${hint()}`);
6790
- const choice = await new Promise((resolve13) => {
6853
+ const choice = await new Promise((resolve14) => {
6791
6854
  const finish = (decision) => {
6792
6855
  input.removeListener("keypress", onKeypress);
6793
6856
  input.removeListener("end", onEnd);
6794
6857
  input.removeListener("close", onEnd);
6795
- resolve13(decision);
6858
+ resolve14(decision);
6796
6859
  };
6797
6860
  const onKeypress = (text3, key) => {
6798
6861
  const decided = keyChoiceFor(key, text3);
@@ -6834,7 +6897,7 @@ function operatorError(operator) {
6834
6897
  `Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
6835
6898
  );
6836
6899
  }
6837
- function tokenizeShellInput(input) {
6900
+ function tokenizeShellInput(input, platform = process.platform) {
6838
6901
  if (input.includes("\0")) {
6839
6902
  throw new ShellParseError("Command input must not contain NUL bytes.");
6840
6903
  }
@@ -6869,7 +6932,7 @@ function tokenizeShellInput(input) {
6869
6932
  quote = "none";
6870
6933
  continue;
6871
6934
  }
6872
- if (character === "\\") {
6935
+ if (character === "\\" && platform !== "win32") {
6873
6936
  const escaped = input[index + 1];
6874
6937
  if (escaped === void 0) {
6875
6938
  throw new ShellParseError("Command ends with an unfinished escape.");
@@ -6901,7 +6964,7 @@ function tokenizeShellInput(input) {
6901
6964
  tokenStarted = true;
6902
6965
  continue;
6903
6966
  }
6904
- if (character === "\\") {
6967
+ if (character === "\\" && platform !== "win32") {
6905
6968
  const escaped = input[index + 1];
6906
6969
  if (escaped === void 0) {
6907
6970
  throw new ShellParseError("Command ends with an unfinished escape.");
@@ -8446,7 +8509,7 @@ var TunedTensorAgentSession = class {
8446
8509
  // src/local-runner.ts
8447
8510
  import { constants as osConstants } from "os";
8448
8511
  import { randomUUID as randomUUID7 } from "crypto";
8449
- import { spawn } from "child_process";
8512
+ import { spawn as spawn2 } from "child_process";
8450
8513
  import { existsSync as existsSync5 } from "fs";
8451
8514
  import { fileURLToPath as fileURLToPath2 } from "url";
8452
8515
  import {
@@ -8459,7 +8522,7 @@ import {
8459
8522
  basename as basename6,
8460
8523
  dirname as dirname7,
8461
8524
  isAbsolute as isAbsolute4,
8462
- join as join9,
8525
+ join as join10,
8463
8526
  resolve as resolve5
8464
8527
  } from "path";
8465
8528
 
@@ -9248,17 +9311,17 @@ function positionalSpecArgument(args2, start, defaultPath) {
9248
9311
  function selectedSpecArgument(args2, cwd) {
9249
9312
  const command2 = args2[0];
9250
9313
  if (["doctor", "validate", "run"].includes(command2 ?? "")) {
9251
- return positionalSpecArgument(args2, 1, join9(cwd, DEFAULT_SPEC_NAME));
9314
+ return positionalSpecArgument(args2, 1, join10(cwd, DEFAULT_SPEC_NAME));
9252
9315
  }
9253
9316
  if (command2 === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
9254
- return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
9317
+ return positionalSpecArgument(args2, 2, join10(cwd, DEFAULT_SPEC_NAME));
9255
9318
  }
9256
9319
  if (command2 === "datasets") {
9257
9320
  if (args2[1] === void 0 || args2[1].startsWith("-") || args2[1].endsWith(".json")) {
9258
9321
  args2.splice(1, 0, "prefetch");
9259
9322
  }
9260
9323
  if (["prefetch", "verify"].includes(args2[1] ?? "")) {
9261
- return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
9324
+ return positionalSpecArgument(args2, 2, join10(cwd, DEFAULT_SPEC_NAME));
9262
9325
  }
9263
9326
  }
9264
9327
  const serves = command2 === "serve" || command2 === "models" && args2[1] === "serve";
@@ -9346,7 +9409,7 @@ async function projectSpecArguments(originalArgs, cwd) {
9346
9409
  }
9347
9410
  };
9348
9411
  }
9349
- const temporaryPath = join9(
9412
+ const temporaryPath = join10(
9350
9413
  dirname7(sourcePath),
9351
9414
  `.${basename6(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
9352
9415
  );
@@ -9416,18 +9479,20 @@ async function spawnLocalCli(args2) {
9416
9479
  const childEnv = errorTerminal.isTTY && !args2.jsonMode && args2.env.TT_PROGRESS_TTY === void 0 ? { ...args2.env, TT_PROGRESS_TTY: "1", TT_PROGRESS_COLUMNS: String(errorTerminal.columns ?? 100) } : args2.env;
9417
9480
  const pipeInput = Boolean(args2.stdin && args2.stdin !== process.stdin);
9418
9481
  const pipeStreamingOutput = args2.streamingStdout && Boolean(args2.stdout && args2.stdout !== process.stdout);
9419
- const child = spawn(
9482
+ const child = spawn2(
9420
9483
  args2.nodeExecutable,
9421
9484
  [args2.entrypoint, ...args2.commandArgs],
9422
9485
  {
9423
9486
  cwd: args2.cwd,
9424
- env: childEnv,
9487
+ env: process.platform === "win32" ? { ...childEnv, TT_PARENT_IPC: "1" } : childEnv,
9425
9488
  stdio: [
9426
9489
  pipeInput ? "pipe" : "inherit",
9427
9490
  args2.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
9428
- "pipe"
9491
+ "pipe",
9492
+ ...process.platform === "win32" ? ["ipc"] : []
9429
9493
  ],
9430
- detached: process.platform !== "win32"
9494
+ detached: process.platform !== "win32",
9495
+ windowsHide: true
9431
9496
  }
9432
9497
  );
9433
9498
  if (pipeInput && args2.stdin && child.stdin) args2.stdin.pipe(child.stdin);
@@ -9456,8 +9521,10 @@ async function spawnLocalCli(args2) {
9456
9521
  });
9457
9522
  return new Promise((resolveChild) => {
9458
9523
  let spawnError;
9524
+ const windowsSignals = windowsChildSignals(child);
9459
9525
  const forwardSignal = (signal) => {
9460
9526
  const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
9527
+ if (windowsSignals.forward(childSignal)) return;
9461
9528
  if (child.pid && process.platform !== "win32") {
9462
9529
  try {
9463
9530
  process.kill(-child.pid, childSignal);
@@ -9470,14 +9537,16 @@ async function spawnLocalCli(args2) {
9470
9537
  const onSigint = () => forwardSignal("SIGINT");
9471
9538
  const onSigterm = () => forwardSignal("SIGTERM");
9472
9539
  const onSighup = () => forwardSignal("SIGHUP");
9540
+ const signals = args2.signals ?? process;
9473
9541
  const cleanup = () => {
9474
- process.off("SIGINT", onSigint);
9475
- process.off("SIGTERM", onSigterm);
9476
- if (process.platform !== "win32") process.off("SIGHUP", onSighup);
9542
+ windowsSignals.dispose();
9543
+ signals.off("SIGINT", onSigint);
9544
+ signals.off("SIGTERM", onSigterm);
9545
+ if (process.platform !== "win32") signals.off("SIGHUP", onSighup);
9477
9546
  };
9478
- process.on("SIGINT", onSigint);
9479
- process.on("SIGTERM", onSigterm);
9480
- if (process.platform !== "win32") process.on("SIGHUP", onSighup);
9547
+ signals.on("SIGINT", onSigint);
9548
+ signals.on("SIGTERM", onSigterm);
9549
+ if (process.platform !== "win32") signals.on("SIGHUP", onSighup);
9481
9550
  child.once("error", (error) => {
9482
9551
  spawnError = error;
9483
9552
  });
@@ -9528,6 +9597,7 @@ async function runLocalCommand(args2, options = {}) {
9528
9597
  stdin: options.stdin,
9529
9598
  stdout: options.stdout,
9530
9599
  stderr: options.stderr,
9600
+ signals: options.signals,
9531
9601
  pathReplacements: projected.pathReplacements
9532
9602
  });
9533
9603
  const parsed = parseJsonOutput(result2.stdout);
@@ -9715,11 +9785,11 @@ function registerAgentCommands(parent, options) {
9715
9785
 
9716
9786
  // src/commands/pipeline.ts
9717
9787
  import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
9718
- import { basename as basename8, dirname as dirname11, join as join13, resolve as resolve8 } from "path";
9788
+ import { basename as basename8, dirname as dirname11, join as join14, resolve as resolve8 } from "path";
9719
9789
 
9720
9790
  // src/local-runtime/project-migration.ts
9721
- import { lstat as lstat3, readFile as readFile6, rename as rename2, writeFile as writeFile6, unlink as unlink2 } from "fs/promises";
9722
- import { dirname as dirname8, join as join10 } from "path";
9791
+ import { lstat as lstat3, readFile as readFile6, rename, writeFile as writeFile6, unlink as unlink2 } from "fs/promises";
9792
+ import { dirname as dirname8, join as join11 } from "path";
9723
9793
  async function migrateProjectWorkflow(specPath) {
9724
9794
  const info = await lstat3(specPath);
9725
9795
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
@@ -9730,7 +9800,7 @@ async function migrateProjectWorkflow(specPath) {
9730
9800
  const files = [];
9731
9801
  const sources = /* @__PURE__ */ new Map([[specPath, original]]);
9732
9802
  for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
9733
- const path = join10(dirname8(specPath), name);
9803
+ const path = join11(dirname8(specPath), name);
9734
9804
  const entry = await lstat3(path).catch((error) => {
9735
9805
  if (error.code === "ENOENT") return void 0;
9736
9806
  throw error;
@@ -9779,7 +9849,7 @@ async function migrateProjectWorkflow(specPath) {
9779
9849
  throw new Error(`Configuration changed during migration: ${path}; retry after reviewing it.`);
9780
9850
  }
9781
9851
  }
9782
- await rename2(temp, specPath);
9852
+ await rename(temp, specPath);
9783
9853
  } catch (error) {
9784
9854
  if (createdTemp) await unlink2(temp).catch(() => {
9785
9855
  });
@@ -9797,8 +9867,8 @@ async function migrateProjectWorkflow(specPath) {
9797
9867
  // src/local-runtime/foundation-runner.ts
9798
9868
  import { createHash as createHash7, randomUUID as randomUUID8 } from "crypto";
9799
9869
  import { createReadStream } from "fs";
9800
- import { chmod as chmod2, lstat as lstat4, mkdir as mkdir3, open as open2, readdir as readdir4, readFile as readFile7, rename as rename3, rm as rm5 } from "fs/promises";
9801
- import { basename as basename7, dirname as dirname9, join as join11, resolve as resolve6 } from "path";
9870
+ import { chmod as chmod2, lstat as lstat4, mkdir as mkdir3, open as open2, readdir as readdir4, readFile as readFile7, rename as rename2, rm as rm5 } from "fs/promises";
9871
+ import { basename as basename7, dirname as dirname9, join as join12, resolve as resolve6 } from "path";
9802
9872
  var INSTRUCTION_CORPUS_VERSION = 1;
9803
9873
  function isRef(value) {
9804
9874
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
@@ -9827,7 +9897,7 @@ async function hashPath(path) {
9827
9897
  if (info.isDirectory()) {
9828
9898
  for (const name of (await readdir4(path)).sort()) {
9829
9899
  hash.update(name);
9830
- hash.update(await hashPath(join11(path, name)));
9900
+ hash.update(await hashPath(join12(path, name)));
9831
9901
  }
9832
9902
  return hash.digest("hex");
9833
9903
  }
@@ -9850,7 +9920,7 @@ async function makePrivateTree(path) {
9850
9920
  if (info.isDirectory()) {
9851
9921
  await chmod2(path, 448);
9852
9922
  for (const name of await readdir4(path)) {
9853
- await makePrivateTree(join11(path, name));
9923
+ await makePrivateTree(join12(path, name));
9854
9924
  }
9855
9925
  return;
9856
9926
  }
@@ -9879,7 +9949,7 @@ async function readRequiredJsonObject(path, description) {
9879
9949
  return parsed;
9880
9950
  }
9881
9951
  async function writePrivateTextAtomic(path, content) {
9882
- const temporary = join11(dirname9(path), `.${basename7(path)}.${randomUUID8()}.tmp`);
9952
+ const temporary = join12(dirname9(path), `.${basename7(path)}.${randomUUID8()}.tmp`);
9883
9953
  const file = await open2(temporary, "wx", 384);
9884
9954
  let closed = false;
9885
9955
  try {
@@ -9888,7 +9958,7 @@ async function writePrivateTextAtomic(path, content) {
9888
9958
  await file.sync();
9889
9959
  await file.close();
9890
9960
  closed = true;
9891
- await rename3(temporary, path);
9961
+ await rename2(temporary, path);
9892
9962
  } finally {
9893
9963
  if (!closed) await file.close().catch(() => void 0);
9894
9964
  await rm5(temporary, { force: true }).catch(() => void 0);
@@ -9995,7 +10065,7 @@ async function runFoundationPipeline(args2) {
9995
10065
  assertFoundationPlanSupported(args2.spec, args2.plan);
9996
10066
  assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
9997
10067
  const outputDir = resolve6(
9998
- args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join11(resolve6(dirname9(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join11(dirname9(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
10068
+ args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join12(resolve6(dirname9(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join12(dirname9(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
9999
10069
  );
10000
10070
  if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
10001
10071
  throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
@@ -10020,7 +10090,7 @@ async function runFoundationPipeline(args2) {
10020
10090
  await mkdir3(outputDir, { recursive: true, mode: 448 });
10021
10091
  }
10022
10092
  await makePrivateTree(outputDir);
10023
- const workflowPath = join11(outputDir, "resolved-workflow.json");
10093
+ const workflowPath = join12(outputDir, "resolved-workflow.json");
10024
10094
  const workflow = { spec: args2.spec, plan: args2.plan };
10025
10095
  if (outputExists) {
10026
10096
  const previous = await readFile7(workflowPath, "utf8").catch((error) => {
@@ -10053,16 +10123,16 @@ async function runFoundationPipeline(args2) {
10053
10123
  throw new Error(`Missing ${kind} artifact.`);
10054
10124
  };
10055
10125
  for (const step of args2.plan.steps) {
10056
- const stepDir = join11(outputDir, step.id);
10057
- const output = join11(stepDir, "output");
10126
+ const stepDir = join12(outputDir, step.id);
10127
+ const output = join12(stepDir, "output");
10058
10128
  await mkdir3(stepDir, { recursive: true, mode: 448 });
10059
10129
  await chmod2(stepDir, 448);
10060
10130
  await mkdir3(output, { recursive: true, mode: 448 });
10061
10131
  await chmod2(output, 448);
10062
- const configPath2 = join11(stepDir, "config.json");
10063
- const logPath = join11(stepDir, "step.log");
10064
- const metricsPath = join11(output, "metrics.json");
10065
- const completionPath = join11(stepDir, "completion.json");
10132
+ const configPath2 = join12(stepDir, "config.json");
10133
+ const logPath = join12(stepDir, "step.log");
10134
+ const metricsPath = join12(output, "metrics.json");
10135
+ const completionPath = join12(stepDir, "completion.json");
10066
10136
  const fields = stepConfig(step);
10067
10137
  let entrypoint;
10068
10138
  let config;
@@ -10083,7 +10153,7 @@ async function runFoundationPipeline(args2) {
10083
10153
  config = {
10084
10154
  tokenizer_dir: tokenizerDir,
10085
10155
  output_dir: output,
10086
- work_dir: join11(stepDir, "recovery"),
10156
+ work_dir: join12(stepDir, "recovery"),
10087
10157
  depth: fields.depth ?? hp.depth,
10088
10158
  steps: fields.steps ?? hp.pretrain_steps,
10089
10159
  batch_size: fields.batchSize ?? hp.batch_size,
@@ -10188,15 +10258,15 @@ async function runFoundationPipeline(args2) {
10188
10258
  const artifacts = {};
10189
10259
  const record6 = {};
10190
10260
  if (step.uses === "tokenize") {
10191
- const tokenizerPath = join11(output, "tokenizer.json");
10261
+ const tokenizerPath = join12(output, "tokenizer.json");
10192
10262
  await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
10193
10263
  await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
10194
10264
  record6.tokenizer = output;
10195
10265
  tokenizerDir = output;
10196
10266
  artifacts.tokenizer = await artifactFor(output);
10197
10267
  } else if (step.uses === "pretrain" || step.uses === "finetune" || step.uses === "rl") {
10198
- const modelPath = join11(output, "model.safetensors");
10199
- const modelConfigPath = join11(output, "config.json");
10268
+ const modelPath = join12(output, "model.safetensors");
10269
+ const modelConfigPath = join12(output, "config.json");
10200
10270
  await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
10201
10271
  const modelConfig = await readRequiredJsonObject(
10202
10272
  modelConfigPath,
@@ -10211,7 +10281,7 @@ async function runFoundationPipeline(args2) {
10211
10281
  modelDir = output;
10212
10282
  artifacts.model = await artifactFor(output);
10213
10283
  } else if (step.uses === "evaluate") {
10214
- const reportPath2 = join11(output, "report.json");
10284
+ const reportPath2 = join12(output, "report.json");
10215
10285
  const stepReport = await readRequiredJsonObject(
10216
10286
  reportPath2,
10217
10287
  `Foundation step "${step.id}" report.json`
@@ -10251,7 +10321,7 @@ async function runFoundationPipeline(args2) {
10251
10321
  spec: args2.specPath,
10252
10322
  steps
10253
10323
  };
10254
- const reportPath = join11(outputDir, "report.json");
10324
+ const reportPath = join12(outputDir, "report.json");
10255
10325
  await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
10256
10326
  `);
10257
10327
  return {
@@ -10264,8 +10334,8 @@ async function runFoundationPipeline(args2) {
10264
10334
 
10265
10335
  // src/local-runtime/decision-runner.ts
10266
10336
  import { randomUUID as randomUUID9 } from "crypto";
10267
- import { lstat as lstat5, mkdir as mkdir4, readFile as readFile8, rename as rename4, rm as rm6, writeFile as writeFile7 } from "fs/promises";
10268
- import { dirname as dirname10, join as join12, resolve as resolve7 } from "path";
10337
+ import { lstat as lstat5, mkdir as mkdir4, readFile as readFile8, rename as rename3, rm as rm6, writeFile as writeFile7 } from "fs/promises";
10338
+ import { dirname as dirname10, join as join13, resolve as resolve7 } from "path";
10269
10339
  var PROBABILITY_FLOOR = 1e-6;
10270
10340
  function assertDecisionPlanSupported(plan) {
10271
10341
  for (const step of plan.steps) {
@@ -10428,7 +10498,7 @@ async function writePrivateAtomic(path, content) {
10428
10498
  const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
10429
10499
  try {
10430
10500
  await writeFile7(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
10431
- await rename4(temporary, path);
10501
+ await rename3(temporary, path);
10432
10502
  } finally {
10433
10503
  await rm6(temporary, { force: true });
10434
10504
  }
@@ -10447,7 +10517,7 @@ async function runDecisionPipeline(args2) {
10447
10517
  assertDecisionPlanSupported(args2.plan);
10448
10518
  const model = resolveDecisionModel(args2.spec.base_model);
10449
10519
  const specDir = dirname10(resolve7(args2.specPath));
10450
- const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join12(resolve7(specDir, args2.spec.runtime.artifactRoot), "decision-runs", randomUUID9()) : join12(specDir, DEFAULT_DECISION_RUNS_DIR, randomUUID9())));
10520
+ const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join13(resolve7(specDir, args2.spec.runtime.artifactRoot), "decision-runs", randomUUID9()) : join13(specDir, DEFAULT_DECISION_RUNS_DIR, randomUUID9())));
10451
10521
  if (await lstat5(outputDir).catch(() => null)) {
10452
10522
  throw new Error(`Decision output directory already exists: ${outputDir}`);
10453
10523
  }
@@ -10466,13 +10536,13 @@ async function runDecisionPipeline(args2) {
10466
10536
  const hp = args2.spec.hyperparameters ?? {};
10467
10537
  const device = hp.device ?? "auto";
10468
10538
  const evalIds = split.holdout.map((_, index) => `eval-${index}`);
10469
- const dataDir = join12(outputDir, "data");
10539
+ const dataDir = join13(outputDir, "data");
10470
10540
  await mkdir4(dataDir, { recursive: true, mode: 448 });
10471
- const trainPath = join12(dataDir, "train.jsonl");
10472
- const evalInputsPath = join12(dataDir, "eval-inputs.jsonl");
10541
+ const trainPath = join13(dataDir, "train.jsonl");
10542
+ const evalInputsPath = join13(dataDir, "eval-inputs.jsonl");
10473
10543
  await writePrivateAtomic(trainPath, jsonl(split.train));
10474
10544
  await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
10475
- await writePrivateJson2(join12(outputDir, "resolved-workflow.json"), {
10545
+ await writePrivateJson2(join13(outputDir, "resolved-workflow.json"), {
10476
10546
  spec: args2.spec,
10477
10547
  plan: args2.plan,
10478
10548
  model: { id: model.id, revision: model.revision },
@@ -10485,13 +10555,13 @@ async function runDecisionPipeline(args2) {
10485
10555
  const steps = [];
10486
10556
  let tunedModelDir;
10487
10557
  for (const step of args2.plan.steps) {
10488
- const stepDir = join12(outputDir, step.id);
10558
+ const stepDir = join13(outputDir, step.id);
10489
10559
  await mkdir4(stepDir, { recursive: true, mode: 448 });
10490
- const configPath2 = join12(stepDir, "config.json");
10491
- const logPath = join12(stepDir, "step.log");
10492
- const metricsPath = join12(stepDir, "metrics.json");
10560
+ const configPath2 = join13(stepDir, "config.json");
10561
+ const logPath = join13(stepDir, "step.log");
10562
+ const metricsPath = join13(stepDir, "metrics.json");
10493
10563
  if (step.uses === "train") {
10494
- const modelDir = join12(stepDir, "model");
10564
+ const modelDir = join13(stepDir, "model");
10495
10565
  args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
10496
10566
  await writePrivateJson2(configPath2, {
10497
10567
  model: model.id,
@@ -10511,8 +10581,8 @@ async function runDecisionPipeline(args2) {
10511
10581
  await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
10512
10582
  const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
10513
10583
  if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
10514
- await requireRegularNonemptyFile2(join12(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
10515
- await readJsonObject2(join12(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
10584
+ await requireRegularNonemptyFile2(join13(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
10585
+ await readJsonObject2(join13(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
10516
10586
  producedModels.set(step.id, modelDir);
10517
10587
  tunedModelDir = modelDir;
10518
10588
  steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
@@ -10523,7 +10593,7 @@ async function runDecisionPipeline(args2) {
10523
10593
  const tuned = source !== "base";
10524
10594
  const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
10525
10595
  if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
10526
- const predictionsPath = join12(stepDir, "predictions.jsonl");
10596
+ const predictionsPath = join13(stepDir, "predictions.jsonl");
10527
10597
  args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
10528
10598
  await writePrivateJson2(configPath2, {
10529
10599
  model: modelDir ?? model.id,
@@ -10537,7 +10607,7 @@ async function runDecisionPipeline(args2) {
10537
10607
  await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
10538
10608
  const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
10539
10609
  if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
10540
- const reportPath2 = join12(stepDir, "report.json");
10610
+ const reportPath2 = join13(stepDir, "report.json");
10541
10611
  const scored = scoreDecisionPredictions({
10542
10612
  kind: tuned ? "candidate" : "baseline",
10543
10613
  modelId: modelDir ? fileUri(modelDir) : model.id,
@@ -10561,10 +10631,10 @@ async function runDecisionPipeline(args2) {
10561
10631
  log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
10562
10632
  brier_delta: after.metrics.brier - before.metrics.brier
10563
10633
  };
10564
- await writePrivateJson2(join12(stepDir, "comparison.json"), comparison);
10634
+ await writePrivateJson2(join13(stepDir, "comparison.json"), comparison);
10565
10635
  steps.push({ id: step.id, uses: step.uses, comparison });
10566
10636
  }
10567
- const reportPath = join12(outputDir, "report.json");
10637
+ const reportPath = join13(outputDir, "report.json");
10568
10638
  const report = {
10569
10639
  status: "succeeded",
10570
10640
  engine: "decision",
@@ -11190,7 +11260,7 @@ function registerTopupCommands(parent) {
11190
11260
  import { createInterface as createInterface4 } from "readline/promises";
11191
11261
  import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
11192
11262
  import { homedir as homedir2 } from "os";
11193
- import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
11263
+ import { isAbsolute as isAbsolute5, join as join15, resolve as resolve9 } from "path";
11194
11264
  import { stdin as defaultStdin, stdout as defaultStdout } from "process";
11195
11265
  var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
11196
11266
  function expandPath2(value, baseDirectory, homeDirectory) {
@@ -11202,8 +11272,8 @@ function resolvePublishStoreRoot(options) {
11202
11272
  const cwd = options?.cwd ?? process.cwd();
11203
11273
  const env = options?.env ?? process.env;
11204
11274
  const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir2());
11205
- const localConfigPath = join14(cwd, "local-runner.json");
11206
- const projectPath = join14(cwd, "tunedtensor.json");
11275
+ const localConfigPath = join15(cwd, "local-runner.json");
11276
+ const projectPath = join15(cwd, "tunedtensor.json");
11207
11277
  if (existsSync7(projectPath)) {
11208
11278
  const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync8(projectPath, "utf8")));
11209
11279
  if (existsSync7(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
@@ -12272,31 +12342,166 @@ Eval Results (${data3._evals.length}):`);
12272
12342
  });
12273
12343
  }
12274
12344
 
12345
+ // src/commands/endpoints.ts
12346
+ import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
12347
+ import { createReadStream as createReadStream2, readFileSync as readFileSync11, statSync as statSync4, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
12348
+ import { open as open4 } from "fs/promises";
12349
+ import { finished } from "stream/promises";
12350
+ import { resolve as resolve10, join as join16 } from "path";
12351
+ import { z } from "zod";
12352
+ async function inspectCheckpoint(modelPath) {
12353
+ const directory = resolve10(modelPath);
12354
+ const config = JSON.parse(readFileSync11(join16(directory, "rl_agent_config.json"), "utf8"));
12355
+ if (config.encoder !== "answerdotai/ModernBERT-large" || config.head_layers !== 2 || config.max_len !== 512 || config.head_max_len !== 192) {
12356
+ throw new Error("Hosting currently supports only the pinned English Laya checkpoint and TT decision fine-tunes.");
12357
+ }
12358
+ const kind = config.tuned_tensor ? "tuned" : "base";
12359
+ if (kind === "tuned" && (JSON.stringify(config.temperature) !== "[1,1,1]" || config.temperature_by_options)) {
12360
+ throw new Error("Custom calibration is not supported by hosted decision inference.");
12361
+ }
12362
+ const file = join16(directory, "model.safetensors");
12363
+ const size = statSync4(file).size;
12364
+ if (size <= 8 || size > 2e9) throw new Error("Weights must be a safetensors file smaller than 2 GB. GPU and adapter models are not supported.");
12365
+ const fd = openSync3(file, "r");
12366
+ try {
12367
+ const length = Buffer.alloc(8);
12368
+ if (readSync(fd, length, 0, 8, 0) !== 8) throw new Error("Invalid safetensors header.");
12369
+ const headerSize = Number(length.readBigUInt64LE());
12370
+ if (!Number.isSafeInteger(headerSize) || headerSize < 2 || headerSize > 1e6 || headerSize + 8 >= size) throw new Error("Invalid safetensors header.");
12371
+ const header = Buffer.alloc(headerSize);
12372
+ if (readSync(fd, header, 0, headerSize, 8) !== headerSize) throw new Error("Truncated safetensors header.");
12373
+ const tensors = JSON.parse(header.toString("utf8"));
12374
+ for (const prefix of ["encoder.", "type_emb.", "scorer.", "act_head."]) {
12375
+ if (!Object.keys(tensors).some((key) => key.startsWith(prefix))) throw new Error("Not a complete Laya checkpoint.");
12376
+ }
12377
+ } finally {
12378
+ closeSync3(fd);
12379
+ }
12380
+ const hash = createHash8("sha256");
12381
+ for await (const chunk of createReadStream2(file)) hash.update(chunk);
12382
+ return { file, weights_bytes: size, weights_sha256: hash.digest("hex"), checkpoint_kind: kind };
12383
+ }
12384
+ function readInferencePayload(file) {
12385
+ const path = resolve10(file);
12386
+ if (statSync4(path).size > 131072) throw new Error("Inference JSON must be at most 128 KiB.");
12387
+ const data3 = JSON.parse(readFileSync11(path, "utf8"));
12388
+ return z.record(z.string(), z.json()).refine((value) => Object.keys(value).length > 0).parse(data3);
12389
+ }
12390
+ async function uploadWeights(endpoint, file) {
12391
+ const url = new URL(endpoint.upload.url);
12392
+ if (url.protocol !== "https:" || url.username || url.password || !/\.s3\.[a-z0-9-]+\.amazonaws\.com$/.test(url.hostname)) {
12393
+ throw new Error("The server returned an invalid model upload URL.");
12394
+ }
12395
+ const handle = await open4(file, "r");
12396
+ const stream = handle.createReadStream();
12397
+ const completion = finished(stream);
12398
+ void completion.catch(() => {
12399
+ });
12400
+ try {
12401
+ const response = await fetch(url, {
12402
+ method: "PUT",
12403
+ headers: endpoint.upload.headers,
12404
+ body: stream,
12405
+ duplex: "half",
12406
+ redirect: "error",
12407
+ signal: AbortSignal.timeout(60 * 6e4)
12408
+ });
12409
+ if (!response.ok) throw new Error(`Model upload failed (${response.status}). Delete endpoint ${endpoint.id} and retry.`);
12410
+ } finally {
12411
+ stream.destroy();
12412
+ await completion.catch(() => {
12413
+ });
12414
+ }
12415
+ }
12416
+ function show(endpoint, opts) {
12417
+ const result2 = { ...endpoint, invocation_url: new URL(endpoint.invocation_path, getBaseUrl(opts)).toString() };
12418
+ if (isJsonMode()) return printJson(result2);
12419
+ printDetail([
12420
+ ["Endpoint", result2.id],
12421
+ ["Name", result2.name],
12422
+ ["Status", result2.status],
12423
+ ["Inference URL", result2.invocation_url],
12424
+ ["Price per successful request", `${result2.price_cents} cents`],
12425
+ ...result2.inference_key ? [["Inference key (save securely)", result2.inference_key]] : []
12426
+ ]);
12427
+ }
12428
+ function registerEndpointCommands(parent) {
12429
+ const endpoints = parent.command("endpoints").description("Host small CPU decision models using prepaid TT credits");
12430
+ endpoints.command("create").description("Upload Laya weights and deploy a serverless endpoint").requiredOption("--model <directory>", "Local Laya checkpoint (e.g. a decision run's train/model)").requiredOption("--name <name>", "Endpoint display name").action(async (options) => {
12431
+ const opts = parent.optsWithGlobals();
12432
+ const { file, ...artifact } = await inspectCheckpoint(options.model);
12433
+ const { data: data3 } = await post("/endpoints", { name: options.name, format: "laya-safetensors-v1", ...artifact }, opts);
12434
+ if (!isJsonMode()) console.error(`Endpoint ${data3.id} created. Uploading ${(artifact.weights_bytes / 1e9).toFixed(2)} GB of weights...`);
12435
+ try {
12436
+ await uploadWeights(data3, file);
12437
+ const { data: deployed } = await post(`/endpoints/${data3.id}/deploy`, {}, opts);
12438
+ show({ ...deployed, inference_key: data3.inference_key }, opts);
12439
+ } catch (error) {
12440
+ throw new Error(`Endpoint ${data3.id}: ${error instanceof Error ? error.message : "upload/deployment failed"}. Use tt cloud endpoints get ${data3.id} or delete ${data3.id}. Rotate its key if the create response was lost.`, { cause: error });
12441
+ }
12442
+ });
12443
+ endpoints.command("list").action(async () => {
12444
+ const opts = parent.optsWithGlobals();
12445
+ const { data: data3 } = await get("/endpoints", void 0, opts);
12446
+ if (isJsonMode()) return printJson(data3);
12447
+ for (const row of data3) show(row, opts);
12448
+ });
12449
+ endpoints.command("get <id>").description("Check deployment or deletion status").action(async (id) => {
12450
+ z.uuid().parse(id);
12451
+ const opts = parent.optsWithGlobals();
12452
+ show((await get(`/endpoints/${id}`, void 0, opts)).data, opts);
12453
+ });
12454
+ endpoints.command("delete <id>").description("Stop serving and delete cloud weights and resources").action(async (id) => {
12455
+ z.uuid().parse(id);
12456
+ const opts = parent.optsWithGlobals();
12457
+ show((await del(`/endpoints/${id}`, opts)).data, opts);
12458
+ });
12459
+ endpoints.command("rotate-key <id>").description("Revoke the old inference key and return a new key").action(async (id) => {
12460
+ z.uuid().parse(id);
12461
+ const { data: data3 } = await post(`/endpoints/${id}/key`, {}, parent.optsWithGlobals());
12462
+ if (isJsonMode()) return printJson(data3);
12463
+ printDetail([["Inference key (save securely)", data3.inference_key]]);
12464
+ });
12465
+ endpoints.command("invoke <id>").requiredOption("--payload <file>", "Complete model request as a JSON object (up to 128 KiB)").option("--request-id <uuid>", "Reuse this ID to retry without a second charge").action(async (id, options) => {
12466
+ z.uuid().parse(id);
12467
+ const requestId = options.requestId ?? randomUUID10();
12468
+ z.uuid().parse(requestId);
12469
+ const payload = readInferencePayload(options.payload);
12470
+ if (!isJsonMode()) console.error(`Request ID: ${requestId} (reuse with --request-id when retrying)`);
12471
+ try {
12472
+ const { data: data3 } = await post(`/endpoints/${id}/invoke`, payload, parent.optsWithGlobals(), { "Idempotency-Key": requestId });
12473
+ printJson({ request_id: requestId, data: data3 });
12474
+ } catch (error) {
12475
+ throw new Error(`Request ${requestId}: ${error instanceof Error ? error.message : "inference failed"}. Retry with --request-id ${requestId}.`, { cause: error });
12476
+ }
12477
+ });
12478
+ }
12479
+
12275
12480
  // src/commands/models.ts
12276
- import { spawn as spawn3, execFileSync } from "child_process";
12277
- import { createHash as createHash8 } from "crypto";
12481
+ import { spawn as spawn4, execFileSync } from "child_process";
12482
+ import { createHash as createHash9 } from "crypto";
12278
12483
  import {
12279
12484
  existsSync as existsSync10,
12280
12485
  mkdirSync as mkdirSync5,
12281
- statSync as statSync4,
12486
+ statSync as statSync5,
12282
12487
  createWriteStream,
12283
12488
  unlinkSync,
12284
- readFileSync as readFileSync12,
12489
+ readFileSync as readFileSync13,
12285
12490
  writeFileSync as writeFileSync6,
12286
12491
  readdirSync,
12287
12492
  rmSync
12288
12493
  } from "fs";
12289
12494
  import { homedir as homedir3 } from "os";
12290
- import { dirname as dirname13, join as join15, resolve as resolve11, basename as basename9, normalize, isAbsolute as isAbsolute6 } from "path";
12495
+ import { dirname as dirname13, join as join17, resolve as resolve12, basename as basename9, normalize, isAbsolute as isAbsolute6 } from "path";
12291
12496
  import { Readable, Transform } from "stream";
12292
12497
  import { pipeline } from "stream/promises";
12293
12498
 
12294
12499
  // src/commands/init.ts
12295
- import { existsSync as existsSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
12296
- import { resolve as resolve10 } from "path";
12500
+ import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
12501
+ import { resolve as resolve11 } from "path";
12297
12502
  var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
12298
12503
  function loadSpec(filePath) {
12299
- const resolved = resolve10(filePath || DEFAULT_SPEC_FILE2);
12504
+ const resolved = resolve11(filePath || DEFAULT_SPEC_FILE2);
12300
12505
  if (!existsSync9(resolved)) {
12301
12506
  printError(
12302
12507
  `Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
@@ -12304,13 +12509,13 @@ Run \`tt init\` to create one.`
12304
12509
  );
12305
12510
  process.exit(1);
12306
12511
  }
12307
- const raw = JSON.parse(readFileSync11(resolved, "utf-8"));
12512
+ const raw = JSON.parse(readFileSync12(resolved, "utf-8"));
12308
12513
  return raw;
12309
12514
  }
12310
12515
 
12311
12516
  // src/serve-manager.ts
12312
- import { spawn as spawn2 } from "child_process";
12313
- import { randomUUID as randomUUID10 } from "crypto";
12517
+ import { spawn as spawn3 } from "child_process";
12518
+ import { randomUUID as randomUUID11 } from "crypto";
12314
12519
  import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
12315
12520
  import { createServer } from "http";
12316
12521
  import { createServer as createNetServer } from "net";
@@ -12327,10 +12532,10 @@ function parsePositiveInteger(value, fallback) {
12327
12532
  return Number.isInteger(value) && value >= 0 ? value : fallback;
12328
12533
  }
12329
12534
  function sleep2(ms) {
12330
- return new Promise((resolve13) => setTimeout(resolve13, ms));
12535
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
12331
12536
  }
12332
12537
  function readRequestBody(req) {
12333
- return new Promise((resolve13, reject) => {
12538
+ return new Promise((resolve14, reject) => {
12334
12539
  if (Number(req.headers["content-length"]) > MAX_SERVE_REQUEST_BYTES) {
12335
12540
  reject(new RequestTooLargeError());
12336
12541
  return;
@@ -12360,7 +12565,7 @@ function readRequestBody(req) {
12360
12565
  };
12361
12566
  const onEnd = () => {
12362
12567
  cleanup();
12363
- resolve13(Buffer.concat(chunks, bytes));
12568
+ resolve14(Buffer.concat(chunks, bytes));
12364
12569
  };
12365
12570
  req.on("data", onData);
12366
12571
  req.on("end", onEnd);
@@ -12437,7 +12642,7 @@ function errorSummary(response, fallback) {
12437
12642
  return typeof message === "string" ? message : fallback;
12438
12643
  }
12439
12644
  async function allocatePort(host) {
12440
- return new Promise((resolve13, reject) => {
12645
+ return new Promise((resolve14, reject) => {
12441
12646
  const server = createNetServer();
12442
12647
  server.on("error", reject);
12443
12648
  server.listen(0, host, () => {
@@ -12447,14 +12652,14 @@ async function allocatePort(host) {
12447
12652
  return;
12448
12653
  }
12449
12654
  const port = address.port;
12450
- server.close(() => resolve13(port));
12655
+ server.close(() => resolve14(port));
12451
12656
  });
12452
12657
  });
12453
12658
  }
12454
12659
  var ManagedModelServer = class {
12455
12660
  constructor(options) {
12456
12661
  this.options = options;
12457
- this.spawnModelServer = options.spawnModelServer ?? spawn2;
12662
+ this.spawnModelServer = options.spawnModelServer ?? spawn3;
12458
12663
  this.idleTimeoutMs = parsePositiveInteger(options.idleTimeoutSeconds, 300) * 1e3;
12459
12664
  this.restartAfterRequests = parsePositiveInteger(options.restartAfterRequests, 100);
12460
12665
  this.logRecordOverride = options.logRecord;
@@ -12488,11 +12693,11 @@ var ManagedModelServer = class {
12488
12693
  sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
12489
12694
  });
12490
12695
  });
12491
- await new Promise((resolve13, reject) => {
12696
+ await new Promise((resolve14, reject) => {
12492
12697
  this.server?.once("error", reject);
12493
12698
  this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
12494
12699
  this.server?.off("error", reject);
12495
- resolve13();
12700
+ resolve14();
12496
12701
  });
12497
12702
  });
12498
12703
  }
@@ -12502,15 +12707,15 @@ var ManagedModelServer = class {
12502
12707
  if (!this.server) return;
12503
12708
  const server = this.server;
12504
12709
  this.server = null;
12505
- await new Promise((resolve13, reject) => {
12506
- server.close((err) => err ? reject(err) : resolve13());
12710
+ await new Promise((resolve14, reject) => {
12711
+ server.close((err) => err ? reject(err) : resolve14());
12507
12712
  });
12508
12713
  }
12509
12714
  async run() {
12510
12715
  await this.start();
12511
- await new Promise((resolve13, reject) => {
12716
+ await new Promise((resolve14, reject) => {
12512
12717
  this.server?.once("error", reject);
12513
- this.server?.once("close", resolve13);
12718
+ this.server?.once("close", resolve14);
12514
12719
  });
12515
12720
  }
12516
12721
  async handle(req, res) {
@@ -12591,7 +12796,7 @@ var ManagedModelServer = class {
12591
12796
  });
12592
12797
  }
12593
12798
  async processGeneration(route, body, res, receivedAt) {
12594
- const requestId = `ttreq-${randomUUID10()}`;
12799
+ const requestId = `ttreq-${randomUUID11()}`;
12595
12800
  const startedAt = Date.now();
12596
12801
  const queuedMs = startedAt - receivedAt;
12597
12802
  this.activeRequests += 1;
@@ -12697,7 +12902,7 @@ var ManagedModelServer = class {
12697
12902
  try {
12698
12903
  child.kill("SIGTERM");
12699
12904
  await Promise.race([
12700
- new Promise((resolve13) => child.once("exit", () => resolve13())),
12905
+ new Promise((resolve14) => child.once("exit", () => resolve14())),
12701
12906
  sleep2(5e3).then(() => {
12702
12907
  if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
12703
12908
  })
@@ -13328,8 +13533,8 @@ function validateDownloadFilename(filename) {
13328
13533
  function resolveOutputPath(output, filename) {
13329
13534
  validateDownloadFilename(filename);
13330
13535
  if (!output) return filename;
13331
- if (existsSync10(output) && statSync4(output).isDirectory()) {
13332
- return join15(output, filename);
13536
+ if (existsSync10(output) && statSync5(output).isDirectory()) {
13537
+ return join17(output, filename);
13333
13538
  }
13334
13539
  return output;
13335
13540
  }
@@ -13436,15 +13641,15 @@ async function downloadUrlToFile(url, outputPath) {
13436
13641
  return contentLength;
13437
13642
  }
13438
13643
  function defaultCacheDir() {
13439
- const root = process.env.XDG_CACHE_HOME || join15(homedir3(), ".cache");
13440
- return join15(root, "tuned-tensor", "models");
13644
+ const root = process.env.XDG_CACHE_HOME || join17(homedir3(), ".cache");
13645
+ return join17(root, "tuned-tensor", "models");
13441
13646
  }
13442
13647
  function runtimeDir(cacheDir) {
13443
- return join15(cacheDir, "_runtime");
13648
+ return join17(cacheDir, "_runtime");
13444
13649
  }
13445
13650
  function runtimePythonPath(cacheDir) {
13446
13651
  const dir = runtimeDir(cacheDir);
13447
- return process.platform === "win32" ? join15(dir, "Scripts", "python.exe") : join15(dir, "bin", "python");
13652
+ return process.platform === "win32" ? join17(dir, "Scripts", "python.exe") : join17(dir, "bin", "python");
13448
13653
  }
13449
13654
  function resolveServePython(cacheDir, explicitPython) {
13450
13655
  if (explicitPython) return explicitPython;
@@ -13517,7 +13722,7 @@ print(json.dumps({"missing": missing}))
13517
13722
  }
13518
13723
  }
13519
13724
  function setupRuntimeCommands(python, venvDir, deps) {
13520
- const venvPython = process.platform === "win32" ? join15(venvDir, "Scripts", "python.exe") : join15(venvDir, "bin", "python");
13725
+ const venvPython = process.platform === "win32" ? join17(venvDir, "Scripts", "python.exe") : join17(venvDir, "bin", "python");
13521
13726
  return [
13522
13727
  [python, "-m", "venv", venvDir],
13523
13728
  [venvPython, "-m", "pip", "install", "--upgrade", "pip"],
@@ -13528,7 +13733,7 @@ function safeSegment(value) {
13528
13733
  return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "model";
13529
13734
  }
13530
13735
  function directoryHasFiles(path) {
13531
- return existsSync10(path) && statSync4(path).isDirectory() && readdirSync(path).length > 0;
13736
+ return existsSync10(path) && statSync5(path).isDirectory() && readdirSync(path).length > 0;
13532
13737
  }
13533
13738
  function validateArchiveEntryPath(entry) {
13534
13739
  const normalized = normalize(entry).replace(/^[\\/]+/, "");
@@ -13555,9 +13760,9 @@ function extractArchive(archivePath, targetDir, force) {
13555
13760
  return targetDir;
13556
13761
  }
13557
13762
  function localArchiveCacheDir(archivePath, cacheDir) {
13558
- const resolved = resolve11(archivePath);
13559
- const digest = createHash8("sha256").update(resolved).digest("hex").slice(0, 12);
13560
- return join15(cacheDir, `local-${safeSegment(basename9(archivePath))}-${digest}`);
13763
+ const resolved = resolve12(archivePath);
13764
+ const digest = createHash9("sha256").update(resolved).digest("hex").slice(0, 12);
13765
+ return join17(cacheDir, `local-${safeSegment(basename9(archivePath))}-${digest}`);
13561
13766
  }
13562
13767
  function buildSystemPrompt(spec) {
13563
13768
  const parts = [];
@@ -13573,40 +13778,40 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
13573
13778
  return parts.join("\n\n");
13574
13779
  }
13575
13780
  function findSpecPath(explicitPath, modelPath) {
13576
- const candidates = explicitPath ? [explicitPath] : [join15(process.cwd(), DEFAULT_SPEC_FILE2), join15(modelPath, DEFAULT_SPEC_FILE2)];
13781
+ const candidates = explicitPath ? [explicitPath] : [join17(process.cwd(), DEFAULT_SPEC_FILE2), join17(modelPath, DEFAULT_SPEC_FILE2)];
13577
13782
  for (const candidate of candidates) {
13578
- const resolved = resolve11(candidate);
13579
- if (existsSync10(resolved) && statSync4(resolved).isFile()) return resolved;
13783
+ const resolved = resolve12(candidate);
13784
+ if (existsSync10(resolved) && statSync5(resolved).isFile()) return resolved;
13580
13785
  }
13581
13786
  return null;
13582
13787
  }
13583
13788
  function loadSystemPromptFromSpec(specPath) {
13584
- const spec = JSON.parse(readFileSync12(specPath, "utf8"));
13789
+ const spec = JSON.parse(readFileSync13(specPath, "utf8"));
13585
13790
  return buildSystemPrompt(spec);
13586
13791
  }
13587
13792
  function loadJsonSchemaForServe(schemaPath) {
13588
- const resolved = resolve11(schemaPath);
13589
- if (!existsSync10(resolved) || !statSync4(resolved).isFile()) {
13793
+ const resolved = resolve12(schemaPath);
13794
+ if (!existsSync10(resolved) || !statSync5(resolved).isFile()) {
13590
13795
  throw new Error(`JSON schema file not found: ${schemaPath}`);
13591
13796
  }
13592
13797
  try {
13593
- const schema = JSON.parse(readFileSync12(resolved, "utf8"));
13798
+ const schema = JSON.parse(readFileSync13(resolved, "utf8"));
13594
13799
  return JSON.stringify(schema);
13595
13800
  } catch (err) {
13596
13801
  throw new Error(`JSON schema file is not valid JSON: ${schemaPath}`);
13597
13802
  }
13598
13803
  }
13599
13804
  function writeReferenceServerScript(cacheDir) {
13600
- const scriptDir = join15(cacheDir, "_server");
13805
+ const scriptDir = join17(cacheDir, "_server");
13601
13806
  mkdirSync5(scriptDir, { recursive: true });
13602
- const scriptPath = join15(scriptDir, "openai_reference_server.py");
13807
+ const scriptPath = join17(scriptDir, "openai_reference_server.py");
13603
13808
  writeFileSync6(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
13604
13809
  return scriptPath;
13605
13810
  }
13606
13811
  async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
13607
13812
  if (existsSync10(target)) {
13608
- const resolved = resolve11(target);
13609
- const stats = statSync4(resolved);
13813
+ const resolved = resolve12(target);
13814
+ const stats = statSync5(resolved);
13610
13815
  if (stats.isDirectory()) {
13611
13816
  return {
13612
13817
  modelPath: resolved,
@@ -13615,7 +13820,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
13615
13820
  };
13616
13821
  }
13617
13822
  if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
13618
- const extractDir2 = join15(localArchiveCacheDir(resolved, cacheDir), "model");
13823
+ const extractDir2 = join17(localArchiveCacheDir(resolved, cacheDir), "model");
13619
13824
  return {
13620
13825
  modelPath: extractArchive(resolved, extractDir2, forceDownload),
13621
13826
  modelName: basename9(resolved).replace(/\.t(ar\.)?gz$/, ""),
@@ -13629,9 +13834,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
13629
13834
  const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
13630
13835
  validateDownloadFilename(fullId);
13631
13836
  validateDownloadFilename(data3.filename);
13632
- const modelCacheDir = join15(cacheDir, fullId);
13633
- const archivePath = join15(modelCacheDir, data3.filename);
13634
- const extractDir = join15(modelCacheDir, "model");
13837
+ const modelCacheDir = join17(cacheDir, fullId);
13838
+ const archivePath = join17(modelCacheDir, data3.filename);
13839
+ const extractDir = join17(modelCacheDir, "model");
13635
13840
  if (!existsSync10(archivePath) || forceDownload) {
13636
13841
  await downloadUrlToFile(data3.url, archivePath);
13637
13842
  }
@@ -13736,8 +13941,8 @@ function ollamaSlug(name) {
13736
13941
  }
13737
13942
  function firstExisting(candidates) {
13738
13943
  for (const candidate of candidates) {
13739
- if (candidate && existsSync10(candidate) && statSync4(candidate).isFile()) {
13740
- return resolve11(candidate);
13944
+ if (candidate && existsSync10(candidate) && statSync5(candidate).isFile()) {
13945
+ return resolve12(candidate);
13741
13946
  }
13742
13947
  }
13743
13948
  return null;
@@ -13747,14 +13952,14 @@ function llamaCppDir(explicit) {
13747
13952
  }
13748
13953
  function resolveConvertScript(opts, required) {
13749
13954
  if (opts.convertScript) {
13750
- const resolved = resolve11(opts.convertScript);
13955
+ const resolved = resolve12(opts.convertScript);
13751
13956
  if (required && !existsSync10(resolved)) {
13752
13957
  throw new Error(`Conversion script not found: ${opts.convertScript}`);
13753
13958
  }
13754
13959
  return resolved;
13755
13960
  }
13756
13961
  const dir = llamaCppDir(opts.llamaCpp);
13757
- const candidates = dir ? [join15(dir, "convert_hf_to_gguf.py"), join15(dir, "convert-hf-to-gguf.py")] : [];
13962
+ const candidates = dir ? [join17(dir, "convert_hf_to_gguf.py"), join17(dir, "convert-hf-to-gguf.py")] : [];
13758
13963
  const found = firstExisting(candidates);
13759
13964
  if (found) return found;
13760
13965
  if (required) {
@@ -13762,11 +13967,11 @@ function resolveConvertScript(opts, required) {
13762
13967
  "Could not find convert_hf_to_gguf.py. Pass --llama-cpp <dir> (a llama.cpp checkout) or --convert-script <path>. Get it from https://github.com/ggml-org/llama.cpp."
13763
13968
  );
13764
13969
  }
13765
- return dir ? join15(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
13970
+ return dir ? join17(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
13766
13971
  }
13767
13972
  function resolveQuantizeBin(opts, required) {
13768
13973
  if (opts.quantizeBin) {
13769
- const resolved = resolve11(opts.quantizeBin);
13974
+ const resolved = resolve12(opts.quantizeBin);
13770
13975
  if (required && !existsSync10(resolved)) {
13771
13976
  throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
13772
13977
  }
@@ -13775,10 +13980,10 @@ function resolveQuantizeBin(opts, required) {
13775
13980
  const dir = llamaCppDir(opts.llamaCpp);
13776
13981
  if (dir) {
13777
13982
  const candidates = [
13778
- join15(dir, "build", "bin", "llama-quantize"),
13779
- join15(dir, "build", "bin", "quantize"),
13780
- join15(dir, "llama-quantize"),
13781
- join15(dir, "quantize")
13983
+ join17(dir, "build", "bin", "llama-quantize"),
13984
+ join17(dir, "build", "bin", "quantize"),
13985
+ join17(dir, "llama-quantize"),
13986
+ join17(dir, "quantize")
13782
13987
  ];
13783
13988
  const found = firstExisting(candidates);
13784
13989
  if (found) return found;
@@ -13926,7 +14131,7 @@ function registerModelsCommands(parent) {
13926
14131
  }
13927
14132
  const quantPlan = planQuant(String(cmdOpts.quant));
13928
14133
  const printOnly = Boolean(cmdOpts.printCommand);
13929
- const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
14134
+ const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
13930
14135
  mkdirSync5(cacheDir, { recursive: true });
13931
14136
  const serveTarget = await resolveServeTarget(
13932
14137
  target,
@@ -13935,9 +14140,9 @@ function registerModelsCommands(parent) {
13935
14140
  Boolean(cmdOpts.forceDownload)
13936
14141
  );
13937
14142
  const slug = ollamaSlug(serveTarget.modelName);
13938
- const outputDir = resolve11(cmdOpts.output || join15(process.cwd(), `${slug}-gguf`));
13939
- const finalPath = join15(outputDir, `${slug}.${quantPlan.quant}.gguf`);
13940
- const intermediatePath = quantPlan.requiresQuantize ? join15(outputDir, `${slug}.f16.gguf`) : void 0;
14143
+ const outputDir = resolve12(cmdOpts.output || join17(process.cwd(), `${slug}-gguf`));
14144
+ const finalPath = join17(outputDir, `${slug}.${quantPlan.quant}.gguf`);
14145
+ const intermediatePath = quantPlan.requiresQuantize ? join17(outputDir, `${slug}.f16.gguf`) : void 0;
13941
14146
  const convertScript = resolveConvertScript(cmdOpts, !printOnly);
13942
14147
  const convertOutfile = intermediatePath ?? finalPath;
13943
14148
  const steps = [
@@ -13975,7 +14180,7 @@ function registerModelsCommands(parent) {
13975
14180
  }
13976
14181
  if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
13977
14182
  }
13978
- modelfilePath = join15(outputDir, "Modelfile");
14183
+ modelfilePath = join17(outputDir, "Modelfile");
13979
14184
  modelfileContent = buildModelfile(basename9(finalPath), systemPrompt2);
13980
14185
  const createModel = cmdOpts.ollamaCreate !== false;
13981
14186
  if (createModel) {
@@ -14062,7 +14267,7 @@ function registerModelsCommands(parent) {
14062
14267
  }
14063
14268
  });
14064
14269
  models.command("setup-runtime").description("Install an isolated Python runtime for local model serving").option("--python <path>", "Python 3.10-3.12 executable to create the runtime").option("--cache-dir <path>", "Cache directory for the managed runtime").option("-f, --force", "Recreate the runtime if it already exists").option("--print-command", "Print the setup commands without running them").action(async (cmdOpts) => {
14065
- const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
14270
+ const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
14066
14271
  const venvDir = runtimeDir(cacheDir);
14067
14272
  const venvPython = runtimePythonPath(cacheDir);
14068
14273
  const python = pickRuntimePython(cmdOpts.python);
@@ -14108,7 +14313,7 @@ function registerModelsCommands(parent) {
14108
14313
  });
14109
14314
  models.command("serve").description("Serve a downloaded model with an OpenAI-compatible local API").argument("<target>", "Model ID/prefix, model directory, or .tar.gz artifact").option("--spec <path>", "Behavior spec JSON to apply as the default system prompt").option("--no-spec-prompt", "Do not auto-apply a behavior spec system prompt").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "8000").option("--python <path>", "Python executable").option("--cache-dir <path>", "Cache directory for downloaded/extracted models").option("--device <device>", "Inference device: auto, cpu, cuda, or mps", "auto").option("--force-download", "Re-download and re-extract model artifacts").option("--max-tokens <n>", "Default max completion tokens", "512").option("--temperature <n>", "Default sampling temperature", "0.7").option("--json-schema <path>", "JSON Schema file to enforce by default for chat completions").option("--json-repair-attempts <n>", "Number of JSON/schema repair retries", "1").option("--trust-remote-code", "Pass trust_remote_code=True to transformers").option("--managed", "Run a local lifecycle manager in front of the model server").option("--idle-timeout <seconds>", "Managed mode idle timeout before stopping the model", "300").option("--restart-after-requests <n>", "Managed mode restart threshold; 0 disables request-count restarts", "100").option("--gate-field <field>", "Response JSON field to log as the managed serving gate result", "should_process").option("--log-file <path>", "Write managed serving JSONL request logs to a file").option("--print-command", "Print the underlying Python command without starting it").action(async (target, cmdOpts) => {
14110
14315
  const opts = parent.optsWithGlobals();
14111
- const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
14316
+ const cacheDir = resolve12(cmdOpts.cacheDir || defaultCacheDir());
14112
14317
  mkdirSync5(cacheDir, { recursive: true });
14113
14318
  const python = resolveServePython(cacheDir, cmdOpts.python);
14114
14319
  if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
@@ -14157,7 +14362,7 @@ function registerModelsCommands(parent) {
14157
14362
  idle_timeout_seconds: idleTimeoutSeconds,
14158
14363
  restart_after_requests: restartAfterRequests,
14159
14364
  gate_field: String(cmdOpts.gateField),
14160
- log_file: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
14365
+ log_file: cmdOpts.logFile ? resolve12(cmdOpts.logFile) : void 0
14161
14366
  } : void 0;
14162
14367
  if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
14163
14368
  if (!isJsonMode()) {
@@ -14194,12 +14399,12 @@ function registerModelsCommands(parent) {
14194
14399
  idleTimeoutSeconds,
14195
14400
  restartAfterRequests,
14196
14401
  gateField: String(cmdOpts.gateField),
14197
- logFile: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
14402
+ logFile: cmdOpts.logFile ? resolve12(cmdOpts.logFile) : void 0
14198
14403
  });
14199
14404
  return;
14200
14405
  }
14201
14406
  await new Promise((resolvePromise, reject) => {
14202
- const child = spawn3(python, args2, {
14407
+ const child = spawn4(python, args2, {
14203
14408
  stdio: "inherit",
14204
14409
  env: { ...process.env, ...env }
14205
14410
  });
@@ -14220,15 +14425,15 @@ function registerModelsCommands(parent) {
14220
14425
  }
14221
14426
 
14222
14427
  // src/commands/push.ts
14223
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
14224
- import { resolve as resolve12 } from "path";
14428
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
14429
+ import { resolve as resolve13 } from "path";
14225
14430
  function persistRemoteId(filePath, id) {
14226
- const raw = JSON.parse(readFileSync13(filePath, "utf-8"));
14431
+ const raw = JSON.parse(readFileSync14(filePath, "utf-8"));
14227
14432
  raw.id = id;
14228
14433
  writeFileSync7(filePath, JSON.stringify(raw, null, 2) + "\n");
14229
14434
  }
14230
14435
  async function pushLocalSpec(file, opts) {
14231
- const filePath = resolve12(file);
14436
+ const filePath = resolve13(file);
14232
14437
  const spec = loadSpec(file);
14233
14438
  const evalCaseErrors = validateEvalCases(spec);
14234
14439
  if (evalCaseErrors.length > 0) {
@@ -14287,6 +14492,7 @@ function registerCloudCommands(parent) {
14287
14492
  registerLabelCommands(cloud);
14288
14493
  registerRunsCommands(cloud);
14289
14494
  registerModelsCommands(cloud);
14495
+ registerEndpointCommands(cloud);
14290
14496
  }
14291
14497
  function weeklyRows(data3) {
14292
14498
  if (typeof data3.weekly_remaining_percent !== "number" || !Number.isFinite(data3.weekly_remaining_percent) || typeof data3.weekly_cost_limit_usd !== "number" || !Number.isFinite(data3.weekly_cost_limit_usd) || data3.weekly_cost_limit_usd <= 0) return [];
@@ -14325,15 +14531,15 @@ Use \`tt balance\` for cloud training credits.`);
14325
14531
  }
14326
14532
 
14327
14533
  // src/commands/upgrade.ts
14328
- import { spawn as spawn5 } from "child_process";
14329
- import { accessSync, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync15, realpathSync } from "fs";
14330
- import { dirname as dirname15, join as join17 } from "path";
14534
+ import { spawn as spawn6 } from "child_process";
14535
+ import { accessSync, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync16, realpathSync } from "fs";
14536
+ import { dirname as dirname15, join as join19, win32 } from "path";
14331
14537
  import chalk14 from "chalk";
14332
14538
 
14333
14539
  // src/update-check.ts
14334
- import { spawn as spawn4 } from "child_process";
14335
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
14336
- import { dirname as dirname14, join as join16 } from "path";
14540
+ import { spawn as spawn5 } from "child_process";
14541
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
14542
+ import { dirname as dirname14, join as join18 } from "path";
14337
14543
  import chalk13 from "chalk";
14338
14544
  var CLI_PACKAGE_NAME = "@tuned-tensor/cli";
14339
14545
  var PACKAGE_LATEST_URL = "https://registry.npmjs.org/@tuned-tensor%2fcli/latest";
@@ -14382,14 +14588,14 @@ function compareVersions(left, right) {
14382
14588
  return comparePrerelease(left.prerelease, right.prerelease);
14383
14589
  }
14384
14590
  function getCliUpdateCacheFile(env = process.env) {
14385
- return join16(getTunedTensorHome(env), "update-check.json");
14591
+ return join18(getTunedTensorHome(env), "update-check.json");
14386
14592
  }
14387
14593
  function cliUpdateChecksDisabled(env = process.env) {
14388
14594
  return [env.TT_NO_UPDATE_CHECK, env.NO_UPDATE_NOTIFIER, env.CI].some((value) => value !== void 0 && value !== "" && value !== "0" && value !== "false");
14389
14595
  }
14390
14596
  function readUpdateCache(file) {
14391
14597
  try {
14392
- const payload = JSON.parse(readFileSync14(file, "utf8"));
14598
+ const payload = JSON.parse(readFileSync15(file, "utf8"));
14393
14599
  if (typeof payload.checkedAt !== "number" || typeof payload.latestVersion !== "string" || !parseVersion(payload.latestVersion)) return null;
14394
14600
  return { checkedAt: payload.checkedAt, latestVersion: payload.latestVersion };
14395
14601
  } catch {
@@ -14441,10 +14647,10 @@ async function fetchLatestCliVersion(options = {}) {
14441
14647
  return null;
14442
14648
  }
14443
14649
  })();
14444
- const deadline = new Promise((resolve13) => {
14650
+ const deadline = new Promise((resolve14) => {
14445
14651
  timeout = setTimeout(() => {
14446
14652
  controller.abort();
14447
- resolve13(null);
14653
+ resolve14(null);
14448
14654
  }, timeoutMs);
14449
14655
  });
14450
14656
  try {
@@ -14484,7 +14690,7 @@ function spawnCliUpdateRefresh(cacheFile) {
14484
14690
  const cached = readUpdateCache(cacheFile);
14485
14691
  if (cached) recordLatestCliVersion(cacheFile, cached.latestVersion);
14486
14692
  try {
14487
- const child = spawn4(
14693
+ const child = spawn5(
14488
14694
  process.execPath,
14489
14695
  ["-e", REFRESH_SCRIPT, PACKAGE_LATEST_URL, cacheFile],
14490
14696
  { detached: true, stdio: "ignore", windowsHide: true }
@@ -14526,10 +14732,11 @@ function resolveGlobalInstall(entrypoint, platform = process.platform) {
14526
14732
  if (/[\\/]_npx[\\/]/.test(script)) return null;
14527
14733
  const match = platform === "win32" ? script.match(/^(.*)\\node_modules\\@tuned-tensor\\cli\\dist\\index\.js$/i) : script.match(/^(.*)\/lib\/node_modules\/@tuned-tensor\/cli\/dist\/index\.js$/);
14528
14734
  if (!match?.[1]) return null;
14529
- return { prefix: match[1], packageDir: dirname15(dirname15(script)) };
14735
+ const pathApi = platform === "win32" ? win32 : { dirname: dirname15 };
14736
+ return { prefix: match[1], packageDir: pathApi.dirname(pathApi.dirname(script)) };
14530
14737
  }
14531
14738
  function prefixIsWritable(prefix, platform) {
14532
- const modules = platform === "win32" ? join17(prefix, "node_modules") : join17(prefix, "lib", "node_modules");
14739
+ const modules = platform === "win32" ? join19(prefix, "node_modules") : join19(prefix, "lib", "node_modules");
14533
14740
  try {
14534
14741
  accessSync(existsSync11(modules) ? modules : prefix, constants2.W_OK);
14535
14742
  return true;
@@ -14539,32 +14746,50 @@ function prefixIsWritable(prefix, platform) {
14539
14746
  }
14540
14747
  function installedVersion(packageDir) {
14541
14748
  try {
14542
- const manifest = JSON.parse(readFileSync15(join17(packageDir, "package.json"), "utf8"));
14749
+ const manifest = JSON.parse(readFileSync16(join19(packageDir, "package.json"), "utf8"));
14543
14750
  return typeof manifest.version === "string" ? manifest.version : void 0;
14544
14751
  } catch {
14545
14752
  return void 0;
14546
14753
  }
14547
14754
  }
14548
- function quoteForCmd(arg) {
14549
- return /[\s"&|<>^]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg;
14755
+ function resolveWindowsNpmCli(env, execPath) {
14756
+ const path = Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
14757
+ const directories = [win32.dirname(execPath), ...path.split(";")];
14758
+ for (const directory of directories) {
14759
+ if (!directory) continue;
14760
+ const candidate = win32.join(directory.replace(/^"|"$/g, ""), "node_modules", "npm", "bin", "npm-cli.js");
14761
+ if (existsSync11(candidate)) return candidate;
14762
+ }
14763
+ throw new Error("Could not locate npm-cli.js beside Node.js or on PATH. Reinstall Node.js with npm, then retry.");
14550
14764
  }
14551
- function defaultNpmRunner(platform) {
14552
- return async (args2) => await new Promise((resolve13, reject) => {
14765
+ function isWindowsInstaller(prefix) {
14766
+ try {
14767
+ return JSON.parse(readFileSync16(join19(prefix, "tt-install.json"), "utf8")).manager === "windows-installer";
14768
+ } catch {
14769
+ return false;
14770
+ }
14771
+ }
14772
+ function defaultNpmRunner(platform, env) {
14773
+ return async (args2) => await new Promise((resolve14, reject) => {
14553
14774
  const windows = platform === "win32";
14554
- const child = spawn5(
14555
- windows ? "npm.cmd" : "npm",
14556
- windows ? args2.map(quoteForCmd) : args2,
14775
+ const child = spawn6(
14776
+ windows ? process.execPath : "npm",
14777
+ windows ? [resolveWindowsNpmCli(env, process.execPath), ...args2] : args2,
14557
14778
  {
14558
- shell: windows,
14779
+ env,
14780
+ shell: false,
14559
14781
  // Keep stdout clean for --json; npm progress goes to stderr.
14560
14782
  stdio: ["ignore", isJsonMode() ? 2 : "inherit", "inherit"]
14561
14783
  }
14562
14784
  );
14563
14785
  child.once("error", reject);
14564
- child.once("close", (code2) => resolve13(code2 ?? 1));
14786
+ child.once("close", (code2) => resolve14(code2 ?? 1));
14565
14787
  });
14566
14788
  }
14567
- function formatCommand(args2) {
14789
+ function formatCommand(args2, platform) {
14790
+ if (platform === "win32") {
14791
+ return ["npm.cmd", ...args2].map((arg) => /^[a-zA-Z0-9_@./:\\+=-]+$/.test(arg) ? arg : "'" + arg.replaceAll("'", "''") + "'").join(" ");
14792
+ }
14568
14793
  return ["npm", ...args2].map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(" ");
14569
14794
  }
14570
14795
  function registerUpgradeCommand(program, deps) {
@@ -14584,6 +14809,15 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
14584
14809
  const cacheFile = getCliUpdateCacheFile(deps.env);
14585
14810
  const current = deps.version;
14586
14811
  const target = requested?.trim() || "latest";
14812
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]*$/.test(target)) {
14813
+ throw new Error("Choose an exact version or npm dist-tag, such as 0.27.0, latest, or beta.");
14814
+ }
14815
+ const install = resolveGlobalInstall(deps.entrypoint, platform);
14816
+ if (!options.check && install && isWindowsInstaller(install.prefix)) {
14817
+ throw new Error(
14818
+ "This tt is managed by the Windows installer. Upgrade with:\n winget upgrade --id TunedTensor.CLI --exact --source winget\nOr download and run the newer Windows setup from https://github.com/tunedtensor/tuned-tensor-cli/releases.\nWinGet requires the release to be available in its catalog; use setup for prereleases."
14819
+ );
14820
+ }
14587
14821
  let latest;
14588
14822
  if (target === "latest") {
14589
14823
  latest = await fetchLatestCliVersion({ fetchImpl: deps.fetchImpl, timeoutMs: 1e4 }) ?? void 0;
@@ -14614,7 +14848,6 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
14614
14848
  }
14615
14849
  return;
14616
14850
  }
14617
- const install = resolveGlobalInstall(deps.entrypoint, platform);
14618
14851
  const packageSpec = `${CLI_PACKAGE_NAME}@${target}`;
14619
14852
  if (!install) {
14620
14853
  throw new Error(
@@ -14626,26 +14859,27 @@ Upgrade it the way you installed it, for example:
14626
14859
  const npmArgs = ["install", "-g", "--ignore-scripts", "--prefix", install.prefix, packageSpec];
14627
14860
  if (!prefixIsWritable(install.prefix, platform)) {
14628
14861
  throw new Error(
14629
- `tt is installed in ${install.prefix}, which this user can't write to. Run:
14630
- sudo ${formatCommand(npmArgs)}`
14862
+ `tt is installed in ${install.prefix}, which this user can't write to. ` + (platform === "win32" ? `Use an account with access to that directory and run:
14863
+ ${formatCommand(npmArgs, platform)}` : `Run:
14864
+ sudo ${formatCommand(npmArgs, platform)}`)
14631
14865
  );
14632
14866
  }
14633
14867
  if (!isJsonMode()) {
14634
14868
  console.log(`Upgrading tt ${current} \u2192 ${chalk14.bold(latest ?? target)}`);
14635
- console.log(chalk14.dim(`$ ${formatCommand(npmArgs)}`));
14869
+ console.log(chalk14.dim(`$ ${formatCommand(npmArgs, platform)}`));
14636
14870
  }
14637
14871
  let code2;
14638
14872
  try {
14639
- code2 = await (deps.runNpm ?? defaultNpmRunner(platform))(npmArgs);
14873
+ code2 = await (deps.runNpm ?? defaultNpmRunner(platform, deps.env))(npmArgs);
14640
14874
  } catch (error) {
14641
14875
  throw new Error(
14642
14876
  `Could not run npm (${error instanceof Error ? error.message : String(error)}). Run it yourself:
14643
- ${formatCommand(npmArgs)}`
14877
+ ${formatCommand(npmArgs, platform)}`
14644
14878
  );
14645
14879
  }
14646
14880
  if (code2 !== 0) {
14647
14881
  throw new Error(`npm exited with code ${code2}. Run it yourself to see details:
14648
- ${formatCommand(npmArgs)}`);
14882
+ ${formatCommand(npmArgs, platform)}`);
14649
14883
  }
14650
14884
  const installed = installedVersion(install.packageDir) ?? latest ?? target;
14651
14885
  if (isJsonMode()) {
@@ -14802,8 +15036,8 @@ async function runSelfCommand(args2, options = {}) {
14802
15036
  if (!entrypoint) {
14803
15037
  throw new Error("Cannot locate the tt CLI entrypoint.");
14804
15038
  }
14805
- return await new Promise((resolve13, reject) => {
14806
- const child = spawn6(process.execPath, [entrypoint, ...options.captureJson ? ["--json"] : [], ...args2], {
15039
+ return await new Promise((resolve14, reject) => {
15040
+ const child = spawn7(process.execPath, [entrypoint, ...options.captureJson ? ["--json"] : [], ...args2], {
14807
15041
  cwd: options.cwd,
14808
15042
  env: options.env ?? process.env,
14809
15043
  stdio: options.captureJson ? ["inherit", "pipe", "inherit"] : "inherit",
@@ -14849,7 +15083,7 @@ async function runSelfCommand(args2, options = {}) {
14849
15083
  child.once("close", (exitCode, signal) => {
14850
15084
  cleanup();
14851
15085
  const signalNumber = signal ? osConstants2.signals[signal] : void 0;
14852
- resolve13({
15086
+ resolve14({
14853
15087
  exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
14854
15088
  signal,
14855
15089
  ...options.captureJson ? { stdout: stdout3 } : {}
@@ -15084,7 +15318,7 @@ ${formatCliUpdateNotice(update)}
15084
15318
  }
15085
15319
 
15086
15320
  // src/index.ts
15087
- runCli("0.26.2").catch((err) => {
15321
+ runCli("0.28.0").catch((err) => {
15088
15322
  reportError(err);
15089
15323
  process.exit(1);
15090
15324
  });