@tuned-tensor/cli 0.27.0 → 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 {
@@ -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,28 +1127,34 @@ 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
  });
1157
+ windowsSignals = windowsChildSignals(child);
1120
1158
  const closed = new Promise((resolve14) => {
1121
1159
  child.once("error", (error) => {
1122
1160
  state.spawnError = error;
@@ -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();
@@ -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();
@@ -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.
@@ -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) {
@@ -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.");
@@ -12277,11 +12347,11 @@ import { createHash as createHash8, randomUUID as randomUUID10 } from "crypto";
12277
12347
  import { createReadStream as createReadStream2, readFileSync as readFileSync11, statSync as statSync4, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
12278
12348
  import { open as open4 } from "fs/promises";
12279
12349
  import { finished } from "stream/promises";
12280
- import { resolve as resolve10, join as join15 } from "path";
12350
+ import { resolve as resolve10, join as join16 } from "path";
12281
12351
  import { z } from "zod";
12282
12352
  async function inspectCheckpoint(modelPath) {
12283
12353
  const directory = resolve10(modelPath);
12284
- const config = JSON.parse(readFileSync11(join15(directory, "rl_agent_config.json"), "utf8"));
12354
+ const config = JSON.parse(readFileSync11(join16(directory, "rl_agent_config.json"), "utf8"));
12285
12355
  if (config.encoder !== "answerdotai/ModernBERT-large" || config.head_layers !== 2 || config.max_len !== 512 || config.head_max_len !== 192) {
12286
12356
  throw new Error("Hosting currently supports only the pinned English Laya checkpoint and TT decision fine-tunes.");
12287
12357
  }
@@ -12289,7 +12359,7 @@ async function inspectCheckpoint(modelPath) {
12289
12359
  if (kind === "tuned" && (JSON.stringify(config.temperature) !== "[1,1,1]" || config.temperature_by_options)) {
12290
12360
  throw new Error("Custom calibration is not supported by hosted decision inference.");
12291
12361
  }
12292
- const file = join15(directory, "model.safetensors");
12362
+ const file = join16(directory, "model.safetensors");
12293
12363
  const size = statSync4(file).size;
12294
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.");
12295
12365
  const fd = openSync3(file, "r");
@@ -12408,7 +12478,7 @@ function registerEndpointCommands(parent) {
12408
12478
  }
12409
12479
 
12410
12480
  // src/commands/models.ts
12411
- import { spawn as spawn3, execFileSync } from "child_process";
12481
+ import { spawn as spawn4, execFileSync } from "child_process";
12412
12482
  import { createHash as createHash9 } from "crypto";
12413
12483
  import {
12414
12484
  existsSync as existsSync10,
@@ -12422,7 +12492,7 @@ import {
12422
12492
  rmSync
12423
12493
  } from "fs";
12424
12494
  import { homedir as homedir3 } from "os";
12425
- import { dirname as dirname13, join as join16, resolve as resolve12, 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";
12426
12496
  import { Readable, Transform } from "stream";
12427
12497
  import { pipeline } from "stream/promises";
12428
12498
 
@@ -12444,7 +12514,7 @@ Run \`tt init\` to create one.`
12444
12514
  }
12445
12515
 
12446
12516
  // src/serve-manager.ts
12447
- import { spawn as spawn2 } from "child_process";
12517
+ import { spawn as spawn3 } from "child_process";
12448
12518
  import { randomUUID as randomUUID11 } from "crypto";
12449
12519
  import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
12450
12520
  import { createServer } from "http";
@@ -12589,7 +12659,7 @@ async function allocatePort(host) {
12589
12659
  var ManagedModelServer = class {
12590
12660
  constructor(options) {
12591
12661
  this.options = options;
12592
- this.spawnModelServer = options.spawnModelServer ?? spawn2;
12662
+ this.spawnModelServer = options.spawnModelServer ?? spawn3;
12593
12663
  this.idleTimeoutMs = parsePositiveInteger(options.idleTimeoutSeconds, 300) * 1e3;
12594
12664
  this.restartAfterRequests = parsePositiveInteger(options.restartAfterRequests, 100);
12595
12665
  this.logRecordOverride = options.logRecord;
@@ -13464,7 +13534,7 @@ function resolveOutputPath(output, filename) {
13464
13534
  validateDownloadFilename(filename);
13465
13535
  if (!output) return filename;
13466
13536
  if (existsSync10(output) && statSync5(output).isDirectory()) {
13467
- return join16(output, filename);
13537
+ return join17(output, filename);
13468
13538
  }
13469
13539
  return output;
13470
13540
  }
@@ -13571,15 +13641,15 @@ async function downloadUrlToFile(url, outputPath) {
13571
13641
  return contentLength;
13572
13642
  }
13573
13643
  function defaultCacheDir() {
13574
- const root = process.env.XDG_CACHE_HOME || join16(homedir3(), ".cache");
13575
- return join16(root, "tuned-tensor", "models");
13644
+ const root = process.env.XDG_CACHE_HOME || join17(homedir3(), ".cache");
13645
+ return join17(root, "tuned-tensor", "models");
13576
13646
  }
13577
13647
  function runtimeDir(cacheDir) {
13578
- return join16(cacheDir, "_runtime");
13648
+ return join17(cacheDir, "_runtime");
13579
13649
  }
13580
13650
  function runtimePythonPath(cacheDir) {
13581
13651
  const dir = runtimeDir(cacheDir);
13582
- return process.platform === "win32" ? join16(dir, "Scripts", "python.exe") : join16(dir, "bin", "python");
13652
+ return process.platform === "win32" ? join17(dir, "Scripts", "python.exe") : join17(dir, "bin", "python");
13583
13653
  }
13584
13654
  function resolveServePython(cacheDir, explicitPython) {
13585
13655
  if (explicitPython) return explicitPython;
@@ -13652,7 +13722,7 @@ print(json.dumps({"missing": missing}))
13652
13722
  }
13653
13723
  }
13654
13724
  function setupRuntimeCommands(python, venvDir, deps) {
13655
- const venvPython = process.platform === "win32" ? join16(venvDir, "Scripts", "python.exe") : join16(venvDir, "bin", "python");
13725
+ const venvPython = process.platform === "win32" ? join17(venvDir, "Scripts", "python.exe") : join17(venvDir, "bin", "python");
13656
13726
  return [
13657
13727
  [python, "-m", "venv", venvDir],
13658
13728
  [venvPython, "-m", "pip", "install", "--upgrade", "pip"],
@@ -13692,7 +13762,7 @@ function extractArchive(archivePath, targetDir, force) {
13692
13762
  function localArchiveCacheDir(archivePath, cacheDir) {
13693
13763
  const resolved = resolve12(archivePath);
13694
13764
  const digest = createHash9("sha256").update(resolved).digest("hex").slice(0, 12);
13695
- return join16(cacheDir, `local-${safeSegment(basename9(archivePath))}-${digest}`);
13765
+ return join17(cacheDir, `local-${safeSegment(basename9(archivePath))}-${digest}`);
13696
13766
  }
13697
13767
  function buildSystemPrompt(spec) {
13698
13768
  const parts = [];
@@ -13708,7 +13778,7 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
13708
13778
  return parts.join("\n\n");
13709
13779
  }
13710
13780
  function findSpecPath(explicitPath, modelPath) {
13711
- const candidates = explicitPath ? [explicitPath] : [join16(process.cwd(), DEFAULT_SPEC_FILE2), join16(modelPath, DEFAULT_SPEC_FILE2)];
13781
+ const candidates = explicitPath ? [explicitPath] : [join17(process.cwd(), DEFAULT_SPEC_FILE2), join17(modelPath, DEFAULT_SPEC_FILE2)];
13712
13782
  for (const candidate of candidates) {
13713
13783
  const resolved = resolve12(candidate);
13714
13784
  if (existsSync10(resolved) && statSync5(resolved).isFile()) return resolved;
@@ -13732,9 +13802,9 @@ function loadJsonSchemaForServe(schemaPath) {
13732
13802
  }
13733
13803
  }
13734
13804
  function writeReferenceServerScript(cacheDir) {
13735
- const scriptDir = join16(cacheDir, "_server");
13805
+ const scriptDir = join17(cacheDir, "_server");
13736
13806
  mkdirSync5(scriptDir, { recursive: true });
13737
- const scriptPath = join16(scriptDir, "openai_reference_server.py");
13807
+ const scriptPath = join17(scriptDir, "openai_reference_server.py");
13738
13808
  writeFileSync6(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
13739
13809
  return scriptPath;
13740
13810
  }
@@ -13750,7 +13820,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
13750
13820
  };
13751
13821
  }
13752
13822
  if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
13753
- const extractDir2 = join16(localArchiveCacheDir(resolved, cacheDir), "model");
13823
+ const extractDir2 = join17(localArchiveCacheDir(resolved, cacheDir), "model");
13754
13824
  return {
13755
13825
  modelPath: extractArchive(resolved, extractDir2, forceDownload),
13756
13826
  modelName: basename9(resolved).replace(/\.t(ar\.)?gz$/, ""),
@@ -13764,9 +13834,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
13764
13834
  const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
13765
13835
  validateDownloadFilename(fullId);
13766
13836
  validateDownloadFilename(data3.filename);
13767
- const modelCacheDir = join16(cacheDir, fullId);
13768
- const archivePath = join16(modelCacheDir, data3.filename);
13769
- const extractDir = join16(modelCacheDir, "model");
13837
+ const modelCacheDir = join17(cacheDir, fullId);
13838
+ const archivePath = join17(modelCacheDir, data3.filename);
13839
+ const extractDir = join17(modelCacheDir, "model");
13770
13840
  if (!existsSync10(archivePath) || forceDownload) {
13771
13841
  await downloadUrlToFile(data3.url, archivePath);
13772
13842
  }
@@ -13889,7 +13959,7 @@ function resolveConvertScript(opts, required) {
13889
13959
  return resolved;
13890
13960
  }
13891
13961
  const dir = llamaCppDir(opts.llamaCpp);
13892
- const candidates = dir ? [join16(dir, "convert_hf_to_gguf.py"), join16(dir, "convert-hf-to-gguf.py")] : [];
13962
+ const candidates = dir ? [join17(dir, "convert_hf_to_gguf.py"), join17(dir, "convert-hf-to-gguf.py")] : [];
13893
13963
  const found = firstExisting(candidates);
13894
13964
  if (found) return found;
13895
13965
  if (required) {
@@ -13897,7 +13967,7 @@ function resolveConvertScript(opts, required) {
13897
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."
13898
13968
  );
13899
13969
  }
13900
- return dir ? join16(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";
13901
13971
  }
13902
13972
  function resolveQuantizeBin(opts, required) {
13903
13973
  if (opts.quantizeBin) {
@@ -13910,10 +13980,10 @@ function resolveQuantizeBin(opts, required) {
13910
13980
  const dir = llamaCppDir(opts.llamaCpp);
13911
13981
  if (dir) {
13912
13982
  const candidates = [
13913
- join16(dir, "build", "bin", "llama-quantize"),
13914
- join16(dir, "build", "bin", "quantize"),
13915
- join16(dir, "llama-quantize"),
13916
- join16(dir, "quantize")
13983
+ join17(dir, "build", "bin", "llama-quantize"),
13984
+ join17(dir, "build", "bin", "quantize"),
13985
+ join17(dir, "llama-quantize"),
13986
+ join17(dir, "quantize")
13917
13987
  ];
13918
13988
  const found = firstExisting(candidates);
13919
13989
  if (found) return found;
@@ -14070,9 +14140,9 @@ function registerModelsCommands(parent) {
14070
14140
  Boolean(cmdOpts.forceDownload)
14071
14141
  );
14072
14142
  const slug = ollamaSlug(serveTarget.modelName);
14073
- const outputDir = resolve12(cmdOpts.output || join16(process.cwd(), `${slug}-gguf`));
14074
- const finalPath = join16(outputDir, `${slug}.${quantPlan.quant}.gguf`);
14075
- const intermediatePath = quantPlan.requiresQuantize ? join16(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;
14076
14146
  const convertScript = resolveConvertScript(cmdOpts, !printOnly);
14077
14147
  const convertOutfile = intermediatePath ?? finalPath;
14078
14148
  const steps = [
@@ -14110,7 +14180,7 @@ function registerModelsCommands(parent) {
14110
14180
  }
14111
14181
  if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
14112
14182
  }
14113
- modelfilePath = join16(outputDir, "Modelfile");
14183
+ modelfilePath = join17(outputDir, "Modelfile");
14114
14184
  modelfileContent = buildModelfile(basename9(finalPath), systemPrompt2);
14115
14185
  const createModel = cmdOpts.ollamaCreate !== false;
14116
14186
  if (createModel) {
@@ -14334,7 +14404,7 @@ function registerModelsCommands(parent) {
14334
14404
  return;
14335
14405
  }
14336
14406
  await new Promise((resolvePromise, reject) => {
14337
- const child = spawn3(python, args2, {
14407
+ const child = spawn4(python, args2, {
14338
14408
  stdio: "inherit",
14339
14409
  env: { ...process.env, ...env }
14340
14410
  });
@@ -14461,15 +14531,15 @@ Use \`tt balance\` for cloud training credits.`);
14461
14531
  }
14462
14532
 
14463
14533
  // src/commands/upgrade.ts
14464
- import { spawn as spawn5 } from "child_process";
14534
+ import { spawn as spawn6 } from "child_process";
14465
14535
  import { accessSync, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync16, realpathSync } from "fs";
14466
- import { dirname as dirname15, join as join18 } from "path";
14536
+ import { dirname as dirname15, join as join19, win32 } from "path";
14467
14537
  import chalk14 from "chalk";
14468
14538
 
14469
14539
  // src/update-check.ts
14470
- import { spawn as spawn4 } from "child_process";
14540
+ import { spawn as spawn5 } from "child_process";
14471
14541
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
14472
- import { dirname as dirname14, join as join17 } from "path";
14542
+ import { dirname as dirname14, join as join18 } from "path";
14473
14543
  import chalk13 from "chalk";
14474
14544
  var CLI_PACKAGE_NAME = "@tuned-tensor/cli";
14475
14545
  var PACKAGE_LATEST_URL = "https://registry.npmjs.org/@tuned-tensor%2fcli/latest";
@@ -14518,7 +14588,7 @@ function compareVersions(left, right) {
14518
14588
  return comparePrerelease(left.prerelease, right.prerelease);
14519
14589
  }
14520
14590
  function getCliUpdateCacheFile(env = process.env) {
14521
- return join17(getTunedTensorHome(env), "update-check.json");
14591
+ return join18(getTunedTensorHome(env), "update-check.json");
14522
14592
  }
14523
14593
  function cliUpdateChecksDisabled(env = process.env) {
14524
14594
  return [env.TT_NO_UPDATE_CHECK, env.NO_UPDATE_NOTIFIER, env.CI].some((value) => value !== void 0 && value !== "" && value !== "0" && value !== "false");
@@ -14620,7 +14690,7 @@ function spawnCliUpdateRefresh(cacheFile) {
14620
14690
  const cached = readUpdateCache(cacheFile);
14621
14691
  if (cached) recordLatestCliVersion(cacheFile, cached.latestVersion);
14622
14692
  try {
14623
- const child = spawn4(
14693
+ const child = spawn5(
14624
14694
  process.execPath,
14625
14695
  ["-e", REFRESH_SCRIPT, PACKAGE_LATEST_URL, cacheFile],
14626
14696
  { detached: true, stdio: "ignore", windowsHide: true }
@@ -14662,10 +14732,11 @@ function resolveGlobalInstall(entrypoint, platform = process.platform) {
14662
14732
  if (/[\\/]_npx[\\/]/.test(script)) return null;
14663
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$/);
14664
14734
  if (!match?.[1]) return null;
14665
- 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)) };
14666
14737
  }
14667
14738
  function prefixIsWritable(prefix, platform) {
14668
- const modules = platform === "win32" ? join18(prefix, "node_modules") : join18(prefix, "lib", "node_modules");
14739
+ const modules = platform === "win32" ? join19(prefix, "node_modules") : join19(prefix, "lib", "node_modules");
14669
14740
  try {
14670
14741
  accessSync(existsSync11(modules) ? modules : prefix, constants2.W_OK);
14671
14742
  return true;
@@ -14675,23 +14746,38 @@ function prefixIsWritable(prefix, platform) {
14675
14746
  }
14676
14747
  function installedVersion(packageDir) {
14677
14748
  try {
14678
- const manifest = JSON.parse(readFileSync16(join18(packageDir, "package.json"), "utf8"));
14749
+ const manifest = JSON.parse(readFileSync16(join19(packageDir, "package.json"), "utf8"));
14679
14750
  return typeof manifest.version === "string" ? manifest.version : void 0;
14680
14751
  } catch {
14681
14752
  return void 0;
14682
14753
  }
14683
14754
  }
14684
- function quoteForCmd(arg) {
14685
- 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.");
14764
+ }
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
+ }
14686
14771
  }
14687
- function defaultNpmRunner(platform) {
14772
+ function defaultNpmRunner(platform, env) {
14688
14773
  return async (args2) => await new Promise((resolve14, reject) => {
14689
14774
  const windows = platform === "win32";
14690
- const child = spawn5(
14691
- windows ? "npm.cmd" : "npm",
14692
- windows ? args2.map(quoteForCmd) : args2,
14775
+ const child = spawn6(
14776
+ windows ? process.execPath : "npm",
14777
+ windows ? [resolveWindowsNpmCli(env, process.execPath), ...args2] : args2,
14693
14778
  {
14694
- shell: windows,
14779
+ env,
14780
+ shell: false,
14695
14781
  // Keep stdout clean for --json; npm progress goes to stderr.
14696
14782
  stdio: ["ignore", isJsonMode() ? 2 : "inherit", "inherit"]
14697
14783
  }
@@ -14700,7 +14786,10 @@ function defaultNpmRunner(platform) {
14700
14786
  child.once("close", (code2) => resolve14(code2 ?? 1));
14701
14787
  });
14702
14788
  }
14703
- 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
+ }
14704
14793
  return ["npm", ...args2].map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(" ");
14705
14794
  }
14706
14795
  function registerUpgradeCommand(program, deps) {
@@ -14720,6 +14809,15 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
14720
14809
  const cacheFile = getCliUpdateCacheFile(deps.env);
14721
14810
  const current = deps.version;
14722
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
+ }
14723
14821
  let latest;
14724
14822
  if (target === "latest") {
14725
14823
  latest = await fetchLatestCliVersion({ fetchImpl: deps.fetchImpl, timeoutMs: 1e4 }) ?? void 0;
@@ -14750,7 +14848,6 @@ Set TT_NO_UPDATE_CHECK=1 to silence update notices.
14750
14848
  }
14751
14849
  return;
14752
14850
  }
14753
- const install = resolveGlobalInstall(deps.entrypoint, platform);
14754
14851
  const packageSpec = `${CLI_PACKAGE_NAME}@${target}`;
14755
14852
  if (!install) {
14756
14853
  throw new Error(
@@ -14762,26 +14859,27 @@ Upgrade it the way you installed it, for example:
14762
14859
  const npmArgs = ["install", "-g", "--ignore-scripts", "--prefix", install.prefix, packageSpec];
14763
14860
  if (!prefixIsWritable(install.prefix, platform)) {
14764
14861
  throw new Error(
14765
- `tt is installed in ${install.prefix}, which this user can't write to. Run:
14766
- 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)}`)
14767
14865
  );
14768
14866
  }
14769
14867
  if (!isJsonMode()) {
14770
14868
  console.log(`Upgrading tt ${current} \u2192 ${chalk14.bold(latest ?? target)}`);
14771
- console.log(chalk14.dim(`$ ${formatCommand(npmArgs)}`));
14869
+ console.log(chalk14.dim(`$ ${formatCommand(npmArgs, platform)}`));
14772
14870
  }
14773
14871
  let code2;
14774
14872
  try {
14775
- code2 = await (deps.runNpm ?? defaultNpmRunner(platform))(npmArgs);
14873
+ code2 = await (deps.runNpm ?? defaultNpmRunner(platform, deps.env))(npmArgs);
14776
14874
  } catch (error) {
14777
14875
  throw new Error(
14778
14876
  `Could not run npm (${error instanceof Error ? error.message : String(error)}). Run it yourself:
14779
- ${formatCommand(npmArgs)}`
14877
+ ${formatCommand(npmArgs, platform)}`
14780
14878
  );
14781
14879
  }
14782
14880
  if (code2 !== 0) {
14783
14881
  throw new Error(`npm exited with code ${code2}. Run it yourself to see details:
14784
- ${formatCommand(npmArgs)}`);
14882
+ ${formatCommand(npmArgs, platform)}`);
14785
14883
  }
14786
14884
  const installed = installedVersion(install.packageDir) ?? latest ?? target;
14787
14885
  if (isJsonMode()) {
@@ -14939,7 +15037,7 @@ async function runSelfCommand(args2, options = {}) {
14939
15037
  throw new Error("Cannot locate the tt CLI entrypoint.");
14940
15038
  }
14941
15039
  return await new Promise((resolve14, reject) => {
14942
- const child = spawn6(process.execPath, [entrypoint, ...options.captureJson ? ["--json"] : [], ...args2], {
15040
+ const child = spawn7(process.execPath, [entrypoint, ...options.captureJson ? ["--json"] : [], ...args2], {
14943
15041
  cwd: options.cwd,
14944
15042
  env: options.env ?? process.env,
14945
15043
  stdio: options.captureJson ? ["inherit", "pipe", "inherit"] : "inherit",
@@ -15220,7 +15318,7 @@ ${formatCliUpdateNotice(update)}
15220
15318
  }
15221
15319
 
15222
15320
  // src/index.ts
15223
- runCli("0.27.0").catch((err) => {
15321
+ runCli("0.28.0").catch((err) => {
15224
15322
  reportError(err);
15225
15323
  process.exit(1);
15226
15324
  });