@tuned-tensor/cli 0.20.0 → 0.21.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
@@ -1,22 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ DECISION_MODELS,
3
4
  DEFAULT_ARTIFACT_ROOT,
5
+ DEFAULT_DECISION_RUNS_DIR,
4
6
  DEFAULT_FOUNDATION_RUNS_DIR,
5
7
  DEFAULT_PROJECT_STORE_ROOT,
6
8
  TRAINING_MODELS,
7
9
  assertFoundationSettingsMatch,
8
10
  assessHardware,
11
+ buildDecisionPythonCommand,
9
12
  buildFoundationPythonCommand,
10
13
  buildSystemMessage,
11
14
  canonicalFoundationPipeline,
12
15
  canonicalJson,
13
16
  canonicalPipeline,
17
+ compareEvalReports,
14
18
  configForSpec,
15
19
  createExecutionPlan,
16
20
  createLocalStore,
21
+ decisionLabels,
17
22
  defaultLocalHome,
18
23
  defaultStoreRoot,
24
+ deriveSampleSeed,
25
+ evalReportSchema,
26
+ evaluateCapabilities,
19
27
  expandUserPath,
28
+ fileUri,
20
29
  formatAgentHostBlock,
21
30
  formatHostStatusLine,
22
31
  getAgentConfigDir,
@@ -32,24 +41,27 @@ import {
32
41
  pipelineForRunInput,
33
42
  pipelineFromFoundationHyperparameters,
34
43
  readHardwareSnapshot,
44
+ resolveDecisionModel,
35
45
  resolveProjectConfig,
36
46
  runGpuProcess,
37
47
  runLocalPipeline,
38
48
  runLoggedProcess,
39
49
  runReportSchema,
40
50
  specHash,
51
+ splitSpecExamples,
41
52
  validateBehaviorSpec,
42
53
  validateEvalCases,
43
54
  validatePipeline,
44
55
  warningsFromSnapshot,
56
+ withDecisionPythonEnvironment,
45
57
  withFoundationPythonEnvironment
46
- } from "./chunk-4Q54NHKR.js";
58
+ } from "./chunk-PVLX7LJI.js";
47
59
 
48
60
  // src/cli.ts
49
61
  import { spawn as spawn4 } from "child_process";
50
62
  import { constants as osConstants2 } from "os";
51
63
  import { Command } from "commander";
52
- import chalk10 from "chalk";
64
+ import chalk13 from "chalk";
53
65
 
54
66
  // src/local-agent-client.ts
55
67
  import { randomUUID as randomUUID5 } from "crypto";
@@ -75,15 +87,18 @@ var FolderName = Type.String({
75
87
  description: "One new folder name directly beneath the current workspace"
76
88
  });
77
89
  var LocalBaseModel = Type.String({
78
- type: "string",
79
- enum: TRAINING_MODELS.map((model) => model.id)
90
+ minLength: 3,
91
+ maxLength: 193,
92
+ pattern: "^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$",
93
+ description: `Hugging Face repo id. Certified adapter models: ${TRAINING_MODELS.map((model) => model.id).join(", ")}. Decision models: ${DECISION_MODELS.map((model) => model.id).join(", ")}. Other adapter repos require hyperparameters.base_model_revision (40-character commit SHA).`
80
94
  });
81
95
  var NonEmptyText = Type.String({ minLength: 1, maxLength: 1e3 });
82
96
  var LocalProjectSpecSchema = Type.Object({
83
97
  name: Type.String({ minLength: 1, maxLength: 255 }),
84
98
  description: Type.Optional(Type.String({ maxLength: 5e3 })),
85
99
  base_model: Type.Optional(LocalBaseModel),
86
- engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation")])),
100
+ engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation"), Type.Literal("decision")])),
101
+ decision: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
87
102
  hyperparameters: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
88
103
  foundation: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
89
104
  dataset_prebuilt: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
@@ -335,6 +350,7 @@ var EDIT_KEYS = /* @__PURE__ */ new Set([
335
350
  "base_model",
336
351
  "hyperparameters",
337
352
  "foundation",
353
+ "decision",
338
354
  "dataset_prebuilt",
339
355
  "runtime",
340
356
  "evaluation",
@@ -774,7 +790,7 @@ async function prepareLocalPipelineAction(args2) {
774
790
  specPath: spec.displayPath,
775
791
  specSha256: createHash2("sha256").update(spec.source).digest("hex"),
776
792
  dryRun,
777
- engine: input.kind === "foundation-spec" ? "foundation" : "adapter",
793
+ engine: input.kind === "foundation-spec" ? "foundation" : input.kind === "decision-spec" ? "decision" : "adapter",
778
794
  resolvedSpec: input.spec,
779
795
  resolvedConfig,
780
796
  ...config ? {
@@ -1534,9 +1550,16 @@ function canonicalizeHostedBaseModel(model) {
1534
1550
  }
1535
1551
  return canonical;
1536
1552
  }
1553
+ var HUGGING_FACE_REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/;
1554
+ function canonicalizeLocalBaseModel(model) {
1555
+ if (typeof model === "string" && HUGGING_FACE_REPO_ID.test(model.trim())) {
1556
+ return BASE_MODEL_ALIASES.get(model.trim().toLowerCase()) ?? model.trim();
1557
+ }
1558
+ return canonicalizeBaseModel(model);
1559
+ }
1537
1560
  function canonicalizeSpecBaseModel(body) {
1538
1561
  if ("base_model" in body && body.base_model !== void 0) {
1539
- return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
1562
+ return { ...body, base_model: canonicalizeLocalBaseModel(body.base_model) };
1540
1563
  }
1541
1564
  return body;
1542
1565
  }
@@ -1958,8 +1981,8 @@ function createTunedTensorTools(api, options = {}) {
1958
1981
  });
1959
1982
  }
1960
1983
  ),
1961
- define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter or foundation workflow and exact TT commands. This never executes anything.", Type2.Object({
1962
- engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation")])),
1984
+ define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter, foundation or decision workflow and exact TT commands. A decision spec fine-tunes a small typed decision model (e.g. convaiinnovations/laya) that returns a calibrated probability per label instead of generated text; use it for classification, routing, triage and yes/no or score gates. This never executes anything.", Type2.Object({
1985
+ engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation"), Type2.Literal("decision")])),
1963
1986
  target: Type2.Optional(Type2.Union([Type2.Literal("local"), Type2.Literal("cloud")]))
1964
1987
  }, { additionalProperties: false }), async (p) => {
1965
1988
  const engine = p.engine ?? "adapter";
@@ -1980,6 +2003,24 @@ function createTunedTensorTools(api, options = {}) {
1980
2003
  },
1981
2004
  note: "Configure runtime.gpu in tunedtensor.json with provider aws, instanceId, SSH user and optional AWS profile/region/identityFile. The instance must already be running and reachable with a verified SSH host key. AWS profile lookup and SSH access are separate; no TT token is required. GPU processes use it automatically; orchestration, scoring and artifacts stay local. Hardware tools inspect only the laptop; doctor checks the configured instance. The user manages AWS quota, costs and shutdown. Keep the laptop running until outputs return. Hosted training is retired."
1982
2005
  } : void 0;
2006
+ if (engine === "decision") {
2007
+ return {
2008
+ version: 1,
2009
+ engine,
2010
+ scope: { execution: "local", devices: ["cpu", "mps", "cuda"] },
2011
+ canonical: canonicalPipeline("local"),
2012
+ models: DECISION_MODELS.map((model) => ({ id: model.id, revision: model.revision, parameters: model.parameters, license: model.license })),
2013
+ spec: 'engine "decision", base_model, decision {type: choice|noul|score, criteria}; example outputs are labels (choice keys, true/false, or score level indices). system_prompt, guidelines and constraints become the question instructions.',
2014
+ commands: {
2015
+ init: "tt init --engine decision",
2016
+ validate: "tt validate",
2017
+ dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
2018
+ run: "tt pipeline run --spec tunedtensor.json"
2019
+ },
2020
+ 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>).",
2021
+ ...host ? { host } : {}
2022
+ };
2023
+ }
1983
2024
  if (engine === "foundation") {
1984
2025
  const foundation = {
1985
2026
  version: 1,
@@ -2097,7 +2138,7 @@ function createTunedTensorTools(api, options = {}) {
2097
2138
  define(
2098
2139
  "prepare_update_local_spec",
2099
2140
  "Prepare behavior spec edit",
2100
- "Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
2141
+ "Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays, the decision question and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
2101
2142
  Type2.Object({
2102
2143
  spec_path: Type2.Optional(Type2.String({ maxLength: 1e3 })),
2103
2144
  expected_sha256: Type2.String({ pattern: "^[a-f0-9]{64}$" }),
@@ -2118,6 +2159,7 @@ function createTunedTensorTools(api, options = {}) {
2118
2159
  }, { additionalProperties: false }), { minItems: 1 })),
2119
2160
  hyperparameters: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2120
2161
  foundation: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2162
+ decision: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2121
2163
  runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2122
2164
  evaluation: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2123
2165
  pipeline: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
@@ -2246,11 +2288,13 @@ function createTunedTensorTools(api, options = {}) {
2246
2288
  // src/local-agent-client.ts
2247
2289
  var MAX_TOOL_CALLS_PER_TURN = 12;
2248
2290
  var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
2249
- 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 project. 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. Use /spec, /spec diff, /spec validate and /spec history for direct review. General educational or inspection questions do not require a spec mutation.
2291
+ 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 project. 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.
2250
2292
  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.
2251
2293
  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.
2252
- For accurate adapter and foundation workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
2294
+ For accurate adapter, foundation and decision workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
2253
2295
  When the user asks to train, fine-tune, dry-run, or execute a workflow, call \`prepare_pipeline_run\`. Omit its pipeline argument to use the saved recipe or derive its default from the workspace spec. To change workflow stages, propose an edit to the spec pipeline section first; runtime and evaluation settings also belong in the core spec. This prepares a sealed local pipeline dry-run; approved pipeline actions are dry-runs only and may be stopped with Ctrl-C. Never claim training started. Real training requires an explicit direct \`tt pipeline run --spec tunedtensor.json\` command in the shell; preview uses the same command with \`--dry-run\`. The spec path is a --spec option, not a positional argument. No pipeline file or pipeline init is required for this default spec-driven workflow.
2296
+ Adapter base models: TT certifies ${TRAINING_MODELS.map((model) => model.id).join(", ")} (reviewed revision, architecture and memory profile). For adapter training, when the user asks for a model that is not certified, do not silently substitute a certified one. Confirm the repo exists with \`search_hugging_face\`, then explain the trade-off: an uncertified model trains as a text causal LM with generic LoRA defaults, safetensors weights and no remote code, but TT has not verified its architecture, chat template or hardware fit. If the user accepts, propose setting base_model to the exact repo id and hyperparameters.base_model_revision to a 40-character commit SHA the user copies from its Hugging Face commits page; never invent a SHA. Suggest the closest certified model as the lower-risk option, and point out that models needing remote code, vision inputs or gated access the user lacks will fail.
2297
+ Decision models use engine "decision" and their own certified registry. Call \`describe_pipeline\` with engine "decision" for supported models and typed question settings; do not apply adapter revision requirements to decision specs.
2254
2298
  When the user asks to discover public models or datasets, call \`search_hugging_face\`; it searches Hugging Face metadata for foundation and fine-tuning workflows. The query is sent to huggingface.co, so never include secrets or private data.
2255
2299
  For educational questions about how or why training works, call \`inspect_training_source\` before answering. Ground the explanation in the returned code and distinguish observed behavior from inferred rationale; do not invent author intent.
2256
2300
  When the user wants to examine this host, GPU, VRAM, CUDA, or decide what this machine can train, fine-tune, or infer, call \`examine_hardware\` before recommending a base model, engine, or pipeline. For local execution, recommend only workloads marked ready (mention tight as a caution). This tool inspects only the laptop: do not use a missing local GPU to reject AWS training or infer remote capacity. For AWS, direct the user to \`tt doctor tunedtensor.json\` to check the configured instance. Never invent generic 7B/70B sizing. Never start or cancel training from the model tool loop.
@@ -2778,16 +2822,79 @@ function del(path, opts) {
2778
2822
  }
2779
2823
 
2780
2824
  // src/agent.ts
2781
- import chalk5 from "chalk";
2825
+ import chalk8 from "chalk";
2826
+
2827
+ // src/mascot.ts
2828
+ import chalk from "chalk";
2829
+ var MASCOT_NAME = "tt";
2830
+ var MASCOT_WIDTH = 11;
2831
+ var PALETTE = {
2832
+ a: "#B39DFB",
2833
+ // diagonal, top-left
2834
+ b: "#9B72F8",
2835
+ // diagonal, center
2836
+ c: "#7C3AED",
2837
+ // diagonal, bottom-right
2838
+ d: "#2B323D"
2839
+ // slate tiles
2840
+ };
2841
+ var MARK = [
2842
+ "aa.dd...",
2843
+ "aa.dd...",
2844
+ "........",
2845
+ "dd.bb.dd",
2846
+ "dd.bb.dd",
2847
+ "........",
2848
+ "...dd.cc",
2849
+ "...dd.cc"
2850
+ ];
2851
+ function pixelRow(upper, lower) {
2852
+ let line = "";
2853
+ for (let column = 0; column < upper.length; column += 1) {
2854
+ const up = upper[column];
2855
+ const down = lower[column];
2856
+ if (up === "." && down === ".") line += " ";
2857
+ else if (up === ".") line += chalk.hex(PALETTE[down])("\u2584");
2858
+ else if (down === ".") line += chalk.hex(PALETTE[up])("\u2580");
2859
+ else if (up === down) line += chalk.hex(PALETTE[up])("\u2588");
2860
+ else line += chalk.hex(PALETTE[up]).bgHex(PALETTE[down])("\u2580");
2861
+ }
2862
+ return line;
2863
+ }
2864
+ function renderMascot() {
2865
+ if (chalk.level === 0) {
2866
+ return ["", " \u25A0 \u25A1", " \u25A1 \u25A0 \u25A1", " \u25A1 \u25A0"].map((row) => row.padEnd(MASCOT_WIDTH));
2867
+ }
2868
+ const rows2 = [];
2869
+ for (let row = 0; row < MARK.length; row += 2) {
2870
+ rows2.push(` ${pixelRow(MARK[row], MARK[row + 1])} `);
2871
+ }
2872
+ return rows2;
2873
+ }
2874
+ function mascotMark() {
2875
+ if (chalk.level === 0) return "\u259A";
2876
+ return `${chalk.hex(PALETTE.a)("\u2580")}${chalk.hex(PALETTE.c)("\u2584")}`;
2877
+ }
2878
+ function mascotSays(message) {
2879
+ return `${mascotMark()} ${chalk.hex(PALETTE.b).bold(MASCOT_NAME)} ${chalk.dim("\u203A")} ${message}`;
2880
+ }
2881
+ var FAREWELLS = [
2882
+ "See you next epoch.",
2883
+ "Bye! Your checkpoints are safe with me.",
2884
+ "Run tt to pick up where you left off."
2885
+ ];
2886
+ function mascotFarewell(seed = Date.now()) {
2887
+ return mascotSays(FAREWELLS[Math.abs(seed) % FAREWELLS.length]);
2888
+ }
2782
2889
 
2783
2890
  // src/shell.ts
2784
- import chalk4 from "chalk";
2891
+ import chalk7 from "chalk";
2785
2892
 
2786
2893
  // src/terminal-markdown.ts
2787
- import chalk from "chalk";
2894
+ import chalk2 from "chalk";
2788
2895
  import { stripVTControlCharacters } from "util";
2789
- var accent = chalk.hex("#8B5CF6");
2790
- var code = chalk.hex("#A78BFA");
2896
+ var accent = chalk2.hex("#8B5CF6");
2897
+ var code = chalk2.hex("#A78BFA");
2791
2898
  function sanitizeTerminalText(text2) {
2792
2899
  return stripVTControlCharacters(text2).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
2793
2900
  }
@@ -2806,7 +2913,7 @@ function renderInlineMarkdown(text2) {
2806
2913
  if (text2[index] === "[") {
2807
2914
  const link = text2.slice(index).match(/^\[([^\]]+)]\(([^)]+)\)/);
2808
2915
  if (link) {
2809
- rendered += `${chalk.underline(renderInlineMarkdown(link[1]))}${chalk.dim(` (${link[2]})`)}`;
2916
+ rendered += `${chalk2.underline(renderInlineMarkdown(link[1]))}${chalk2.dim(` (${link[2]})`)}`;
2810
2917
  index += link[0].length;
2811
2918
  continue;
2812
2919
  }
@@ -2815,7 +2922,7 @@ function renderInlineMarkdown(text2) {
2815
2922
  if (strong) {
2816
2923
  const close = closingDelimiter(text2, strong, index);
2817
2924
  if (close > index + strong.length) {
2818
- rendered += chalk.bold(
2925
+ rendered += chalk2.bold(
2819
2926
  renderInlineMarkdown(text2.slice(index + strong.length, close))
2820
2927
  );
2821
2928
  index = close + strong.length;
@@ -2834,7 +2941,7 @@ function renderInlineMarkdown(text2) {
2834
2941
  if (emphasis) {
2835
2942
  const close = closingDelimiter(text2, emphasis, index);
2836
2943
  if (close > index + 1) {
2837
- rendered += chalk.italic(
2944
+ rendered += chalk2.italic(
2838
2945
  renderInlineMarkdown(text2.slice(index + 1, close))
2839
2946
  );
2840
2947
  index = close + 1;
@@ -2849,7 +2956,7 @@ function renderInlineMarkdown(text2) {
2849
2956
  function renderMarkdownLine(line, inCodeBlock) {
2850
2957
  if (inCodeBlock) return code(` ${line}`);
2851
2958
  const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
2852
- if (heading) return chalk.bold(renderInlineMarkdown(heading[1]));
2959
+ if (heading) return chalk2.bold(renderInlineMarkdown(heading[1]));
2853
2960
  const bullet = line.match(/^(\s*)[-+*]\s+(.+)$/);
2854
2961
  if (bullet) {
2855
2962
  return `${bullet[1]}${accent("\u2022")} ${renderInlineMarkdown(bullet[2])}`;
@@ -2859,9 +2966,9 @@ function renderMarkdownLine(line, inCodeBlock) {
2859
2966
  return `${numbered[1]}${accent(`${numbered[2]}.`)} ${renderInlineMarkdown(numbered[3])}`;
2860
2967
  }
2861
2968
  const quote = line.match(/^\s*>\s?(.*)$/);
2862
- if (quote) return `${chalk.dim("\u2502")} ${chalk.italic(renderInlineMarkdown(quote[1]))}`;
2969
+ if (quote) return `${chalk2.dim("\u2502")} ${chalk2.italic(renderInlineMarkdown(quote[1]))}`;
2863
2970
  if (/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
2864
- return chalk.dim("\u2500".repeat(32));
2971
+ return chalk2.dim("\u2500".repeat(32));
2865
2972
  }
2866
2973
  return renderInlineMarkdown(line);
2867
2974
  }
@@ -2937,9 +3044,9 @@ function wrapTerminalLine(line, width) {
2937
3044
  }
2938
3045
 
2939
3046
  // src/output.ts
2940
- import chalk2 from "chalk";
3047
+ import chalk3 from "chalk";
2941
3048
  import Table from "cli-table3";
2942
- var labelStyle = chalk2.bold.hex("#A78BFA");
3049
+ var labelStyle = chalk3.bold.hex("#A78BFA");
2943
3050
  var jsonMode = false;
2944
3051
  function setJsonMode(enabled) {
2945
3052
  jsonMode = enabled;
@@ -2962,7 +3069,7 @@ function printTable(headers, rows2, meta) {
2962
3069
  if (meta) {
2963
3070
  const totalPages = Math.ceil(meta.total / meta.per_page);
2964
3071
  console.log(
2965
- chalk2.dim(
3072
+ chalk3.dim(
2966
3073
  `
2967
3074
  Page ${meta.page}/${totalPages} (${meta.total} total)`
2968
3075
  )
@@ -2973,17 +3080,17 @@ function printDetail(fields) {
2973
3080
  const maxLabel = Math.max(...fields.map(([l]) => l.length));
2974
3081
  for (const [label, value] of fields) {
2975
3082
  const paddedLabel = label.padEnd(maxLabel);
2976
- console.log(`${labelStyle(paddedLabel)} ${value ?? chalk2.dim("\u2014")}`);
3083
+ console.log(`${labelStyle(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
2977
3084
  }
2978
3085
  }
2979
3086
  function printSuccess(message) {
2980
- console.log(chalk2.green("\u2713") + " " + message);
3087
+ console.log(chalk3.green("\u2713") + " " + message);
2981
3088
  }
2982
3089
  function printWarning(message) {
2983
- console.log(chalk2.yellow("!") + " " + message);
3090
+ console.log(chalk3.yellow("!") + " " + message);
2984
3091
  }
2985
3092
  function printError(message) {
2986
- console.error(chalk2.red("\u2717") + " " + message);
3093
+ console.error(chalk3.red("\u2717") + " " + message);
2987
3094
  }
2988
3095
  function reportError(err) {
2989
3096
  const isApi = err instanceof ApiError;
@@ -3008,19 +3115,19 @@ function formatDate(iso) {
3008
3115
  }
3009
3116
  function formatStatus(status2) {
3010
3117
  const map = {
3011
- completed: chalk2.green,
3012
- running: chalk2.hex("#A78BFA"),
3013
- training: chalk2.hex("#A78BFA"),
3014
- evaluating: chalk2.hex("#A78BFA"),
3015
- preparing: chalk2.yellow,
3016
- pending: chalk2.yellow,
3017
- uploading: chalk2.yellow,
3018
- failed: chalk2.red,
3019
- cancelled: chalk2.dim,
3020
- validated: chalk2.green,
3021
- invalid: chalk2.red
3118
+ completed: chalk3.green,
3119
+ running: chalk3.hex("#A78BFA"),
3120
+ training: chalk3.hex("#A78BFA"),
3121
+ evaluating: chalk3.hex("#A78BFA"),
3122
+ preparing: chalk3.yellow,
3123
+ pending: chalk3.yellow,
3124
+ uploading: chalk3.yellow,
3125
+ failed: chalk3.red,
3126
+ cancelled: chalk3.dim,
3127
+ validated: chalk3.green,
3128
+ invalid: chalk3.red
3022
3129
  };
3023
- const colorFn = map[status2] || chalk2.white;
3130
+ const colorFn = map[status2] || chalk3.white;
3024
3131
  return colorFn(status2);
3025
3132
  }
3026
3133
  function truncate(str, max) {
@@ -3031,6 +3138,400 @@ function shortId(id) {
3031
3138
  return id.slice(0, 8);
3032
3139
  }
3033
3140
 
3141
+ // src/spec-view.ts
3142
+ import chalk4 from "chalk";
3143
+ var accent2 = chalk4.hex("#8B5CF6");
3144
+ var soft = chalk4.hex("#A78BFA");
3145
+ var keyColor = chalk4.hex("#C4B5FD");
3146
+ var stringColor = chalk4.hex("#86EFAC");
3147
+ var numberColor = chalk4.hex("#FCD34D");
3148
+ var literalColor = chalk4.hex("#F9A8D4");
3149
+ var SPEC_SECTIONS = [
3150
+ "identity",
3151
+ "behavior",
3152
+ "examples",
3153
+ "training",
3154
+ "evaluation",
3155
+ "runtime",
3156
+ "pipeline"
3157
+ ];
3158
+ var SECTION_ALIASES = {
3159
+ id: "identity",
3160
+ about: "identity",
3161
+ prompt: "behavior",
3162
+ system: "behavior",
3163
+ guidelines: "behavior",
3164
+ constraints: "behavior",
3165
+ example: "examples",
3166
+ data: "examples",
3167
+ hyperparameters: "training",
3168
+ hparams: "training",
3169
+ foundation: "training",
3170
+ train: "training",
3171
+ eval: "evaluation",
3172
+ gpu: "runtime"
3173
+ };
3174
+ function resolveSpecSection(name) {
3175
+ const normalized = name.trim().toLowerCase();
3176
+ if (SPEC_SECTIONS.includes(normalized)) return normalized;
3177
+ return SECTION_ALIASES[normalized];
3178
+ }
3179
+ var SECTION_BLURBS = {
3180
+ identity: "what this model is",
3181
+ behavior: "what the model should do",
3182
+ examples: "teaches by demonstration",
3183
+ training: "how it is trained",
3184
+ evaluation: "how results are scored",
3185
+ runtime: "where it runs",
3186
+ pipeline: "custom step recipe"
3187
+ };
3188
+ var SECTION_KEYS = {
3189
+ identity: ["name", "description", "engine", "base_model", "id", "dataset_prebuilt"],
3190
+ behavior: ["system_prompt", "guidelines", "constraints", "decision"],
3191
+ examples: ["examples"],
3192
+ training: ["hyperparameters", "foundation"],
3193
+ evaluation: ["evaluation"],
3194
+ runtime: ["runtime"],
3195
+ pipeline: ["pipeline"]
3196
+ };
3197
+ var KNOWN_KEYS = new Set(Object.values(SECTION_KEYS).flat());
3198
+ function isObject2(value) {
3199
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3200
+ }
3201
+ function clean(value) {
3202
+ return sanitizeTerminalText(value);
3203
+ }
3204
+ function truncateText(value, width) {
3205
+ const flat = clean(value).replace(/\s+/g, " ").trim();
3206
+ if (terminalWidth(flat) <= width) return flat;
3207
+ let out = "";
3208
+ for (const char of flat) {
3209
+ if (terminalWidth(out + char) > width - 1) break;
3210
+ out += char;
3211
+ }
3212
+ return `${out}\u2026`;
3213
+ }
3214
+ function hintLines(items, width) {
3215
+ return joinFitting(items, width).map((line) => chalk4.dim(line));
3216
+ }
3217
+ function joinFitting(items, width) {
3218
+ const lines = [];
3219
+ let current = "";
3220
+ for (const item of items) {
3221
+ const next = current ? `${current} \xB7 ${item}` : item;
3222
+ if (current && terminalWidth(next) > width) {
3223
+ lines.push(current);
3224
+ current = item;
3225
+ } else {
3226
+ current = next;
3227
+ }
3228
+ }
3229
+ if (current) lines.push(current);
3230
+ return lines;
3231
+ }
3232
+ function formatScalar(value) {
3233
+ if (typeof value === "string") return stringColor(clean(value));
3234
+ if (typeof value === "number") return numberColor(String(value));
3235
+ if (typeof value === "boolean" || value === null) return literalColor(String(value));
3236
+ return highlightJson(JSON.stringify(value));
3237
+ }
3238
+ function flattenSettings(value, prefix = "") {
3239
+ if (!isObject2(value)) return [[prefix || "value", value]];
3240
+ const rows2 = [];
3241
+ for (const [key, child] of Object.entries(value)) {
3242
+ const path = prefix ? `${prefix}.${key}` : key;
3243
+ if (isObject2(child) && Object.keys(child).length > 0) rows2.push(...flattenSettings(child, path));
3244
+ else rows2.push([path, child]);
3245
+ }
3246
+ return rows2;
3247
+ }
3248
+ function highlightJson(source) {
3249
+ const token = /("(?:[^"\\]|\\.)*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false|null)\b|([{}[\],])/g;
3250
+ return clean(source).replace(token, (match, string, colon, number2, literal, punctuation) => {
3251
+ if (string !== void 0) {
3252
+ return colon !== void 0 ? `${keyColor(string)}${chalk4.dim(colon)}` : stringColor(string);
3253
+ }
3254
+ if (number2 !== void 0) return numberColor(number2);
3255
+ if (literal !== void 0) return literalColor(literal);
3256
+ if (punctuation !== void 0) return chalk4.dim(punctuation);
3257
+ return match;
3258
+ });
3259
+ }
3260
+ function highlightSpecDiff(diff) {
3261
+ return diff.split("\n").map((line) => {
3262
+ if (line.startsWith("@@")) return accent2.bold(line);
3263
+ if (line.startsWith("+")) return chalk4.green(line);
3264
+ if (line.startsWith("-")) return chalk4.red(line);
3265
+ return line;
3266
+ }).join("\n");
3267
+ }
3268
+ var ViewBuilder = class {
3269
+ constructor(width) {
3270
+ this.width = width;
3271
+ }
3272
+ lines = [];
3273
+ push(line = "") {
3274
+ this.lines.push(line);
3275
+ }
3276
+ /** Wrap text under a fixed indent and optional gutter marker. */
3277
+ paragraph(text2, indent, style = (value) => value, maxLines) {
3278
+ const body = this.width - terminalWidth(indent);
3279
+ const wrapped = clean(text2).split(/\r?\n/).flatMap((line) => line.trim() ? wrapTerminalLine(line, Math.max(20, body)) : [""]);
3280
+ const shown = maxLines && wrapped.length > maxLines ? wrapped.slice(0, maxLines) : wrapped;
3281
+ for (const line of shown) this.push(`${indent}${style(line)}`);
3282
+ if (shown.length < wrapped.length) {
3283
+ this.push(`${indent}${chalk4.dim(`\u2026 ${wrapped.length - shown.length} more lines`)}`);
3284
+ }
3285
+ }
3286
+ heading(section, detail) {
3287
+ const title = section.toUpperCase();
3288
+ const room = this.width - title.length - 3;
3289
+ const extra = detail && room >= 8 ? ` ${chalk4.dim(truncateText(detail, room))}` : "";
3290
+ this.push("");
3291
+ this.push(`${accent2("\u258D")}${accent2.bold(title)}${extra}`);
3292
+ }
3293
+ settingsGrid(entries, indent = " ") {
3294
+ if (entries.length === 0) return;
3295
+ const rows2 = entries.map(([key, value]) => [clean(key), value]);
3296
+ const compact = rows2.every(([, value]) => value === null || ["number", "boolean"].includes(typeof value) || typeof value === "string" && value.length <= 16);
3297
+ if (compact && rows2.length > 4) {
3298
+ const keyWidth2 = Math.max(...rows2.map(([key]) => key.length));
3299
+ const valueWidth = Math.max(...rows2.map(([, value]) => String(value).length));
3300
+ const cell = keyWidth2 + 2 + valueWidth;
3301
+ const columns = Math.min(3, Math.floor((this.width - indent.length + 4) / (cell + 4)));
3302
+ if (columns >= 2) {
3303
+ const height = Math.ceil(rows2.length / columns);
3304
+ for (let row = 0; row < height; row += 1) {
3305
+ const cells = [];
3306
+ for (let column = 0; column < columns; column += 1) {
3307
+ const entry = rows2[column * height + row];
3308
+ if (!entry) continue;
3309
+ const [key, value] = entry;
3310
+ const text2 = String(value);
3311
+ cells.push(`${keyColor(key.padEnd(keyWidth2))} ${formatScalar(typeof value === "string" ? clean(value) : value)}${" ".repeat(valueWidth - text2.length)}`);
3312
+ }
3313
+ this.push(`${indent}${cells.join(" ").trimEnd()}`);
3314
+ }
3315
+ return;
3316
+ }
3317
+ }
3318
+ const keyWidth = Math.min(28, Math.max(...rows2.map(([key]) => key.length)));
3319
+ for (const [key, value] of rows2) {
3320
+ const label = key.length > keyWidth ? truncateText(key, keyWidth) : key.padEnd(keyWidth);
3321
+ const rendered = typeof value === "string" ? stringColor(truncateText(value, Math.max(20, this.width - keyWidth - indent.length - 2))) : formatScalar(value);
3322
+ this.push(`${indent}${keyColor(label)} ${rendered}`);
3323
+ }
3324
+ }
3325
+ };
3326
+ function stringList(value) {
3327
+ return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item : JSON.stringify(item)) : [];
3328
+ }
3329
+ function exampleText(value) {
3330
+ if (typeof value === "string") return value;
3331
+ if (value === void 0) return "";
3332
+ return JSON.stringify(value);
3333
+ }
3334
+ function renderIdentity(view, spec, full) {
3335
+ if (!full) return;
3336
+ view.heading("identity", SECTION_BLURBS.identity);
3337
+ view.settingsGrid(SECTION_KEYS.identity.filter((key) => spec[key] !== void 0).map((key) => [key, spec[key]]));
3338
+ if (spec.engine === void 0) view.push(` ${chalk4.dim("engine defaults to adapter (LoRA on base_model)")}`);
3339
+ }
3340
+ function renderBehavior(view, spec, full) {
3341
+ const guidelines = stringList(spec.guidelines);
3342
+ const constraints = stringList(spec.constraints);
3343
+ view.heading("behavior", SECTION_BLURBS.behavior);
3344
+ view.push(` ${chalk4.bold("System prompt")}`);
3345
+ if (typeof spec.system_prompt === "string" && spec.system_prompt.trim()) {
3346
+ view.paragraph(spec.system_prompt, ` ${soft("\u2502")} `, chalk4.italic, full ? void 0 : 6);
3347
+ } else {
3348
+ view.push(` ${soft("\u2502")} ${chalk4.dim("(none)")}`);
3349
+ }
3350
+ const renderList = (label, items, mark) => {
3351
+ view.push(` ${chalk4.bold(label)} ${chalk4.dim(String(items.length))}`);
3352
+ if (items.length === 0) {
3353
+ view.push(` ${chalk4.dim("(none)")}`);
3354
+ return;
3355
+ }
3356
+ const limit = full ? items.length : 5;
3357
+ for (const item of items.slice(0, limit)) {
3358
+ const before = view.lines.length;
3359
+ view.paragraph(item, " ", void 0, full ? void 0 : 2);
3360
+ view.lines[before] = ` ${mark} ${view.lines[before].slice(6)}`;
3361
+ }
3362
+ if (items.length > limit) view.push(` ${chalk4.dim(`\u2026 ${items.length - limit} more \xB7 /spec behavior`)}`);
3363
+ };
3364
+ if (isObject2(spec.decision)) {
3365
+ view.push(` ${chalk4.bold("Decision question")}`);
3366
+ view.settingsGrid(flattenSettings(spec.decision));
3367
+ }
3368
+ renderList("Guidelines", guidelines, chalk4.green("\u2713"));
3369
+ renderList("Constraints", constraints, chalk4.red("\u2717"));
3370
+ }
3371
+ function renderExamples(view, spec, full) {
3372
+ const examples = Array.isArray(spec.examples) ? spec.examples : [];
3373
+ const count = examples.length;
3374
+ const size = count === 0 ? "none yet" : `${count} example${count === 1 ? "" : "s"}`;
3375
+ view.heading("examples", `${size} \xB7 ${SECTION_BLURBS.examples}`);
3376
+ if (count === 0) {
3377
+ view.push(` ${chalk4.dim("Ask TT to draft examples, or add input/output pairs to tunedtensor.json.")}`);
3378
+ return;
3379
+ }
3380
+ const limit = full ? count : 3;
3381
+ const number2 = String(Math.min(count, limit)).length;
3382
+ const textWidth = Math.max(20, view.width - number2 - 10);
3383
+ examples.slice(0, limit).forEach((example, index) => {
3384
+ const record4 = isObject2(example) ? example : { input: example };
3385
+ const label = chalk4.dim(String(index + 1).padStart(number2));
3386
+ const pad = " ".repeat(number2);
3387
+ if (full) {
3388
+ const before = view.lines.length;
3389
+ view.paragraph(exampleText(record4.input), ` ${pad} ${chalk4.cyan("in ")} `);
3390
+ view.lines[before] = view.lines[before].replace(` ${pad}`, ` ${label}`);
3391
+ view.paragraph(exampleText(record4.output), ` ${pad} ${chalk4.green("out")} `, stringColor);
3392
+ view.push("");
3393
+ } else {
3394
+ view.push(` ${label} ${chalk4.cyan("in ")} ${truncateText(exampleText(record4.input), textWidth)}`);
3395
+ view.push(` ${pad} ${chalk4.green("out")} ${stringColor(truncateText(exampleText(record4.output), textWidth))}`);
3396
+ }
3397
+ });
3398
+ if (full && view.lines.at(-1) === "") view.lines.pop();
3399
+ if (count > limit) view.push(` ${chalk4.dim(`\u2026 ${count - limit} more \xB7 /spec examples shows all`)}`);
3400
+ }
3401
+ function renderTraining(view, spec) {
3402
+ const foundation = spec.engine === "foundation";
3403
+ const settings = foundation ? spec.foundation : spec.hyperparameters;
3404
+ view.heading("training", foundation ? "foundation \xB7 tokenizer, pretrain, SFT" : spec.engine === "decision" ? "decision \xB7 typed classification" : `adapter \xB7 LoRA on ${typeof spec.base_model === "string" ? clean(spec.base_model) : "base_model"}`);
3405
+ const rows2 = isObject2(settings) ? flattenSettings(settings) : [];
3406
+ if (rows2.length === 0) {
3407
+ view.push(` ${chalk4.dim("Defaults \u2014 no overrides set.")}`);
3408
+ return;
3409
+ }
3410
+ view.settingsGrid(rows2);
3411
+ const other = foundation ? spec.hyperparameters : spec.foundation;
3412
+ if (isObject2(other)) view.push(` ${chalk4.yellow("!")} ${chalk4.dim(`${foundation ? "hyperparameters" : "foundation"} is set but unused by this engine`)}`);
3413
+ }
3414
+ function renderSettingsSection(view, spec, section, full) {
3415
+ const value = spec[section];
3416
+ if (value === void 0) {
3417
+ if (full) {
3418
+ view.heading(section, SECTION_BLURBS[section]);
3419
+ view.push(` ${chalk4.dim(section === "runtime" ? "Not set \u2014 runs on this machine with default paths." : section === "pipeline" ? "Not set \u2014 TT derives the default pipeline from this spec." : "Not set \u2014 default scoring.")}`);
3420
+ }
3421
+ return;
3422
+ }
3423
+ if (section === "pipeline" && !full) {
3424
+ const steps = isObject2(value) && Array.isArray(value.steps) ? value.steps.length : void 0;
3425
+ view.heading(section, SECTION_BLURBS[section]);
3426
+ view.push(` ${steps === void 0 ? "custom recipe" : `${steps} custom step${steps === 1 ? "" : "s"}`} ${chalk4.dim("\xB7 /spec pipeline")}`);
3427
+ return;
3428
+ }
3429
+ view.heading(section, SECTION_BLURBS[section]);
3430
+ if (section === "pipeline") {
3431
+ for (const line of JSON.stringify(value, null, 2).split("\n")) view.push(` ${highlightJson(line)}`);
3432
+ return;
3433
+ }
3434
+ view.settingsGrid(flattenSettings(value));
3435
+ }
3436
+ function renderOther(view, spec) {
3437
+ const rows2 = Object.entries(spec).filter(([key]) => !KNOWN_KEYS.has(key));
3438
+ if (rows2.length === 0) return;
3439
+ view.push("");
3440
+ view.push(`${accent2("\u258D")}${accent2.bold("OTHER")}`);
3441
+ view.settingsGrid(rows2);
3442
+ }
3443
+ function renderHeader(view, input, spec) {
3444
+ const name = typeof spec.name === "string" && spec.name.trim() ? clean(spec.name) : "Unnamed spec";
3445
+ const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red(`\u2717 ${input.validation.errors.length} issue${input.validation.errors.length === 1 ? "" : "s"}`);
3446
+ const gap = Math.max(2, view.width - terminalWidth(name) - terminalWidth(status2));
3447
+ view.push(`${chalk4.bold(name)}${" ".repeat(gap)}${status2}`);
3448
+ if (typeof spec.description === "string" && spec.description.trim()) {
3449
+ view.paragraph(spec.description, "", chalk4.dim, 3);
3450
+ }
3451
+ const examples = Array.isArray(spec.examples) ? spec.examples.length : 0;
3452
+ const facts = [
3453
+ spec.engine === "foundation" ? "foundation" : spec.engine === "decision" ? "decision" : "adapter",
3454
+ typeof spec.base_model === "string" ? truncateText(spec.base_model, view.width) : void 0,
3455
+ `${examples} example${examples === 1 ? "" : "s"}`,
3456
+ isObject2(spec.runtime) && isObject2(spec.runtime.gpu) ? "remote GPU" : void 0
3457
+ ].filter((fact) => Boolean(fact));
3458
+ view.lines.push(...joinFitting(facts, view.width));
3459
+ view.lines.push(...hintLines([truncateText(input.displayPath, view.width), `sha256 ${input.sha256.slice(0, 12)}`], view.width));
3460
+ }
3461
+ function renderDiagnostics(view, input) {
3462
+ const { errors, warnings, valid } = input.validation;
3463
+ if (valid && warnings.length === 0) return;
3464
+ view.push("");
3465
+ const marked = (text2, mark, style) => {
3466
+ const before = view.lines.length;
3467
+ view.paragraph(text2, " ", style);
3468
+ view.lines[before] = `${mark} ${view.lines[before].slice(2)}`;
3469
+ };
3470
+ for (const error of errors) marked(error, chalk4.red("\u2717"), chalk4.red);
3471
+ for (const warning of warnings) marked(warning, chalk4.yellow("!"), chalk4.yellow);
3472
+ }
3473
+ function renderInvalid(view, input) {
3474
+ view.push(`${chalk4.red("\u2717")} ${chalk4.bold(clean(input.displayPath))} ${chalk4.dim("could not be parsed")}`);
3475
+ for (const error of input.validation.errors) view.paragraph(error, " ", chalk4.red);
3476
+ view.push("");
3477
+ for (const line of clean(input.source).split("\n")) view.push(` ${chalk4.dim(line)}`);
3478
+ return `${view.lines.join("\n")}
3479
+ `;
3480
+ }
3481
+ function renderSpecView(input, options = {}) {
3482
+ const width = Math.max(40, Math.min(options.columns ?? 88, 88));
3483
+ const view = new ViewBuilder(width);
3484
+ if (!isObject2(input.document)) return renderInvalid(view, input);
3485
+ const spec = input.document;
3486
+ const section = options.section;
3487
+ if (!section) {
3488
+ renderHeader(view, input, spec);
3489
+ renderBehavior(view, spec, false);
3490
+ renderExamples(view, spec, false);
3491
+ renderTraining(view, spec);
3492
+ renderSettingsSection(view, spec, "evaluation", false);
3493
+ renderSettingsSection(view, spec, "runtime", false);
3494
+ renderSettingsSection(view, spec, "pipeline", false);
3495
+ renderOther(view, spec);
3496
+ renderDiagnostics(view, input);
3497
+ view.push("");
3498
+ view.lines.push(...hintLines(["/spec <section> for detail", "/spec show for JSON", "/spec diff", "/spec history"], width));
3499
+ view.lines.push(...wrapTerminalLine(`sections: ${SPEC_SECTIONS.join(", ")}`, width).map((line) => chalk4.dim(line)));
3500
+ return `${view.lines.join("\n")}
3501
+ `;
3502
+ }
3503
+ switch (section) {
3504
+ case "identity":
3505
+ renderIdentity(view, spec, true);
3506
+ break;
3507
+ case "behavior":
3508
+ renderBehavior(view, spec, true);
3509
+ break;
3510
+ case "examples":
3511
+ renderExamples(view, spec, true);
3512
+ break;
3513
+ case "training":
3514
+ renderTraining(view, spec);
3515
+ break;
3516
+ default:
3517
+ renderSettingsSection(view, spec, section, true);
3518
+ }
3519
+ view.lines.shift();
3520
+ return `${view.lines.join("\n")}
3521
+ `;
3522
+ }
3523
+ function renderSpecSource(input) {
3524
+ const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red("\u2717 invalid");
3525
+ const lines = [
3526
+ `${chalk4.bold(clean(input.displayPath))} ${chalk4.dim(`\xB7 sha256 ${input.sha256.slice(0, 12)}`)} ${status2}`,
3527
+ highlightJson(input.source.replace(/\s+$/, "")),
3528
+ ...input.validation.errors.map((error) => chalk4.red(`\u2717 ${clean(error)}`)),
3529
+ ...input.validation.warnings.map((warning) => chalk4.yellow(`! ${clean(warning)}`))
3530
+ ];
3531
+ return `${lines.join("\n")}
3532
+ `;
3533
+ }
3534
+
3034
3535
  // src/commands/spec.ts
3035
3536
  async function reviewSpec(workspaceRoot, operation = "show", specPath = "tunedtensor.json") {
3036
3537
  if (!["show", "validate", "diff", "history"].includes(operation)) {
@@ -3083,8 +3584,14 @@ ${label}
3083
3584
  ${diff}` };
3084
3585
  }
3085
3586
  function registerSpecCommands(program, cwd) {
3086
- program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]", "show, validate, diff, or history", "show").argument("[path]", "Workspace-relative tunedtensor.json", "tunedtensor.json").action(async (operation, path) => {
3087
- const result2 = await reviewSpec(cwd, operation, path);
3587
+ program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]", `show, view, validate, diff, history, or a section (${SPEC_SECTIONS.join(", ")})`, "show").argument("[path]", "Workspace-relative tunedtensor.json", "tunedtensor.json").action(async (operation, path) => {
3588
+ const section = resolveSpecSection(operation);
3589
+ if ((section || operation === "view") && !isJsonMode()) {
3590
+ const spec = await inspectLocalSpec(cwd, path);
3591
+ process.stdout.write(renderSpecView(spec, { section, columns: process.stdout.columns }));
3592
+ return;
3593
+ }
3594
+ const result2 = await reviewSpec(cwd, section || operation === "view" ? "show" : operation, path);
3088
3595
  if (isJsonMode()) {
3089
3596
  const { text: _text, ...data3 } = result2;
3090
3597
  printJson(data3);
@@ -3097,10 +3604,440 @@ function registerSpecCommands(program, cwd) {
3097
3604
  });
3098
3605
  }
3099
3606
 
3607
+ // src/shell-view.ts
3608
+ import chalk5 from "chalk";
3609
+ var accent3 = chalk5.hex("#8B5CF6");
3610
+ var GIB = 1024 ** 3;
3611
+ var BAR_WIDTH = 10;
3612
+ var MAX_PANEL_WIDTH = 88;
3613
+ function panelWidth(columns) {
3614
+ return Math.max(40, Math.min(columns ?? 80, MAX_PANEL_WIDTH));
3615
+ }
3616
+ function formatGiB(bytes) {
3617
+ const amount = bytes / GIB;
3618
+ return amount >= 100 ? amount.toFixed(0) : amount.toFixed(1);
3619
+ }
3620
+ function loadColor(percent2) {
3621
+ if (percent2 >= 85) return chalk5.red;
3622
+ if (percent2 >= 60) return chalk5.yellow;
3623
+ return chalk5.green;
3624
+ }
3625
+ function usageBar(percent2, width = BAR_WIDTH) {
3626
+ if (percent2 === void 0) return chalk5.dim("\xB7".repeat(width));
3627
+ const clamped = Math.max(0, Math.min(100, percent2));
3628
+ const filled = Math.round(clamped / 100 * width);
3629
+ return `${loadColor(clamped)("\u2588".repeat(filled))}${chalk5.dim("\u2591".repeat(width - filled))}`;
3630
+ }
3631
+ function percentLabel(percent2) {
3632
+ if (percent2 === void 0) return chalk5.dim(" n/a");
3633
+ return loadColor(percent2)(`${String(Math.round(percent2)).padStart(3)}%`).padStart(4);
3634
+ }
3635
+ function sectionRule(title, width, detail) {
3636
+ const label = `${accent3.bold(title)}${detail ? ` ${chalk5.dim(detail)}` : ""}`;
3637
+ const rule = Math.max(3, width - terminalWidth(label) - 1);
3638
+ return `${label} ${chalk5.dim("\u2500".repeat(rule))}`;
3639
+ }
3640
+ function inventoryFromUsage(usage) {
3641
+ const nvidia = usage.gpus.filter((gpu) => gpu.vendor === "nvidia");
3642
+ return {
3643
+ collected_at: usage.sampled_at,
3644
+ quick: true,
3645
+ node: { version: process.versions.node, major: Number(process.versions.node.split(".")[0]), ok: true },
3646
+ os: {
3647
+ platform: process.platform,
3648
+ arch: process.arch,
3649
+ type: process.platform,
3650
+ cpu_count: usage.cpu.cores,
3651
+ total_memory_bytes: usage.memory.total_bytes,
3652
+ free_memory_bytes: usage.memory.total_bytes - usage.memory.used_bytes
3653
+ },
3654
+ gpus: nvidia.map((gpu) => ({
3655
+ index: gpu.index,
3656
+ name: gpu.name,
3657
+ memory_total_bytes: gpu.memory_total_bytes,
3658
+ memory_free_bytes: gpu.memory_total_bytes !== void 0 && gpu.memory_used_bytes !== void 0 ? gpu.memory_total_bytes - gpu.memory_used_bytes : void 0,
3659
+ unified_memory: gpu.unified_memory
3660
+ })),
3661
+ nvidia_smi: { ok: nvidia.length > 0, message: usage.gpu_note ?? "" },
3662
+ disks: []
3663
+ };
3664
+ }
3665
+ function shortModel(id, width = 24) {
3666
+ return truncateText(id.split("/").at(-1) ?? id, width);
3667
+ }
3668
+ var STATUS_WORD = {
3669
+ ready: "ready",
3670
+ tight: "tight",
3671
+ not_possible: "not possible"
3672
+ };
3673
+ function describeFineTuneFit(usage, context) {
3674
+ const spec = context.spec;
3675
+ if (spec?.remoteGpu) {
3676
+ return {
3677
+ status: "remote",
3678
+ text: `training runs on AWS ${spec.remoteGpu} (runtime.gpu)`,
3679
+ source: "tt doctor checks it"
3680
+ };
3681
+ }
3682
+ const snapshot = context.host && !context.host.stale ? context.host.capabilities : void 0;
3683
+ let report = snapshot;
3684
+ const source = snapshot ? "from tt hardware" : "quick check \xB7 tt hardware for a full probe";
3685
+ if (!report && usage) report = evaluateCapabilities(inventoryFromUsage(usage));
3686
+ if (!report) return { status: "unknown", text: "not checked yet", source: "run tt hardware" };
3687
+ if (spec?.engine === "decision") {
3688
+ return {
3689
+ status: "unknown",
3690
+ text: "decision training runs locally on CPU, MPS or CUDA",
3691
+ source: "device selected by hyperparameters.device"
3692
+ };
3693
+ }
3694
+ const noCuda = !report.cuda_available;
3695
+ if (spec?.engine === "foundation") {
3696
+ const verdict = report.foundation.train;
3697
+ return {
3698
+ status: verdict.status,
3699
+ text: verdict.status === "not_possible" ? `foundation training ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}` : `foundation training ${STATUS_WORD[verdict.status]} \xB7 suggested max depth ${report.foundation.suggested_max_depth}`,
3700
+ source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
3701
+ };
3702
+ }
3703
+ if (spec?.baseModel) {
3704
+ const adapter = report.adapters.find((item) => item.id.toLowerCase() === spec.baseModel.toLowerCase());
3705
+ if (!adapter) {
3706
+ return {
3707
+ status: "unknown",
3708
+ text: `no certified memory profile for ${shortModel(spec.baseModel, 32)}`,
3709
+ source: "tt hardware lists supported base models"
3710
+ };
3711
+ }
3712
+ const verdict = adapter.finetune;
3713
+ return {
3714
+ status: verdict.status,
3715
+ text: `LoRA ${shortModel(adapter.id)} ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}`,
3716
+ source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
3717
+ };
3718
+ }
3719
+ const ready = report.adapters.filter((item) => item.finetune.status !== "not_possible");
3720
+ if (ready.length === 0) {
3721
+ return {
3722
+ status: "not_possible",
3723
+ text: noCuda ? "no local CUDA GPU for LoRA training" : "no certified base model fits this GPU",
3724
+ source: noCuda ? "runtime.gpu can use your AWS GPU" : source
3725
+ };
3726
+ }
3727
+ const best = ready.some((item) => item.finetune.status === "ready") ? "ready" : "tight";
3728
+ return {
3729
+ status: best,
3730
+ text: `LoRA ${best} for ${ready.map((item) => shortModel(item.id, 20)).join(", ")}`,
3731
+ source
3732
+ };
3733
+ }
3734
+ function fitMark(status2) {
3735
+ switch (status2) {
3736
+ case "ready":
3737
+ return chalk5.green("\u2713");
3738
+ case "tight":
3739
+ return chalk5.yellow("!");
3740
+ case "not_possible":
3741
+ return chalk5.red("\u2717");
3742
+ case "remote":
3743
+ return accent3("\u2197");
3744
+ default:
3745
+ return chalk5.dim("?");
3746
+ }
3747
+ }
3748
+ function gpuDetail(gpu) {
3749
+ const parts = [];
3750
+ if (gpu.vendor === "apple") {
3751
+ parts.push(`${formatGiB(gpu.memory_total_bytes ?? 0)} GiB unified \xB7 Metal`);
3752
+ } else if (gpu.memory_total_bytes !== void 0) {
3753
+ const used = gpu.memory_used_bytes;
3754
+ parts.push(used === void 0 ? `${formatGiB(gpu.memory_total_bytes)} GiB VRAM` : `VRAM ${formatGiB(used)}/${formatGiB(gpu.memory_total_bytes)} GiB`);
3755
+ }
3756
+ if (gpu.temperature_c !== void 0) parts.push(`${Math.round(gpu.temperature_c)}\xB0C`);
3757
+ return parts.join(" ");
3758
+ }
3759
+ function renderMeterRows(rows2, width) {
3760
+ const detailWidth = Math.max(0, ...rows2.map((row) => terminalWidth(row.detail)));
3761
+ const fixed = 6 + 1 + 1 + BAR_WIDTH + 1 + 4 + 2;
3762
+ const nameWidth = Math.max(6, Math.min(28, width - fixed - detailWidth));
3763
+ const detailRoom = Math.max(8, width - fixed - nameWidth);
3764
+ return rows2.map((row) => {
3765
+ const nameCell = truncateText(row.name, nameWidth).padEnd(nameWidth);
3766
+ return `${chalk5.bold(row.label.padEnd(6))} ${nameCell} ${usageBar(row.percent)} ${percentLabel(row.percent)} ${chalk5.dim(truncateText(row.detail, detailRoom))}`;
3767
+ });
3768
+ }
3769
+ function renderMachinePanel(usage, context, options = {}) {
3770
+ const width = panelWidth(options.columns);
3771
+ const lines = [sectionRule("MACHINE", width)];
3772
+ if (usage.gpus.length === 0) {
3773
+ const reason = usage.gpu_note ? ` (${usage.gpu_note})` : "";
3774
+ lines.push(`${chalk5.bold("GPU".padEnd(6))} ${chalk5.dim(truncateText(`no NVIDIA GPU detected${reason}`, width - 7))}`);
3775
+ }
3776
+ const meters = usage.gpus.map((gpu) => ({
3777
+ label: usage.gpus.length > 1 ? `GPU ${gpu.index}` : "GPU",
3778
+ // "NVIDIA GeForce RTX 4090" reads better as "GeForce RTX 4090" in a narrow column.
3779
+ name: sanitizeTerminalText(gpu.name).replace(/^NVIDIA\s+(?=\S)/, ""),
3780
+ percent: gpu.utilization_percent,
3781
+ detail: gpuDetail(gpu)
3782
+ }));
3783
+ meters.push({
3784
+ label: "CPU",
3785
+ name: `${usage.cpu.cores}\xD7 ${sanitizeTerminalText(usage.cpu.model)}`,
3786
+ percent: usage.cpu.utilization_percent,
3787
+ detail: `load ${usage.cpu.load_average[0].toFixed(2)}`
3788
+ });
3789
+ meters.push({
3790
+ label: "RAM",
3791
+ name: "system memory",
3792
+ percent: usage.memory.total_bytes > 0 ? usage.memory.used_bytes / usage.memory.total_bytes * 100 : void 0,
3793
+ detail: `${formatGiB(usage.memory.used_bytes)}/${formatGiB(usage.memory.total_bytes)} GiB`
3794
+ });
3795
+ if (usage.disk && usage.disk.total_bytes > 0) {
3796
+ meters.push({
3797
+ label: "Disk",
3798
+ name: "model cache",
3799
+ percent: (usage.disk.total_bytes - usage.disk.free_bytes) / usage.disk.total_bytes * 100,
3800
+ detail: `${formatGiB(usage.disk.free_bytes)} GiB free`
3801
+ });
3802
+ }
3803
+ lines.push(...renderMeterRows(meters, width));
3804
+ const fit = describeFineTuneFit(usage, context);
3805
+ lines.push(`${chalk5.bold("Tune".padEnd(6))} ${fitMark(fit.status)} ${truncateText(fit.text, width - 9)}`);
3806
+ if (fit.source) lines.push(`${" ".repeat(9)}${chalk5.dim(truncateText(fit.source, width - 9))}`);
3807
+ if (options.detailed) {
3808
+ const report = context.host && !context.host.stale ? context.host.capabilities : evaluateCapabilities(inventoryFromUsage(usage));
3809
+ if (report) {
3810
+ lines.push("", sectionRule("CERTIFIED BASE MODELS", width, "LoRA fine-tune"));
3811
+ const idWidth = Math.min(44, Math.floor(width * 0.55));
3812
+ const reasonWidth = Math.max(10, width - idWidth - 3);
3813
+ const row = (status2, label, reason) => `${fitMark(status2)} ${truncateText(label, idWidth).padEnd(idWidth)} ${chalk5.dim(truncateText(reason, reasonWidth))}`;
3814
+ for (const adapter of report.adapters) {
3815
+ lines.push(row(adapter.finetune.status, adapter.id, adapter.finetune.reason));
3816
+ }
3817
+ const foundation = report.foundation.train;
3818
+ lines.push(row(
3819
+ foundation.status,
3820
+ "foundation engine (from scratch)",
3821
+ foundation.status === "not_possible" ? foundation.reason : `max depth ${report.foundation.suggested_max_depth}`
3822
+ ));
3823
+ }
3824
+ lines.push("");
3825
+ const note = context.host ? `Full probe ${context.host.stale ? "is stale" : "cached"} from ${context.host.collectedAt.slice(0, 16).replace("T", " ")} \xB7 tt hardware refreshes it` : "No full probe yet \xB7 tt hardware checks CUDA, torch and disk space";
3826
+ lines.push(...wrapTerminalLine(note, width).map((line) => chalk5.dim(line)));
3827
+ }
3828
+ return lines;
3829
+ }
3830
+ function agentLabel(context) {
3831
+ const agent = context.agent;
3832
+ if (!agent?.provider || !agent.model) return void 0;
3833
+ return `${agent.provider}/${agent.model}`;
3834
+ }
3835
+ function bannerTip(input) {
3836
+ const { context, usage } = input;
3837
+ const agent = agentLabel(context);
3838
+ if (!agent) {
3839
+ return "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now.";
3840
+ }
3841
+ if (!context.spec) {
3842
+ return "No tunedtensor.json here yet. Describe the model you want and I'll draft a spec, or run init.";
3843
+ }
3844
+ if (context.spec.parseError) {
3845
+ return "tunedtensor.json doesn't parse. /spec show points at the problem.";
3846
+ }
3847
+ const busy = usage?.gpus.find((gpu) => (gpu.utilization_percent ?? 0) >= 85);
3848
+ if (busy) {
3849
+ return `${sanitizeTerminalText(busy.name)} is ${busy.utilization_percent}% busy; training now may be slow.`;
3850
+ }
3851
+ const examples = context.spec.exampleCount ?? 0;
3852
+ if (examples < 20) {
3853
+ return `${examples} example${examples === 1 ? "" : "s"} is a small set. Ask me to draft more, or /spec examples to review them.`;
3854
+ }
3855
+ return "Ask TT anything. Known commands run directly.";
3856
+ }
3857
+ function specLine(context) {
3858
+ const spec = context.spec;
3859
+ if (!spec) return `${chalk5.dim("spec")} ${chalk5.yellow("none here")} ${chalk5.dim("\xB7 init creates one")}`;
3860
+ if (spec.parseError) return `${chalk5.dim("spec")} ${chalk5.red("tunedtensor.json is invalid JSON")}`;
3861
+ const name = sanitizeTerminalText(spec.name ?? "unnamed");
3862
+ const facts = [
3863
+ spec.engine === "foundation" ? "foundation" : spec.baseModel ? sanitizeTerminalText(spec.baseModel) : void 0,
3864
+ spec.exampleCount === void 0 ? void 0 : `${spec.exampleCount} examples`
3865
+ ].filter(Boolean).join(" \xB7 ");
3866
+ return `${chalk5.dim("spec")} ${chalk5.bold(name)}${facts ? chalk5.dim(` \xB7 ${facts}`) : ""}`;
3867
+ }
3868
+ function renderBanner(input) {
3869
+ const width = panelWidth(input.columns);
3870
+ const { context } = input;
3871
+ const tip = bannerTip(input);
3872
+ const heading = [
3873
+ `${accent3.bold("tt")}${input.version ? ` ${chalk5.dim(`v${input.version}`)}` : ""} ${chalk5.dim("Tuned Tensor")}`,
3874
+ specLine(context),
3875
+ chalk5.dim(`agent ${agentLabel(context) ?? "not configured"} \xB7 workflow model ${input.activeModel}`)
3876
+ ];
3877
+ const mascot = renderMascot();
3878
+ const textWidth = width - MASCOT_WIDTH - 1;
3879
+ const offset = mascot.length - heading.length;
3880
+ const lines = mascot.map((row, index) => {
3881
+ const text2 = heading[index - offset] ?? "";
3882
+ return `${row} ${terminalWidth(text2) > textWidth ? truncateText(text2, textWidth) : text2}`;
3883
+ });
3884
+ if (input.usage) {
3885
+ lines.push("", ...renderMachinePanel(input.usage, context, { columns: input.columns }));
3886
+ }
3887
+ const prefix = mascotSays("");
3888
+ const hang = terminalWidth(prefix);
3889
+ const wrapped = wrapTerminalLine(tip, Math.max(20, width - hang));
3890
+ lines.push("", `${prefix}${wrapped[0]}`, ...wrapped.slice(1).map((line) => `${" ".repeat(hang)}${line}`));
3891
+ lines.push(...hintLines([
3892
+ "/spec review",
3893
+ "/system machine",
3894
+ "/help commands",
3895
+ "tab complete",
3896
+ "ctrl+c stop/clear",
3897
+ "ctrl+d exit"
3898
+ ], width));
3899
+ return `${lines.join("\n")}
3900
+
3901
+ `;
3902
+ }
3903
+
3904
+ // src/local-runtime/live-usage.ts
3905
+ import { execFile } from "child_process";
3906
+ import { statfs } from "fs/promises";
3907
+ import { cpus, freemem, homedir, loadavg, totalmem } from "os";
3908
+ import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
3909
+ function cpuTimes() {
3910
+ let idle = 0;
3911
+ let total = 0;
3912
+ for (const cpu of cpus()) {
3913
+ const times = cpu.times;
3914
+ idle += times.idle;
3915
+ total += times.user + times.nice + times.sys + times.irq + times.idle;
3916
+ }
3917
+ return { idle, total };
3918
+ }
3919
+ function cpuUtilization(before, after) {
3920
+ const total = after.total - before.total;
3921
+ if (!(total > 0)) return void 0;
3922
+ const busy = total - (after.idle - before.idle);
3923
+ return Math.min(100, Math.max(0, Math.round(busy / total * 100)));
3924
+ }
3925
+ function optionalNumber(value) {
3926
+ if (value === void 0) return void 0;
3927
+ const trimmed = value.trim();
3928
+ if (!trimmed || /^\[?n\/a\]?$/i.test(trimmed) || /not supported/i.test(trimmed)) return void 0;
3929
+ const parsed = Number(trimmed);
3930
+ return Number.isFinite(parsed) ? parsed : void 0;
3931
+ }
3932
+ var MIB = 1024 * 1024;
3933
+ var NVIDIA_USAGE_QUERY = [
3934
+ "--query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
3935
+ "--format=csv,noheader,nounits"
3936
+ ];
3937
+ function parseNvidiaUsage(stdout3) {
3938
+ const gpus = [];
3939
+ for (const line of stdout3.split(/\r?\n/).map((item) => item.trim()).filter(Boolean)) {
3940
+ const parts = line.split(",").map((part) => part.trim());
3941
+ if (parts.length < 2 || !/^\d+$/.test(parts[0] ?? "")) continue;
3942
+ const name = parts[1] || "NVIDIA GPU";
3943
+ const used = optionalNumber(parts[3]);
3944
+ const total = optionalNumber(parts[4]);
3945
+ gpus.push({
3946
+ index: Number(parts[0]),
3947
+ name,
3948
+ utilization_percent: optionalNumber(parts[2]),
3949
+ memory_used_bytes: used === void 0 ? void 0 : Math.round(used * MIB),
3950
+ memory_total_bytes: total === void 0 || total <= 0 ? void 0 : Math.round(total * MIB),
3951
+ temperature_c: optionalNumber(parts[5]),
3952
+ unified_memory: /spark|gb10/i.test(name),
3953
+ vendor: "nvidia"
3954
+ });
3955
+ }
3956
+ return gpus;
3957
+ }
3958
+ function queryNvidia(env, timeoutMs) {
3959
+ return new Promise((resolve13) => {
3960
+ execFile("nvidia-smi", NVIDIA_USAGE_QUERY, { env, timeout: timeoutMs, windowsHide: true }, (error, stdout3) => {
3961
+ if (error) {
3962
+ const code2 = error.code;
3963
+ resolve13({
3964
+ gpus: [],
3965
+ note: code2 === "ENOENT" ? "nvidia-smi not found" : error.killed ? "nvidia-smi timed out" : "nvidia-smi failed"
3966
+ });
3967
+ return;
3968
+ }
3969
+ const gpus = parseNvidiaUsage(String(stdout3));
3970
+ resolve13(gpus.length > 0 ? { gpus } : { gpus, note: "nvidia-smi listed no GPUs" });
3971
+ });
3972
+ });
3973
+ }
3974
+ function tidyCpuModel(model) {
3975
+ return model.replace(/\((?:R|TM|tm|r)\)/g, "").replace(/\s+(?:CPU|Processor)\b/gi, "").replace(/\s*@\s*[\d.]+\s*[GM]Hz/i, "").replace(/\s+\d+-Core$/i, "").replace(/\s+/g, " ").trim() || "CPU";
3976
+ }
3977
+ async function modelCacheDisk(env) {
3978
+ const path = resolve2(env.HF_HOME?.trim() || join7(homedir(), ".cache", "huggingface"));
3979
+ for (let probe = path, depth = 0; depth < 8; depth += 1) {
3980
+ try {
3981
+ const fs = await statfs(probe);
3982
+ return {
3983
+ path,
3984
+ free_bytes: Number(fs.bavail) * Number(fs.bsize),
3985
+ total_bytes: Number(fs.blocks) * Number(fs.bsize)
3986
+ };
3987
+ } catch {
3988
+ const parent = dirname3(probe);
3989
+ if (parent === probe) break;
3990
+ probe = parent;
3991
+ }
3992
+ }
3993
+ return void 0;
3994
+ }
3995
+ function appleGpu(cpuModel, platform, arch) {
3996
+ if (platform !== "darwin" || arch !== "arm64") return void 0;
3997
+ const chip = /apple/i.test(cpuModel) ? cpuModel.trim() : "Apple Silicon";
3998
+ return {
3999
+ index: 0,
4000
+ name: `${chip} GPU`,
4001
+ memory_total_bytes: totalmem(),
4002
+ unified_memory: true,
4003
+ vendor: "apple"
4004
+ };
4005
+ }
4006
+ async function sampleLiveUsage(options = {}) {
4007
+ const env = options.env ?? process.env;
4008
+ const platform = options.platform ?? process.platform;
4009
+ const arch = options.arch ?? process.arch;
4010
+ const before = cpuTimes();
4011
+ const [nvidia, disk] = await Promise.all([
4012
+ queryNvidia(env, options.gpuTimeoutMs ?? 2e3),
4013
+ modelCacheDisk(env).catch(() => void 0),
4014
+ new Promise((resolve13) => setTimeout(resolve13, options.cpuSampleMs ?? 200))
4015
+ ]);
4016
+ const after = cpuTimes();
4017
+ const list = cpus();
4018
+ const model = tidyCpuModel(list[0]?.model ?? "");
4019
+ const total = totalmem();
4020
+ const apple = nvidia.gpus.length === 0 ? appleGpu(model, platform, arch) : void 0;
4021
+ const [one, five, fifteen] = loadavg();
4022
+ return {
4023
+ sampled_at: (/* @__PURE__ */ new Date()).toISOString(),
4024
+ cpu: {
4025
+ model,
4026
+ cores: list.length,
4027
+ utilization_percent: cpuUtilization(before, after),
4028
+ load_average: [one ?? 0, five ?? 0, fifteen ?? 0]
4029
+ },
4030
+ memory: { used_bytes: Math.max(0, total - freemem()), total_bytes: total },
4031
+ disk,
4032
+ gpus: apple ? [apple] : nvidia.gpus,
4033
+ gpu_note: apple ? void 0 : nvidia.note
4034
+ };
4035
+ }
4036
+
3100
4037
  // src/shell.ts
3101
4038
  import { stat as stat3 } from "fs/promises";
3102
- import { homedir } from "os";
3103
- import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
4039
+ import { homedir as homedir2 } from "os";
4040
+ import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
3104
4041
  import { createInterface as createInterface2 } from "readline";
3105
4042
  import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
3106
4043
 
@@ -3108,6 +4045,7 @@ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
3108
4045
  var LOCAL = ["local"];
3109
4046
  var COMMAND_CATALOG = [
3110
4047
  { path: "spec", description: "Review the current behavior spec.", group: "Workflow", modes: LOCAL },
4048
+ { path: "spec view", description: "Readable, highlighted behavior spec; add a section name for detail.", group: "Workflow", modes: LOCAL },
3111
4049
  ...["show", "diff", "validate", "history"].map((operation) => ({ path: `spec ${operation}`, description: `Review behavior spec ${operation}.`, group: "Workflow", modes: LOCAL })),
3112
4050
  { path: "init", description: "Create a local behaviour-spec project.", group: "Workflow", modes: LOCAL },
3113
4051
  { path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
@@ -3184,7 +4122,8 @@ var COMMAND_CATALOG = [
3184
4122
  { path: "agent status", description: "Show the laptop-local TT agent selection.", group: "Inspect", modes: LOCAL }
3185
4123
  ];
3186
4124
  var SLASH_COMMANDS = [
3187
- { path: "/spec", description: "Review the behavior spec; add diff, validate or history." },
4125
+ { path: "/spec", description: "Readable behavior spec; add a section, show, diff, validate or history." },
4126
+ { path: "/system", description: "Live GPU, CPU and memory use, and what this machine can fine-tune." },
3188
4127
  { path: "/help", description: "Show commands; add a word to filter." },
3189
4128
  { path: "/status", description: "Show lightweight workflow status." },
3190
4129
  { path: "/context", description: "Show the current project context." },
@@ -3246,7 +4185,7 @@ function createCommandCompleter(getMode = () => "local") {
3246
4185
 
3247
4186
  // src/shell-context.ts
3248
4187
  import { readFile as readFile3, readdir as readdir3, stat as stat2 } from "fs/promises";
3249
- import { basename as basename2, dirname as dirname3, join as join7, resolve as resolve2 } from "path";
4188
+ import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "path";
3250
4189
  async function readJsonObject(path) {
3251
4190
  try {
3252
4191
  const parsed = JSON.parse(await readFile3(path, "utf8"));
@@ -3273,7 +4212,7 @@ function expandPath(value, baseDirectory, env) {
3273
4212
  return expandUserPath(value, env, baseDirectory);
3274
4213
  }
3275
4214
  function configPath(env) {
3276
- return join7(getConfigDir(env), "config.json");
4215
+ return join8(getConfigDir(env), "config.json");
3277
4216
  }
3278
4217
  function agentSelectionFrom(env, configAgent, hasToken) {
3279
4218
  const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
@@ -3291,14 +4230,14 @@ async function adjacentFile(path) {
3291
4230
  }
3292
4231
  }
3293
4232
  async function readActiveModelId(storeRoot) {
3294
- const result2 = await readJsonObject(join7(storeRoot, "active-model.json"));
4233
+ const result2 = await readJsonObject(join8(storeRoot, "active-model.json"));
3295
4234
  if (!result2.found || result2.invalid) return void 0;
3296
4235
  const modelId = result2.value?.model_id;
3297
4236
  if (modelId === null) return "base";
3298
4237
  return typeof modelId === "string" && modelId ? modelId : void 0;
3299
4238
  }
3300
4239
  async function readLatestRun(storeRoot) {
3301
- const runsRoot = join7(storeRoot, "runs");
4240
+ const runsRoot = join8(storeRoot, "runs");
3302
4241
  let names;
3303
4242
  try {
3304
4243
  names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -3306,7 +4245,7 @@ async function readLatestRun(storeRoot) {
3306
4245
  return void 0;
3307
4246
  }
3308
4247
  const states = await Promise.all(names.map(async (name) => {
3309
- const result2 = await readJsonObject(join7(runsRoot, name, "state.json"));
4248
+ const result2 = await readJsonObject(join8(runsRoot, name, "state.json"));
3310
4249
  if (!result2.found || result2.invalid || !result2.value) return void 0;
3311
4250
  const id = stringField(result2.value, "id") ?? name;
3312
4251
  const state = {
@@ -3334,14 +4273,15 @@ function hostFromSnapshot(snapshot) {
3334
4273
  summary: snapshot.summary,
3335
4274
  line: formatHostStatusLine(snapshot),
3336
4275
  cuda: snapshot.capabilities.cuda_available,
3337
- gpuName: snapshot.capabilities.gpu?.name
4276
+ gpuName: snapshot.capabilities.gpu?.name,
4277
+ capabilities: snapshot.capabilities
3338
4278
  };
3339
4279
  }
3340
4280
  async function discoverShellContext(options = {}) {
3341
4281
  const env = options.env ?? process.env;
3342
- const cwd = resolve2(options.cwd ?? process.cwd());
4282
+ const cwd = resolve3(options.cwd ?? process.cwd());
3343
4283
  const warnings = [];
3344
- const specPath = join7(cwd, "tunedtensor.json");
4284
+ const specPath = join8(cwd, "tunedtensor.json");
3345
4285
  const specJson = await readJsonObject(specPath);
3346
4286
  let spec;
3347
4287
  if (specJson.found) {
@@ -3350,31 +4290,35 @@ async function discoverShellContext(options = {}) {
3350
4290
  warnings.push("tunedtensor.json could not be parsed.");
3351
4291
  } else {
3352
4292
  const examples = specJson.value.examples;
4293
+ const runtime2 = specJson.value.runtime;
4294
+ const gpu = runtime2 && typeof runtime2 === "object" && !Array.isArray(runtime2) ? runtime2.gpu : void 0;
3353
4295
  spec = {
3354
4296
  path: specPath,
3355
4297
  id: stringField(specJson.value, "id"),
3356
4298
  name: stringField(specJson.value, "name"),
3357
4299
  baseModel: stringField(specJson.value, "base_model"),
4300
+ engine: stringField(specJson.value, "engine"),
3358
4301
  exampleCount: Array.isArray(examples) ? examples.length : void 0,
4302
+ remoteGpu: gpu && typeof gpu === "object" && !Array.isArray(gpu) ? stringField(gpu, "instanceId") : void 0,
3359
4303
  parseError: false
3360
4304
  };
3361
4305
  }
3362
4306
  }
3363
- const localConfigPath = join7(cwd, "local-runner.json");
4307
+ const localConfigPath = join8(cwd, "local-runner.json");
3364
4308
  const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
3365
4309
  const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
3366
4310
  if (localConfigJson.invalid) {
3367
4311
  warnings.push("local-runner.json could not be parsed.");
3368
4312
  }
3369
- const localConfigDirectory = hasAdjacentLocalConfig ? dirname3(localConfigPath) : cwd;
4313
+ const localConfigDirectory = hasAdjacentLocalConfig ? dirname4(localConfigPath) : cwd;
3370
4314
  const runtime = specJson.value?.runtime;
3371
4315
  const projectRuntime = runtime && typeof runtime === "object" && !Array.isArray(runtime) ? runtime : void 0;
3372
4316
  if (hasAdjacentLocalConfig && (projectRuntime || specJson.value?.evaluation)) warnings.push("Conflicting configuration sources; run tt pipeline migrate after reconciling the spec and local-runner.json.");
3373
4317
  const config = projectRuntime ?? localConfigJson.value;
3374
4318
  const configuredArtifactRoot = stringField(config, "artifactRoot");
3375
4319
  const configuredStoreRoot = stringField(config, "storeRoot");
3376
- const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) : resolve2(cwd, DEFAULT_ARTIFACT_ROOT);
3377
- const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ? resolve2(cwd, DEFAULT_PROJECT_STORE_ROOT) : defaultStoreRoot(env);
4320
+ const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) : resolve3(cwd, DEFAULT_ARTIFACT_ROOT);
4321
+ const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ? resolve3(cwd, DEFAULT_PROJECT_STORE_ROOT) : defaultStoreRoot(env);
3378
4322
  const storedConfigPath = configPath(env);
3379
4323
  const storedConfigJson = await readJsonObject(storedConfigPath);
3380
4324
  if (storedConfigJson.invalid) {
@@ -3408,7 +4352,7 @@ async function discoverShellContext(options = {}) {
3408
4352
  function valueOrDash(value) {
3409
4353
  return value === void 0 || value === "" ? "\u2014" : String(value);
3410
4354
  }
3411
- function agentLabel(context) {
4355
+ function agentLabel2(context) {
3412
4356
  const agent = context.agent;
3413
4357
  if (!agent?.provider && !agent?.model) return "not configured";
3414
4358
  const id = [agent.provider, agent.model].filter(Boolean).join("/");
@@ -3423,7 +4367,7 @@ function formatShellContext(context) {
3423
4367
  `Spec ID ${valueOrDash(context.spec?.id)}`,
3424
4368
  `Base model ${valueOrDash(context.spec?.baseModel)}`,
3425
4369
  `Examples ${valueOrDash(context.spec?.exampleCount)}`,
3426
- `Agent model ${agentLabel(context)}`,
4370
+ `Agent model ${agentLabel2(context)}`,
3427
4371
  `Local config ${context.local.configPath ?? "not found"}`,
3428
4372
  `Artifact root ${context.local.artifactRoot}`,
3429
4373
  `Store root ${context.local.storeRoot}`,
@@ -3435,7 +4379,7 @@ function formatShellContext(context) {
3435
4379
  function formatShellStatus(context) {
3436
4380
  return [
3437
4381
  `Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`,
3438
- `Agent model ${agentLabel(context)}`,
4382
+ `Agent model ${agentLabel2(context)}`,
3439
4383
  `Local config ${context.local.configPath ?? "not found"}`,
3440
4384
  `Active model ${context.local.activeModelId ?? "base"}`,
3441
4385
  `Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
@@ -3612,7 +4556,7 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
3612
4556
  }
3613
4557
 
3614
4558
  // src/secret-prompt.ts
3615
- import chalk3 from "chalk";
4559
+ import chalk6 from "chalk";
3616
4560
  import { emitKeypressEvents } from "readline";
3617
4561
  import { createInterface } from "readline/promises";
3618
4562
  import { stdin, stdout } from "process";
@@ -3690,16 +4634,16 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
3690
4634
  emitKeypressEvents(input);
3691
4635
  return await withDetachedKeypress(input, async () => {
3692
4636
  const wasRaw = terminal.isRaw === true;
3693
- terminal.setRawMode(true);
3694
- input.resume();
3695
- output.write(`${message} ${hint()}`);
3696
4637
  try {
3697
- const choice = await new Promise((resolve11) => {
4638
+ terminal.setRawMode(true);
4639
+ input.resume();
4640
+ output.write(`${message} ${hint()}`);
4641
+ const choice = await new Promise((resolve13) => {
3698
4642
  const finish = (decision) => {
3699
4643
  input.removeListener("keypress", onKeypress);
3700
4644
  input.removeListener("end", onEnd);
3701
4645
  input.removeListener("close", onEnd);
3702
- resolve11(decision);
4646
+ resolve13(decision);
3703
4647
  };
3704
4648
  const onKeypress = (text2, key) => {
3705
4649
  const decided = keyChoiceFor(key, text2);
@@ -3719,7 +4663,7 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
3719
4663
  });
3720
4664
  }
3721
4665
  function hint() {
3722
- return chalk3.dim("y yes \xB7 n no \xB7 esc decide later ");
4666
+ return chalk6.dim("y yes \xB7 n no \xB7 esc decide later ");
3723
4667
  }
3724
4668
 
3725
4669
  // src/shell.ts
@@ -3880,6 +4824,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
3880
4824
  "help",
3881
4825
  "status",
3882
4826
  "context",
4827
+ "system",
3883
4828
  "model",
3884
4829
  "login",
3885
4830
  "clear",
@@ -3949,9 +4894,9 @@ function parseSlashCommand(input) {
3949
4894
  function errorMessage(error) {
3950
4895
  return error instanceof Error ? error.message : String(error);
3951
4896
  }
3952
- var successMark = () => chalk4.green("\u2713");
3953
- var errorMark = () => chalk4.red("\u2717");
3954
- var accent2 = chalk4.hex("#8B5CF6");
4897
+ var successMark = () => chalk7.green("\u2713");
4898
+ var errorMark = () => chalk7.red("\u2717");
4899
+ var accent4 = chalk7.hex("#8B5CF6");
3955
4900
  var ANSI_BOLD = "\x1B[1m";
3956
4901
  var ANSI_BOLD_OFF = "\x1B[22m";
3957
4902
  var ANSI_CLEAR_LINE = "\x1B[2K";
@@ -3960,15 +4905,15 @@ var ANSI_CLEAR_DOWN = "\x1B[J";
3960
4905
  var ANSI_CURSOR_UP = "\x1B[1A";
3961
4906
  var ANSI_RESET = "\x1B[0m";
3962
4907
  function inputColorCodes() {
3963
- if (chalk4.level === 0) return null;
3964
- if (chalk4.level === 1) {
4908
+ if (chalk7.level === 0) return null;
4909
+ if (chalk7.level === 1) {
3965
4910
  return {
3966
4911
  background: "\x1B[100m",
3967
4912
  accent: "\x1B[95m",
3968
4913
  text: "\x1B[97m"
3969
4914
  };
3970
4915
  }
3971
- if (chalk4.level === 2) {
4916
+ if (chalk7.level === 2) {
3972
4917
  return {
3973
4918
  background: "\x1B[48;5;238m",
3974
4919
  accent: "\x1B[38;5;183m",
@@ -3981,13 +4926,14 @@ function inputColorCodes() {
3981
4926
  text: "\x1B[38;2;245;243;255m"
3982
4927
  };
3983
4928
  }
4929
+ var SPEC_USAGE = `Usage: /spec [${SPEC_SECTIONS.join("|")}|show|diff|validate|history] [path/to/tunedtensor.json]`;
3984
4930
  var DETAIL_LABEL_WIDTH = 15;
3985
4931
  function styleDetailLines(lines) {
3986
4932
  return lines.map((line) => {
3987
4933
  if (line.length <= DETAIL_LABEL_WIDTH) return line;
3988
4934
  const label = line.slice(0, DETAIL_LABEL_WIDTH);
3989
4935
  if (!label.trim()) return line;
3990
- const color = label.startsWith("Warning") ? chalk4.yellow : accent2.bold;
4936
+ const color = label.startsWith("Warning") ? chalk7.yellow : accent4.bold;
3991
4937
  return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
3992
4938
  });
3993
4939
  }
@@ -4000,42 +4946,47 @@ function assertNoArgs(command, args2) {
4000
4946
  }
4001
4947
  }
4002
4948
  function expandDirectory(value, cwd, env) {
4003
- const home = env.HOME ? resolve3(env.HOME) : homedir();
4949
+ const home = env.HOME ? resolve4(env.HOME) : homedir2();
4004
4950
  if (value === "~") return home;
4005
- if (value.startsWith("~/")) return resolve3(home, value.slice(2));
4006
- return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
4951
+ if (value.startsWith("~/")) return resolve4(home, value.slice(2));
4952
+ return isAbsolute3(value) ? resolve4(value) : resolve4(cwd, value);
4007
4953
  }
4008
4954
  function helpText(mode, query, palette = false) {
4009
4955
  const groups = groupedCatalog(mode, query);
4010
4956
  const lines = [];
4011
- lines.push(accent2.bold(palette ? "Commands \u2014 type a command, or prefix with : to make the intent explicit" : `TT commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
4957
+ lines.push(accent4.bold(palette ? "Commands \u2014 type a command, or prefix with : to make the intent explicit" : `TT commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
4012
4958
  if (groups.size === 0) {
4013
- lines.push(chalk4.dim(" No matching commands."));
4959
+ lines.push(chalk7.dim(" No matching commands."));
4014
4960
  } else {
4015
4961
  for (const [group, commands] of groups) {
4016
4962
  lines.push("");
4017
- lines.push(chalk4.bold(group));
4963
+ lines.push(chalk7.bold(group));
4018
4964
  for (const command of commands) {
4019
- lines.push(` ${accent2(command.path.padEnd(22))} ${command.description}`);
4965
+ lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
4020
4966
  }
4021
4967
  }
4022
4968
  }
4023
4969
  if (!query) {
4024
4970
  lines.push("");
4025
- lines.push(chalk4.bold("Conversation"));
4026
- lines.push(` ${accent2("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
4027
- lines.push(` ${accent2("/new".padEnd(22))} Start a new agent conversation.`);
4028
- lines.push(` ${accent2("/threads".padEnd(22))} List recent conversations.`);
4029
- lines.push(` ${accent2("/resume <id>".padEnd(22))} Resume a conversation.`);
4030
- lines.push(` ${accent2("/approve [id]".padEnd(22))} Approve an action left pending.`);
4031
- lines.push(` ${accent2("/reject [id]".padEnd(22))} Reject an action left pending.`);
4971
+ lines.push(chalk7.bold("Conversation"));
4972
+ lines.push(` ${accent4("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
4973
+ lines.push(` ${accent4("/new".padEnd(22))} Start a new agent conversation.`);
4974
+ lines.push(` ${accent4("/threads".padEnd(22))} List recent conversations.`);
4975
+ lines.push(` ${accent4("/resume <id>".padEnd(22))} Resume a conversation.`);
4976
+ lines.push(` ${accent4("/approve [id]".padEnd(22))} Approve an action left pending.`);
4977
+ lines.push(` ${accent4("/reject [id]".padEnd(22))} Reject an action left pending.`);
4032
4978
  lines.push("");
4033
- lines.push(chalk4.bold("Session"));
4979
+ lines.push(chalk7.bold("Session"));
4034
4980
  for (const command of SLASH_COMMANDS) {
4035
- lines.push(` ${accent2(command.path.padEnd(22))} ${command.description}`);
4981
+ lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
4036
4982
  }
4037
- lines.push(` ${accent2("?".padEnd(22))} Alias for /help.`);
4038
- lines.push(chalk4.dim(
4983
+ lines.push(` ${accent4("?".padEnd(22))} Alias for /help.`);
4984
+ lines.push("");
4985
+ lines.push(chalk7.bold("Behavior spec"));
4986
+ lines.push(` ${accent4("/spec".padEnd(22))} Readable overview of tunedtensor.json.`);
4987
+ lines.push(` ${accent4("/spec <section>".padEnd(22))} One section in full: ${SPEC_SECTIONS.join(", ")}.`);
4988
+ lines.push(` ${accent4("/spec show".padEnd(22))} The raw JSON, highlighted.`);
4989
+ lines.push(chalk7.dim(
4039
4990
  "\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
4040
4991
  ));
4041
4992
  }
@@ -4045,33 +4996,17 @@ function helpText(mode, query, palette = false) {
4045
4996
  function activeModelLabel(snapshot) {
4046
4997
  return snapshot.context.local.activeModelId ?? "base";
4047
4998
  }
4048
- function agentModelLabel(context) {
4049
- const agent = context.agent;
4050
- if (!agent?.provider && !agent?.model) return "not configured";
4051
- return [agent.provider, agent.model].filter(Boolean).join("/") || "not configured";
4052
- }
4053
- function renderShellBanner(snapshot) {
4054
- const heading = snapshot.version ? `${accent2.bold("tt")} ${chalk4.dim(`v${snapshot.version}`)}` : accent2.bold("tt");
4055
- const configured = Boolean(
4056
- snapshot.context.agent?.provider && snapshot.context.agent?.model
4057
- );
4058
- const lines = [
4059
- heading,
4060
- chalk4.dim(
4061
- `agent ${agentModelLabel(snapshot.context)} \xB7 workflow model ${activeModelLabel(snapshot)}`
4062
- ),
4063
- chalk4.dim("/spec review \xB7 ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
4064
- "",
4065
- chalk4.dim(
4066
- configured ? "Ask TT anything. Known commands run directly." : "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now."
4067
- )
4068
- ];
4069
- return `${lines.join("\n")}
4070
-
4071
- `;
4999
+ function renderShellBanner(snapshot, columns) {
5000
+ return renderBanner({
5001
+ version: snapshot.version,
5002
+ context: snapshot.context,
5003
+ activeModel: activeModelLabel(snapshot),
5004
+ usage: snapshot.usage,
5005
+ columns
5006
+ });
4072
5007
  }
4073
5008
  function renderShellPrompt() {
4074
- return `${accent2.bold("\u203A")} `;
5009
+ return `${accent4.bold("\u203A")} `;
4075
5010
  }
4076
5011
  function renderSubmittedShellInput(input, columns) {
4077
5012
  const colors = inputColorCodes();
@@ -4104,7 +5039,7 @@ function resetShellPromptStyle() {
4104
5039
  return "";
4105
5040
  }
4106
5041
  var TunedTensorShellSession = class _TunedTensorShellSession {
4107
- constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, initialContext) {
5042
+ constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, systemProbe, columns, initialContext) {
4108
5043
  this.runner = runner;
4109
5044
  this.io = io;
4110
5045
  this.agent = agent;
@@ -4112,6 +5047,8 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4112
5047
  this.contextProvider = contextProvider;
4113
5048
  this.version = version;
4114
5049
  this.agentModelRuntime = agentModelRuntime;
5050
+ this.systemProbe = systemProbe;
5051
+ this.columns = columns;
4115
5052
  this.cwd = initialContext.cwd;
4116
5053
  this.context = initialContext;
4117
5054
  }
@@ -4119,12 +5056,13 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4119
5056
  modeSource = "default-local";
4120
5057
  cwd;
4121
5058
  context;
5059
+ usage;
4122
5060
  static async create(options) {
4123
5061
  const env = options.env ?? process.env;
4124
- const cwd = resolve3(options.cwd ?? process.cwd());
5062
+ const cwd = resolve4(options.cwd ?? process.cwd());
4125
5063
  const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
4126
5064
  const initialContext = await contextProvider({ cwd, env });
4127
- return new _TunedTensorShellSession(
5065
+ const session = new _TunedTensorShellSession(
4128
5066
  options.runner,
4129
5067
  options.io,
4130
5068
  options.agent,
@@ -4132,8 +5070,22 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4132
5070
  contextProvider,
4133
5071
  options.version,
4134
5072
  options.agentModelRuntime,
5073
+ options.systemProbe,
5074
+ options.columns ?? (() => void 0),
4135
5075
  initialContext
4136
5076
  );
5077
+ await session.sampleSystem();
5078
+ return session;
5079
+ }
5080
+ /** Refresh live machine usage; a failed probe only hides the panel. */
5081
+ async sampleSystem() {
5082
+ if (!this.systemProbe) return void 0;
5083
+ try {
5084
+ this.usage = await this.systemProbe();
5085
+ } catch {
5086
+ this.usage = void 0;
5087
+ }
5088
+ return this.usage;
4137
5089
  }
4138
5090
  interruptAgent() {
4139
5091
  return this.agent?.interrupt() ?? false;
@@ -4144,14 +5096,25 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4144
5096
  modeSource: this.modeSource,
4145
5097
  cwd: this.cwd,
4146
5098
  context: this.context,
4147
- version: this.version
5099
+ version: this.version,
5100
+ usage: this.usage
4148
5101
  };
4149
5102
  }
4150
5103
  prompt() {
4151
5104
  return renderShellPrompt();
4152
5105
  }
4153
5106
  banner() {
4154
- return renderShellBanner(this.snapshot());
5107
+ return renderShellBanner(this.snapshot(), this.columns());
5108
+ }
5109
+ async inspectSpecForView(path) {
5110
+ try {
5111
+ return await inspectLocalSpec(this.cwd, path);
5112
+ } catch (error) {
5113
+ if (error.code === "ENOENT") {
5114
+ throw new ShellParseError(`No ${path} in ${this.cwd}. Run init, or ask TT to draft a behavior spec.`);
5115
+ }
5116
+ throw error;
5117
+ }
4155
5118
  }
4156
5119
  async refreshContext() {
4157
5120
  this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
@@ -4187,42 +5150,42 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4187
5150
  const provider = query ? findAgentProvider(runtime, query) : void 0;
4188
5151
  if (provider) {
4189
5152
  const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
4190
- lines.push("", chalk4.bold(`Models from ${provider.id}`));
5153
+ lines.push("", chalk7.bold(`Models from ${provider.id}`));
4191
5154
  this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
4192
5155
  if (matches.length === 0) {
4193
- lines.push(chalk4.dim(" no models"));
5156
+ lines.push(chalk7.dim(" no models"));
4194
5157
  }
4195
5158
  return lines;
4196
5159
  }
4197
5160
  if (query) {
4198
5161
  const matches = listAgentModels(runtime, { ...catalog, query });
4199
- lines.push("", chalk4.bold(`Models matching ${JSON.stringify(query)}`));
5162
+ lines.push("", chalk7.bold(`Models matching ${JSON.stringify(query)}`));
4200
5163
  this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
4201
5164
  if (matches.length === 0) {
4202
- lines.push(chalk4.dim(" no matches"));
5165
+ lines.push(chalk7.dim(" no matches"));
4203
5166
  }
4204
5167
  return lines;
4205
5168
  }
4206
5169
  const providers = listAgentProviders(runtime, { featuredOnly: true });
4207
- lines.push("", chalk4.bold("Providers"));
5170
+ lines.push("", chalk7.bold("Providers"));
4208
5171
  if (providers.length === 0) {
4209
- lines.push(chalk4.dim(" no providers in the catalog"));
5172
+ lines.push(chalk7.dim(" no providers in the catalog"));
4210
5173
  } else {
4211
5174
  for (const choice of providers) {
4212
5175
  lines.push(this.formatAgentProviderChoice(choice));
4213
5176
  }
4214
- lines.push(chalk4.dim(" Other providers: /login <id> or /model <id>"));
5177
+ lines.push(chalk7.dim(" Other providers: /login <id> or /model <id>"));
4215
5178
  if (providers.some((choice) => !choice.authenticated)) {
4216
- lines.push(chalk4.dim(" Use /login tunedtensor for your TT token, or /login <provider> for a BYO key."));
5179
+ lines.push(chalk7.dim(" Use /login tunedtensor for your TT token, or /login <provider> for a BYO key."));
4217
5180
  }
4218
5181
  }
4219
5182
  const suggestions = recommendAgentModels(runtime, featuredCatalog);
4220
- lines.push("", chalk4.bold("Suggestions"));
5183
+ lines.push("", chalk7.bold("Suggestions"));
4221
5184
  this.appendAgentModelChoices(lines, suggestions, summary);
4222
5185
  if (suggestions.length === 0) {
4223
- lines.push(chalk4.dim(" no models in the catalog"));
5186
+ lines.push(chalk7.dim(" no models in the catalog"));
4224
5187
  } else {
4225
- lines.push(chalk4.dim(" Use /model <provider> to list models, /model <query> to search"));
5188
+ lines.push(chalk7.dim(" Use /model <provider> to list models, /model <query> to search"));
4226
5189
  }
4227
5190
  return lines;
4228
5191
  }
@@ -4233,25 +5196,35 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4233
5196
  }
4234
5197
  if (limit && models.length > shown.length) {
4235
5198
  const hint2 = moreHint ? ` \u2014 ${moreHint}` : "";
4236
- lines.push(chalk4.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
5199
+ lines.push(chalk7.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
4237
5200
  }
4238
5201
  }
4239
5202
  formatAgentProviderChoice(provider) {
4240
5203
  const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
4241
- const auth = provider.authenticated ? "" : chalk4.dim(" auth required");
4242
- return ` ${accent2(provider.id)}${displayName}${auth}`;
5204
+ const auth = provider.authenticated ? "" : chalk7.dim(" auth required");
5205
+ return ` ${accent4(provider.id)}${displayName}${auth}`;
4243
5206
  }
4244
5207
  formatAgentModelChoice(model, summary) {
4245
5208
  const active = summary?.provider === model.provider && summary?.model === model.id;
4246
5209
  const displayName = model.name !== model.id ? ` ${model.name}` : "";
4247
- const auth = model.authenticated ? "" : chalk4.dim(" auth required");
4248
- return ` ${accent2(`${model.provider}/${model.id}`)}${displayName}${active ? chalk4.dim(" (active)") : ""}${auth}`;
5210
+ const auth = model.authenticated ? "" : chalk7.dim(" auth required");
5211
+ return ` ${accent4(`${model.provider}/${model.id}`)}${displayName}${active ? chalk7.dim(" (active)") : ""}${auth}`;
4249
5212
  }
4250
5213
  async handleSlash(command) {
4251
5214
  switch (command.name) {
4252
5215
  case "spec": {
4253
- if (command.args.length > 2) throw new ShellParseError("Usage: /spec [show|diff|validate|history] [path/to/tunedtensor.json]");
4254
- const [operation = "show", path = "tunedtensor.json"] = command.args;
5216
+ if (command.args.length > 2) throw new ShellParseError(SPEC_USAGE);
5217
+ let [operation = "view", path = "tunedtensor.json"] = command.args;
5218
+ if (command.args.length === 1 && /(^|\/)tunedtensor\.json$/.test(operation)) {
5219
+ [operation, path] = ["view", operation];
5220
+ }
5221
+ const section = resolveSpecSection(operation);
5222
+ if (section || operation === "view" || operation === "show") {
5223
+ const spec = await this.inspectSpecForView(path);
5224
+ this.io.write(operation === "show" ? renderSpecSource(spec) : renderSpecView(spec, { section, columns: this.columns() }));
5225
+ return "continue";
5226
+ }
5227
+ if (!["diff", "validate", "history"].includes(operation)) throw new ShellParseError(SPEC_USAGE);
4255
5228
  if (operation === "diff") {
4256
5229
  const current = await inspectLocalSpec(this.cwd, path);
4257
5230
  const pending = this.agent?.snapshot?.().pendingActions.filter((action) => {
@@ -4262,18 +5235,27 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4262
5235
  for (const action of pending) {
4263
5236
  const preview = action.preview;
4264
5237
  const input = action.arguments;
4265
- const text2 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
5238
+ const text3 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
4266
5239
  ${preview.diff}
4267
5240
  `;
4268
- this.io.write(sanitizeTerminalText(text2));
5241
+ this.io.write(highlightSpecDiff(sanitizeTerminalText(text3)));
4269
5242
  }
4270
5243
  return "continue";
4271
5244
  }
4272
5245
  }
4273
- this.io.write(`${sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text)}
5246
+ const text2 = sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text);
5247
+ this.io.write(`${operation === "diff" ? highlightSpecDiff(text2) : text2}
4274
5248
  `);
4275
5249
  return "continue";
4276
5250
  }
5251
+ case "system": {
5252
+ assertNoArgs("system", command.args);
5253
+ const usage = await this.sampleSystem();
5254
+ if (!usage) throw new ShellParseError("Live machine usage is unavailable in this session.");
5255
+ await this.refreshContext();
5256
+ this.writeLines(renderMachinePanel(usage, this.context, { columns: this.columns(), detailed: true }));
5257
+ return "continue";
5258
+ }
4277
5259
  case "palette":
4278
5260
  this.io.write(helpText(this.mode, void 0, true));
4279
5261
  return "continue";
@@ -4336,9 +5318,9 @@ ${preview.diff}
4336
5318
  }
4337
5319
  this.writeLines([
4338
5320
  "",
4339
- chalk4.bold("Providers"),
5321
+ chalk7.bold("Providers"),
4340
5322
  ...providers.map((choice) => this.formatAgentProviderChoice(choice)),
4341
- chalk4.dim(" Other providers: /login <id>")
5323
+ chalk7.dim(" Other providers: /login <id>")
4342
5324
  ]);
4343
5325
  if (!this.io.promptLine) {
4344
5326
  throw new ShellParseError("Usage: /login <provider>");
@@ -4526,7 +5508,9 @@ async function startInteractiveShell(options) {
4526
5508
  cwd: options.cwd,
4527
5509
  env: options.env,
4528
5510
  version: options.version,
4529
- agentModelRuntime: options.agentModelRuntime
5511
+ agentModelRuntime: options.agentModelRuntime,
5512
+ systemProbe: options.systemProbe ?? (() => sampleLiveUsage({ env: { ...process.env, ...options.env } })),
5513
+ columns: () => output.columns
4530
5514
  });
4531
5515
  const completer = createCommandCompleter(() => session.snapshot().mode);
4532
5516
  readline = createInterface2({
@@ -4577,6 +5561,9 @@ async function startInteractiveShell(options) {
4577
5561
  } finally {
4578
5562
  output.write(resetShellPromptStyle());
4579
5563
  readline.close();
5564
+ output.write(`
5565
+ ${mascotFarewell()}
5566
+ `);
4580
5567
  }
4581
5568
  }
4582
5569
  function ciEnabled(value) {
@@ -4589,9 +5576,9 @@ function shouldStartInteractiveShell(options) {
4589
5576
  }
4590
5577
 
4591
5578
  // src/agent.ts
4592
- var accent3 = chalk5.hex("#8B5CF6");
4593
- var successMark2 = () => chalk5.green("\u2713");
4594
- var errorMark2 = () => chalk5.red("\u2717");
5579
+ var accent5 = chalk8.hex("#8B5CF6");
5580
+ var successMark2 = () => chalk8.green("\u2713");
5581
+ var errorMark2 = () => chalk8.red("\u2717");
4595
5582
  var ANSI_CLEAR_LINE2 = "\r\x1B[2K";
4596
5583
  var GUTTER = " ";
4597
5584
  function isRecord3(value) {
@@ -4602,25 +5589,25 @@ function stringValue(value) {
4602
5589
  }
4603
5590
  function helpText2() {
4604
5591
  return [
4605
- accent3.bold("Agent commands"),
5592
+ accent5.bold("Agent commands"),
4606
5593
  "",
4607
- ` ${accent3("/new".padEnd(22))} Start a new conversation.`,
4608
- ` ${accent3("/threads".padEnd(22))} List recent conversations.`,
4609
- ` ${accent3("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
4610
- ` ${accent3("/approve [id]".padEnd(22))} Approve an action left pending.`,
4611
- ` ${accent3("/reject [id]".padEnd(22))} Reject an action left pending.`,
4612
- ` ${accent3("/status".padEnd(22))} Show the active conversation.`,
4613
- ` ${accent3("/clear".padEnd(22))} Clear the terminal.`,
4614
- ` ${accent3("/exit".padEnd(22))} Exit the agent.`,
5594
+ ` ${accent5("/new".padEnd(22))} Start a new conversation.`,
5595
+ ` ${accent5("/threads".padEnd(22))} List recent conversations.`,
5596
+ ` ${accent5("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
5597
+ ` ${accent5("/approve [id]".padEnd(22))} Approve an action left pending.`,
5598
+ ` ${accent5("/reject [id]".padEnd(22))} Reject an action left pending.`,
5599
+ ` ${accent5("/status".padEnd(22))} Show the active conversation.`,
5600
+ ` ${accent5("/clear".padEnd(22))} Clear the terminal.`,
5601
+ ` ${accent5("/exit".padEnd(22))} Exit the agent.`,
4615
5602
  "",
4616
- chalk5.dim(
5603
+ chalk8.dim(
4617
5604
  "Everything else is sent to the laptop-local Tuned Tensor assistant; known TT commands still run directly in the shell. Proposed changes ask for approval right after the answer."
4618
5605
  ),
4619
5606
  ""
4620
5607
  ].join("\n");
4621
5608
  }
4622
5609
  function formatThread(thread, activeId) {
4623
- const active = thread.id === activeId ? accent3("\u25CF") : chalk5.dim("\u25CB");
5610
+ const active = thread.id === activeId ? accent5("\u25CF") : chalk8.dim("\u25CB");
4624
5611
  const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
4625
5612
  const date = new Date(time);
4626
5613
  const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
@@ -4629,7 +5616,7 @@ function formatThread(thread, activeId) {
4629
5616
  hour: "2-digit",
4630
5617
  minute: "2-digit"
4631
5618
  });
4632
- return `${active} ${chalk5.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk5.dim(dateLabel)}`;
5619
+ return `${active} ${chalk8.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk8.dim(dateLabel)}`;
4633
5620
  }
4634
5621
  function actionFromPayload(payload) {
4635
5622
  const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
@@ -4660,14 +5647,14 @@ function formatJson(value) {
4660
5647
  }
4661
5648
  function riskLabel(risk) {
4662
5649
  const safe = sanitizeTerminalText(risk);
4663
- const color = safe === "low" ? chalk5.green : safe === "high" ? chalk5.red : chalk5.yellow;
5650
+ const color = safe === "low" ? chalk8.green : safe === "high" ? chalk8.red : chalk8.yellow;
4664
5651
  return color(`${safe} risk`);
4665
5652
  }
4666
5653
  function diffStyle(line) {
4667
- if (line.startsWith("+++") || line.startsWith("---")) return chalk5.dim;
4668
- if (line.startsWith("+")) return chalk5.green;
4669
- if (line.startsWith("-")) return chalk5.red;
4670
- if (line.startsWith("@@")) return chalk5.cyan;
5654
+ if (line.startsWith("+++") || line.startsWith("---")) return chalk8.dim;
5655
+ if (line.startsWith("+")) return chalk8.green;
5656
+ if (line.startsWith("-")) return chalk8.red;
5657
+ if (line.startsWith("@@")) return chalk8.cyan;
4671
5658
  return (text2) => text2;
4672
5659
  }
4673
5660
  function approvalQuestion(action) {
@@ -4805,7 +5792,7 @@ var TunedTensorAgentSession = class {
4805
5792
  wrapped.forEach((part, partIndex) => {
4806
5793
  if (partIndex > 0) this.io.write("\n");
4807
5794
  if (!this.lineOpen || partIndex > 0) {
4808
- const marker = this.blockStart ? `${accent3("\u25CF")} ` : GUTTER;
5795
+ const marker = this.blockStart ? `${accent5("\u25CF")} ` : GUTTER;
4809
5796
  if (this.blockStart && this.wroteInTurn) this.io.write("\n");
4810
5797
  this.blockStart = false;
4811
5798
  this.wroteInTurn = true;
@@ -4822,11 +5809,11 @@ var TunedTensorAgentSession = class {
4822
5809
  renderAction(action) {
4823
5810
  this.flushResponse();
4824
5811
  this.endOpenLine();
4825
- const bar = chalk5.yellow("\u2502");
5812
+ const bar = chalk8.yellow("\u2502");
4826
5813
  const out = [
4827
5814
  "",
4828
- `${GUTTER}${chalk5.yellow.bold("\u25C6 Approval needed")} ${chalk5.dim(action.id.slice(0, 8))}`,
4829
- `${GUTTER}${bar} ${chalk5.bold(sanitizeTerminalText(action.title))} ${chalk5.dim("\xB7")} ${riskLabel(action.risk)}`
5815
+ `${GUTTER}${chalk8.yellow.bold("\u25C6 Approval needed")} ${chalk8.dim(action.id.slice(0, 8))}`,
5816
+ `${GUTTER}${bar} ${chalk8.bold(sanitizeTerminalText(action.title))} ${chalk8.dim("\xB7")} ${riskLabel(action.risk)}`
4830
5817
  ];
4831
5818
  const width = this.wrapWidth();
4832
5819
  const push = (text2, style = (line) => line) => {
@@ -4835,7 +5822,7 @@ var TunedTensorAgentSession = class {
4835
5822
  for (const part of parts) out.push(`${GUTTER}${bar} ${style(part)}`);
4836
5823
  }
4837
5824
  };
4838
- if (action.summary) push(action.summary, chalk5.dim);
5825
+ if (action.summary) push(action.summary, chalk8.dim);
4839
5826
  if (action.operation === "update_local_spec") {
4840
5827
  const preview = action.preview;
4841
5828
  out.push(`${GUTTER}${bar}`);
@@ -4846,7 +5833,7 @@ var TunedTensorAgentSession = class {
4846
5833
  out.push(`${GUTTER}${bar} ${style(index === 0 ? part : ` ${part}`)}`);
4847
5834
  });
4848
5835
  }
4849
- for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk5.yellow);
5836
+ for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk8.yellow);
4850
5837
  } else {
4851
5838
  const request2 = [action.method, action.path].filter(Boolean).join(" ");
4852
5839
  const technical = {
@@ -4856,14 +5843,14 @@ var TunedTensorAgentSession = class {
4856
5843
  preview: action.preview ?? null
4857
5844
  };
4858
5845
  out.push(`${GUTTER}${bar}`);
4859
- push(formatJson(technical), chalk5.dim);
5846
+ push(formatJson(technical), chalk8.dim);
4860
5847
  }
4861
5848
  this.io.write(`${out.join("\n")}
4862
5849
  `);
4863
5850
  }
4864
5851
  writePendingHint() {
4865
5852
  this.io.write(
4866
- chalk5.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
5853
+ chalk8.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
4867
5854
  `)
4868
5855
  );
4869
5856
  }
@@ -4880,14 +5867,14 @@ var TunedTensorAgentSession = class {
4880
5867
  const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
4881
5868
  if (!delta) return;
4882
5869
  if (this.reasoningActive) return;
4883
- this.writeStatus(chalk5.dim("\u25CB Thinking\u2026"), { transient: true });
5870
+ this.writeStatus(`${mascotMark()} ${chalk8.dim("Thinking\u2026")}`, { transient: true });
4884
5871
  this.reasoningActive = true;
4885
5872
  return;
4886
5873
  }
4887
5874
  if (event.type === "tool_call") {
4888
5875
  this.reasoningActive = false;
4889
5876
  const label = toolLabel(payload);
4890
- this.writeStatus(chalk5.dim(`\u25CB ${label}\u2026`), { transient: true });
5877
+ this.writeStatus(chalk8.dim(`\u25CB ${label}\u2026`), { transient: true });
4891
5878
  const id = stringValue(payload.toolUseId);
4892
5879
  if (id) this.pendingToolLabels.set(id, label);
4893
5880
  else this.pendingUnkeyedToolLabel = label;
@@ -4900,7 +5887,7 @@ var TunedTensorAgentSession = class {
4900
5887
  if (id) this.pendingToolLabels.delete(id);
4901
5888
  else this.pendingUnkeyedToolLabel = null;
4902
5889
  this.writeStatus(
4903
- `${failed ? errorMark2() : successMark2()} ${chalk5.dim(failed ? `${label} failed` : label)}`
5890
+ `${failed ? errorMark2() : successMark2()} ${chalk8.dim(failed ? `${label} failed` : label)}`
4904
5891
  );
4905
5892
  return;
4906
5893
  }
@@ -4912,7 +5899,7 @@ var TunedTensorAgentSession = class {
4912
5899
  return;
4913
5900
  }
4914
5901
  if (event.type === "action_started") {
4915
- this.writeStatus(chalk5.dim("\u25CB Running approved action\u2026"));
5902
+ this.writeStatus(chalk8.dim("\u25CB Running approved action\u2026"));
4916
5903
  return;
4917
5904
  }
4918
5905
  if (event.type === "action_result") {
@@ -4959,7 +5946,7 @@ var TunedTensorAgentSession = class {
4959
5946
  }
4960
5947
  }
4961
5948
  if (controller.signal.aborted || result2.status === "cancelled") {
4962
- this.io.write(chalk5.dim(`${GUTTER}Response stopped.
5949
+ this.io.write(chalk8.dim(`${GUTTER}Response stopped.
4963
5950
  `));
4964
5951
  return null;
4965
5952
  }
@@ -4968,7 +5955,7 @@ var TunedTensorAgentSession = class {
4968
5955
  this.flushResponse();
4969
5956
  this.endOpenLine();
4970
5957
  if (controller.signal.aborted) {
4971
- this.io.write(chalk5.dim(`${GUTTER}Response stopped.
5958
+ this.io.write(chalk8.dim(`${GUTTER}Response stopped.
4972
5959
  `));
4973
5960
  return null;
4974
5961
  }
@@ -5041,13 +6028,13 @@ var TunedTensorAgentSession = class {
5041
6028
  }
5042
6029
  for (const action of actions) {
5043
6030
  if (!this.pendingActions.has(action.id)) continue;
5044
- const label = actions.length > 1 ? `${approvalQuestion(action)} ${chalk5.dim(`(${action.title})`)}` : approvalQuestion(action);
5045
- const decision = await this.prompter(`${GUTTER}${chalk5.yellow("?")} ${chalk5.bold(label)}`);
6031
+ const label = actions.length > 1 ? `${approvalQuestion(action)} ${chalk8.dim(`(${action.title})`)}` : approvalQuestion(action);
6032
+ const decision = await this.prompter(`${GUTTER}${chalk8.yellow("?")} ${chalk8.bold(label)}`);
5046
6033
  try {
5047
6034
  if (decision === "approve") await this.approve(action.id, context);
5048
6035
  else if (decision === "reject") await this.reject(action.id);
5049
6036
  else {
5050
- this.io.write(chalk5.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
6037
+ this.io.write(chalk8.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
5051
6038
  `));
5052
6039
  }
5053
6040
  } catch (error) {
@@ -5120,7 +6107,7 @@ var TunedTensorAgentSession = class {
5120
6107
  if (args2.length > 0) throw new Error("/threads does not accept arguments.");
5121
6108
  const threads = await this.options.client.listThreads();
5122
6109
  if (threads.length === 0) {
5123
- this.options.io.write(chalk5.dim("No conversations yet.\n"));
6110
+ this.options.io.write(chalk8.dim("No conversations yet.\n"));
5124
6111
  return "continue";
5125
6112
  }
5126
6113
  this.options.io.write(
@@ -5158,10 +6145,10 @@ var TunedTensorAgentSession = class {
5158
6145
  if (args2.length > 0) throw new Error("/status does not accept arguments.");
5159
6146
  this.options.io.write(
5160
6147
  this.thread ? `${formatThread(this.thread, this.thread.id)}
5161
- ` : chalk5.dim("New conversation; send a message to create it.\n")
6148
+ ` : chalk8.dim("New conversation; send a message to create it.\n")
5162
6149
  );
5163
6150
  this.options.io.write(
5164
- chalk5.dim(`${this.pendingActions.size} pending approval(s).
6151
+ chalk8.dim(`${this.pendingActions.size} pending approval(s).
5165
6152
  `)
5166
6153
  );
5167
6154
  return "continue";
@@ -5205,10 +6192,10 @@ import {
5205
6192
  } from "fs/promises";
5206
6193
  import {
5207
6194
  basename as basename3,
5208
- dirname as dirname4,
6195
+ dirname as dirname5,
5209
6196
  isAbsolute as isAbsolute4,
5210
- join as join8,
5211
- resolve as resolve4
6197
+ join as join9,
6198
+ resolve as resolve5
5212
6199
  } from "path";
5213
6200
 
5214
6201
  // src/project-spec.ts
@@ -5227,6 +6214,7 @@ var LOCAL_ONLY_SPEC_KEYS = [
5227
6214
  "dataset_prebuilt",
5228
6215
  "engine",
5229
6216
  "foundation",
6217
+ "decision",
5230
6218
  "evaluation",
5231
6219
  "runtime",
5232
6220
  "pipeline"
@@ -5268,7 +6256,7 @@ function projectCloudSpec(raw) {
5268
6256
  return projectSpec(raw, CLOUD_SPEC_KEY_SET, canonicalizeHostedSpecBaseModel);
5269
6257
  }
5270
6258
  function projectLocalSpec(raw) {
5271
- return projectSpec(raw, LOCAL_SPEC_KEY_SET, canonicalizeSpecBaseModel);
6259
+ return projectSpec(raw, LOCAL_SPEC_KEY_SET, raw.engine === "decision" ? (body) => body : canonicalizeSpecBaseModel);
5272
6260
  }
5273
6261
  function hasLocalOnlySpecFields(raw) {
5274
6262
  return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
@@ -5383,6 +6371,7 @@ function renderInit(value) {
5383
6371
  ["Path", text(value.path)],
5384
6372
  ["Name", text(value.name)],
5385
6373
  ["ID", text(value.id)],
6374
+ ["Engine", text(value.engine)],
5386
6375
  ["Base model", text(value.base_model)],
5387
6376
  ["Config", text(value.config_path)]
5388
6377
  ]);
@@ -5391,6 +6380,7 @@ function renderValidate(value) {
5391
6380
  printSuccess("Local behavior spec is valid");
5392
6381
  printDetail([
5393
6382
  ["Input", text(value.input_path)],
6383
+ ["Engine", text(value.engine)],
5394
6384
  ["Config", text(value.config_path)],
5395
6385
  ["Spec", text(value.behavior_spec_id)],
5396
6386
  ["Base model", text(value.base_model)],
@@ -5867,10 +6857,10 @@ function positionalSpecArgument(args2, start, defaultPath) {
5867
6857
  function selectedSpecArgument(args2, cwd) {
5868
6858
  const command = args2[0];
5869
6859
  if (["doctor", "validate", "run"].includes(command ?? "")) {
5870
- return positionalSpecArgument(args2, 1, join8(cwd, DEFAULT_SPEC_NAME));
6860
+ return positionalSpecArgument(args2, 1, join9(cwd, DEFAULT_SPEC_NAME));
5871
6861
  }
5872
6862
  if (command === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
5873
- return positionalSpecArgument(args2, 2, join8(cwd, DEFAULT_SPEC_NAME));
6863
+ return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
5874
6864
  }
5875
6865
  const serves = command === "serve" || command === "models" && args2[1] === "serve";
5876
6866
  if (!serves) return null;
@@ -5883,7 +6873,7 @@ async function pathIsFile(path) {
5883
6873
  }
5884
6874
  function resolveFromCwd(path, cwd) {
5885
6875
  if (isAbsolute4(path)) return path;
5886
- return resolve4(cwd, path);
6876
+ return resolve5(cwd, path);
5887
6877
  }
5888
6878
  async function projectSpecArguments(originalArgs, cwd) {
5889
6879
  const args2 = [...originalArgs];
@@ -5946,8 +6936,8 @@ async function projectSpecArguments(originalArgs, cwd) {
5946
6936
  }
5947
6937
  };
5948
6938
  }
5949
- const temporaryPath = join8(
5950
- dirname4(sourcePath),
6939
+ const temporaryPath = join9(
6940
+ dirname5(sourcePath),
5951
6941
  `.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
5952
6942
  );
5953
6943
  await writeFile4(
@@ -6109,7 +7099,7 @@ function setupFailure(originalArgs, error) {
6109
7099
  };
6110
7100
  }
6111
7101
  async function runLocalCommand(args2, options = {}) {
6112
- const cwd = resolve4(options.cwd ?? process.cwd());
7102
+ const cwd = resolve5(options.cwd ?? process.cwd());
6113
7103
  let projected;
6114
7104
  try {
6115
7105
  const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
@@ -6155,7 +7145,7 @@ async function executeLocalCommand(args2, options = {}) {
6155
7145
  }
6156
7146
 
6157
7147
  // src/commands/local.ts
6158
- import chalk6 from "chalk";
7148
+ import chalk9 from "chalk";
6159
7149
 
6160
7150
  // src/passthrough.ts
6161
7151
  function extractPassthroughOptions(args2, root) {
@@ -6210,7 +7200,7 @@ function registerLocalCommands(program, runtime) {
6210
7200
  const runPassthrough = async (forwarded) => {
6211
7201
  const root = program.opts();
6212
7202
  const passthrough = extractPassthroughOptions(forwarded, root);
6213
- if (!passthrough.color) chalk6.level = 0;
7203
+ if (!passthrough.color) chalk9.level = 0;
6214
7204
  const result2 = await runtime.invokeLocal(
6215
7205
  passthrough.args.length > 0 ? passthrough.args : ["--help"],
6216
7206
  {
@@ -6309,11 +7299,11 @@ function registerAgentCommands(parent, options) {
6309
7299
 
6310
7300
  // src/commands/pipeline.ts
6311
7301
  import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
6312
- import { dirname as dirname7, join as join11, resolve as resolve6 } from "path";
7302
+ import { dirname as dirname9, join as join13, resolve as resolve8 } from "path";
6313
7303
 
6314
7304
  // src/local-runtime/project-migration.ts
6315
7305
  import { lstat as lstat5, readFile as readFile5, rename as rename3, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
6316
- import { dirname as dirname5, join as join9 } from "path";
7306
+ import { dirname as dirname6, join as join10 } from "path";
6317
7307
  async function migrateProjectWorkflow(specPath) {
6318
7308
  const info = await lstat5(specPath);
6319
7309
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
@@ -6324,7 +7314,7 @@ async function migrateProjectWorkflow(specPath) {
6324
7314
  const files = [];
6325
7315
  const sources = /* @__PURE__ */ new Map([[specPath, original]]);
6326
7316
  for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
6327
- const path = join9(dirname5(specPath), name);
7317
+ const path = join10(dirname6(specPath), name);
6328
7318
  const entry = await lstat5(path).catch((error) => {
6329
7319
  if (error.code === "ENOENT") return void 0;
6330
7320
  throw error;
@@ -6392,7 +7382,7 @@ async function migrateProjectWorkflow(specPath) {
6392
7382
  import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
6393
7383
  import { createReadStream } from "fs";
6394
7384
  import { chmod as chmod2, lstat as lstat6, mkdir as mkdir4, open as open3, readdir as readdir4, readFile as readFile6, rename as rename4, rm as rm3 } from "fs/promises";
6395
- import { basename as basename4, dirname as dirname6, join as join10, resolve as resolve5 } from "path";
7385
+ import { basename as basename4, dirname as dirname7, join as join11, resolve as resolve6 } from "path";
6396
7386
  var INSTRUCTION_CORPUS_VERSION = 1;
6397
7387
  function isRef(value) {
6398
7388
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
@@ -6421,7 +7411,7 @@ async function hashPath(path) {
6421
7411
  if (info.isDirectory()) {
6422
7412
  for (const name of (await readdir4(path)).sort()) {
6423
7413
  hash.update(name);
6424
- hash.update(await hashPath(join10(path, name)));
7414
+ hash.update(await hashPath(join11(path, name)));
6425
7415
  }
6426
7416
  return hash.digest("hex");
6427
7417
  }
@@ -6444,7 +7434,7 @@ async function makePrivateTree(path) {
6444
7434
  if (info.isDirectory()) {
6445
7435
  await chmod2(path, 448);
6446
7436
  for (const name of await readdir4(path)) {
6447
- await makePrivateTree(join10(path, name));
7437
+ await makePrivateTree(join11(path, name));
6448
7438
  }
6449
7439
  return;
6450
7440
  }
@@ -6473,7 +7463,7 @@ async function readRequiredJsonObject(path, description) {
6473
7463
  return parsed;
6474
7464
  }
6475
7465
  async function writePrivateTextAtomic(path, content) {
6476
- const temporary = join10(dirname6(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
7466
+ const temporary = join11(dirname7(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
6477
7467
  const file = await open3(temporary, "wx", 384);
6478
7468
  let closed = false;
6479
7469
  try {
@@ -6588,8 +7578,8 @@ ${result2.stderr}` : ""}`
6588
7578
  async function runFoundationPipeline(args2) {
6589
7579
  assertFoundationPlanSupported(args2.spec, args2.plan);
6590
7580
  assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
6591
- const outputDir = resolve5(
6592
- args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join10(resolve5(dirname6(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join10(dirname6(resolve5(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
7581
+ const outputDir = resolve6(
7582
+ args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join11(resolve6(dirname7(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join11(dirname7(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
6593
7583
  );
6594
7584
  if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
6595
7585
  throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
@@ -6614,7 +7604,7 @@ async function runFoundationPipeline(args2) {
6614
7604
  await mkdir4(outputDir, { recursive: true, mode: 448 });
6615
7605
  }
6616
7606
  await makePrivateTree(outputDir);
6617
- const workflowPath = join10(outputDir, "resolved-workflow.json");
7607
+ const workflowPath = join11(outputDir, "resolved-workflow.json");
6618
7608
  const workflow = { spec: args2.spec, plan: args2.plan };
6619
7609
  if (outputExists) {
6620
7610
  const previous = await readFile6(workflowPath, "utf8").catch((error) => {
@@ -6647,16 +7637,16 @@ async function runFoundationPipeline(args2) {
6647
7637
  throw new Error(`Missing ${kind} artifact.`);
6648
7638
  };
6649
7639
  for (const step of args2.plan.steps) {
6650
- const stepDir = join10(outputDir, step.id);
6651
- const output = join10(stepDir, "output");
7640
+ const stepDir = join11(outputDir, step.id);
7641
+ const output = join11(stepDir, "output");
6652
7642
  await mkdir4(stepDir, { recursive: true, mode: 448 });
6653
7643
  await chmod2(stepDir, 448);
6654
7644
  await mkdir4(output, { recursive: true, mode: 448 });
6655
7645
  await chmod2(output, 448);
6656
- const configPath2 = join10(stepDir, "config.json");
6657
- const logPath = join10(stepDir, "step.log");
6658
- const metricsPath = join10(output, "metrics.json");
6659
- const completionPath = join10(stepDir, "completion.json");
7646
+ const configPath2 = join11(stepDir, "config.json");
7647
+ const logPath = join11(stepDir, "step.log");
7648
+ const metricsPath = join11(output, "metrics.json");
7649
+ const completionPath = join11(stepDir, "completion.json");
6660
7650
  const fields = stepConfig(step);
6661
7651
  let entrypoint;
6662
7652
  let config;
@@ -6677,7 +7667,7 @@ async function runFoundationPipeline(args2) {
6677
7667
  config = {
6678
7668
  tokenizer_dir: tokenizerDir,
6679
7669
  output_dir: output,
6680
- work_dir: join10(stepDir, "recovery"),
7670
+ work_dir: join11(stepDir, "recovery"),
6681
7671
  depth: fields.depth ?? hp.depth,
6682
7672
  steps: fields.steps ?? hp.pretrain_steps,
6683
7673
  batch_size: fields.batchSize ?? hp.batch_size,
@@ -6782,15 +7772,15 @@ async function runFoundationPipeline(args2) {
6782
7772
  const artifacts = {};
6783
7773
  const record4 = {};
6784
7774
  if (step.uses === "tokenize") {
6785
- const tokenizerPath = join10(output, "tokenizer.json");
7775
+ const tokenizerPath = join11(output, "tokenizer.json");
6786
7776
  await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
6787
7777
  await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
6788
7778
  record4.tokenizer = output;
6789
7779
  tokenizerDir = output;
6790
7780
  artifacts.tokenizer = await artifactFor(output);
6791
7781
  } else if (step.uses === "pretrain" || step.uses === "finetune" || step.uses === "rl") {
6792
- const modelPath = join10(output, "model.safetensors");
6793
- const modelConfigPath = join10(output, "config.json");
7782
+ const modelPath = join11(output, "model.safetensors");
7783
+ const modelConfigPath = join11(output, "config.json");
6794
7784
  await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
6795
7785
  const modelConfig = await readRequiredJsonObject(
6796
7786
  modelConfigPath,
@@ -6805,7 +7795,7 @@ async function runFoundationPipeline(args2) {
6805
7795
  modelDir = output;
6806
7796
  artifacts.model = await artifactFor(output);
6807
7797
  } else if (step.uses === "evaluate") {
6808
- const reportPath2 = join10(output, "report.json");
7798
+ const reportPath2 = join11(output, "report.json");
6809
7799
  const stepReport = await readRequiredJsonObject(
6810
7800
  reportPath2,
6811
7801
  `Foundation step "${step.id}" report.json`
@@ -6845,7 +7835,7 @@ async function runFoundationPipeline(args2) {
6845
7835
  spec: args2.specPath,
6846
7836
  steps
6847
7837
  };
6848
- const reportPath = join10(outputDir, "report.json");
7838
+ const reportPath = join11(outputDir, "report.json");
6849
7839
  await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
6850
7840
  `);
6851
7841
  return {
@@ -6856,6 +7846,334 @@ async function runFoundationPipeline(args2) {
6856
7846
  };
6857
7847
  }
6858
7848
 
7849
+ // src/local-runtime/decision-runner.ts
7850
+ import { randomUUID as randomUUID9 } from "crypto";
7851
+ import { lstat as lstat7, mkdir as mkdir5, readFile as readFile7, rename as rename5, rm as rm4, writeFile as writeFile6 } from "fs/promises";
7852
+ import { dirname as dirname8, join as join12, resolve as resolve7 } from "path";
7853
+ var PROBABILITY_FLOOR = 1e-6;
7854
+ function assertDecisionPlanSupported(plan) {
7855
+ for (const step of plan.steps) {
7856
+ if (step.uses === "evaluate" && step.with.evaluator !== "behavior") {
7857
+ throw new Error(`Decision runner evaluates with the behavior evaluator; step ${step.id} uses ${step.with.evaluator}.`);
7858
+ }
7859
+ if (!["train", "evaluate", "compare"].includes(step.uses)) {
7860
+ throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
7861
+ }
7862
+ }
7863
+ }
7864
+ function decisionQuestion(spec) {
7865
+ return { ...spec.decision, instructions: buildSystemMessage(spec) };
7866
+ }
7867
+ function canonicalLabel(labels, output) {
7868
+ const normalized = output.trim().toLowerCase();
7869
+ const label = labels.find((candidate) => candidate.trim().toLowerCase() === normalized);
7870
+ if (!label) throw new Error(`Example output ${JSON.stringify(output)} is not a decision label (${labels.join(", ")}).`);
7871
+ return label;
7872
+ }
7873
+ async function readPredictions(path, ids, labels) {
7874
+ const rows2 = (await readFile7(path, "utf8")).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
7875
+ if (rows2.length !== ids.length || new Set(rows2.map((row) => row.id)).size !== rows2.length) {
7876
+ throw new Error(`Decision predictions must contain exactly one row per evaluation ID: ${path}`);
7877
+ }
7878
+ for (const row of rows2) validatePrediction(row, labels);
7879
+ const byId = new Map(rows2.map((row) => [row.id, row]));
7880
+ return ids.map((id) => {
7881
+ const row = byId.get(id);
7882
+ if (!row) throw new Error(`Decision predictions are missing row ${id}: ${path}`);
7883
+ if (!labels.includes(row.prediction)) throw new Error(`Decision prediction for ${id} is not a label: ${row.prediction}`);
7884
+ return row;
7885
+ });
7886
+ }
7887
+ function validatePrediction(row, labels) {
7888
+ const values = row?.probabilities;
7889
+ if (!values || Object.keys(values).length !== labels.length || labels.some((label) => !Object.hasOwn(values, label) || !Number.isFinite(values[label]) || values[label] < 0 || values[label] > 1)) {
7890
+ throw new Error("Decision probabilities must contain a finite probability for every label.");
7891
+ }
7892
+ const sum = labels.reduce((total, label) => total + values[label], 0);
7893
+ const maximum = Math.max(...labels.map((label) => values[label]));
7894
+ const sumTolerance = labels.length * 5e-5 + 1e-8;
7895
+ const tolerance = 2e-5;
7896
+ if (Math.abs(sum - 1) > sumTolerance || !labels.includes(row.prediction) || Math.abs(values[row.prediction] - maximum) > tolerance || !Number.isFinite(row.confidence) || Math.abs(row.confidence - maximum) > tolerance || !Number.isFinite(row.latency_ms) || row.latency_ms < 0) {
7897
+ throw new Error("Decision prediction must have normalized probabilities, an argmax label, matching confidence and nonnegative latency.");
7898
+ }
7899
+ }
7900
+ function scoreDecisionPredictions(args2) {
7901
+ if (args2.predictions.length !== args2.examples.length) {
7902
+ throw new Error("Decision prediction count must match the evaluation examples.");
7903
+ }
7904
+ for (const prediction of args2.predictions) validatePrediction(prediction, args2.labels);
7905
+ const perLabel = Object.fromEntries(
7906
+ args2.labels.map((label) => [label, { support: 0, correct: 0, recall: 0 }])
7907
+ );
7908
+ let logLoss = 0;
7909
+ let brier = 0;
7910
+ let confidence = 0;
7911
+ const results = args2.examples.map((example, index) => {
7912
+ const prediction = args2.predictions[index];
7913
+ const passed = prediction.prediction === example.output;
7914
+ const expectedProbability = prediction.probabilities[example.output] ?? 0;
7915
+ logLoss -= Math.log(Math.max(expectedProbability, PROBABILITY_FLOOR));
7916
+ brier += args2.labels.reduce((sum, label) => {
7917
+ const target = label === example.output ? 1 : 0;
7918
+ return sum + ((prediction.probabilities[label] ?? 0) - target) ** 2;
7919
+ }, 0);
7920
+ confidence += prediction.confidence;
7921
+ const entry = perLabel[example.output];
7922
+ entry.support += 1;
7923
+ if (passed) entry.correct += 1;
7924
+ return {
7925
+ prompt: example.input,
7926
+ expected: example.output,
7927
+ actual: prediction.prediction,
7928
+ passed,
7929
+ score: passed ? 1 : 0,
7930
+ reasoning: `p(expected)=${expectedProbability.toFixed(4)}; confidence=${prediction.confidence.toFixed(4)}`,
7931
+ latency_ms: Math.max(0, Math.round(prediction.latency_ms)),
7932
+ scored_by: "exact_match"
7933
+ };
7934
+ });
7935
+ for (const entry of Object.values(perLabel)) entry.recall = entry.support ? entry.correct / entry.support : 0;
7936
+ const total = results.length;
7937
+ const mean = (value) => total ? value / total : 0;
7938
+ const accuracy = mean(results.filter((result2) => result2.passed).length);
7939
+ const report = evalReportSchema.parse({
7940
+ kind: args2.kind,
7941
+ model_id: args2.modelId,
7942
+ total,
7943
+ eval_examples_total: total,
7944
+ eval_examples_used: total,
7945
+ eval_truncated: false,
7946
+ eval_split: "spec_holdout",
7947
+ eval_sample_seed: args2.sampleSeed,
7948
+ avg_score: accuracy,
7949
+ pass_rate: accuracy,
7950
+ exact_match_rate: accuracy,
7951
+ avg_latency_ms: Math.round(mean(results.reduce((sum, result2) => sum + result2.latency_ms, 0))),
7952
+ results,
7953
+ artifact_uri: fileUri(args2.outputPath),
7954
+ scoring_method: "exact_match",
7955
+ scoring_mode: "exact_match"
7956
+ });
7957
+ return {
7958
+ report,
7959
+ metrics: {
7960
+ accuracy,
7961
+ log_loss: mean(logLoss),
7962
+ brier: mean(brier),
7963
+ mean_confidence: mean(confidence),
7964
+ per_label: perLabel
7965
+ }
7966
+ };
7967
+ }
7968
+ async function defaultDecisionSpawn(args2) {
7969
+ const launched = buildDecisionPythonCommand(args2.entrypoint, ["--config", args2.configPath]);
7970
+ const result2 = await runLoggedProcess({
7971
+ command: launched.command,
7972
+ commandArgs: launched.commandArgs,
7973
+ env: withDecisionPythonEnvironment(process.env),
7974
+ logPath: args2.logPath,
7975
+ stage: args2.stepId,
7976
+ appendLog: true,
7977
+ onLine: (line, stream) => {
7978
+ if (stream !== "stdout" || !args2.onProgress) return;
7979
+ try {
7980
+ const event = JSON.parse(line);
7981
+ if (event.event === "step") {
7982
+ args2.onProgress(`${args2.stepId}: step ${event.step}/${event.total_steps} (epoch ${event.epoch}) loss ${event.loss}`);
7983
+ }
7984
+ } catch {
7985
+ }
7986
+ }
7987
+ });
7988
+ if (result2.exitCode !== 0) {
7989
+ throw new Error(
7990
+ `Decision step "${args2.stepId}" failed (exit ${result2.exitCode}). Log: ${args2.logPath}${result2.stderr ? `
7991
+ ${result2.stderr}` : ""}`
7992
+ );
7993
+ }
7994
+ }
7995
+ async function requireRegularNonemptyFile2(path, description) {
7996
+ const info = await lstat7(path).catch(() => null);
7997
+ if (!info || info.isSymbolicLink() || !info.isFile() || info.size === 0) {
7998
+ throw new Error(`${description} must be a non-empty regular file: ${path}`);
7999
+ }
8000
+ }
8001
+ async function readJsonObject2(path, description) {
8002
+ const parsed = JSON.parse(await readFile7(path, "utf8").catch((error) => {
8003
+ throw new Error(`${description} is missing or unreadable: ${path}`, { cause: error });
8004
+ }));
8005
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8006
+ throw new Error(`${description} must be a JSON object: ${path}`);
8007
+ }
8008
+ return parsed;
8009
+ }
8010
+ async function writePrivateAtomic(path, content) {
8011
+ await mkdir5(dirname8(path), { recursive: true, mode: 448 });
8012
+ const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
8013
+ try {
8014
+ await writeFile6(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
8015
+ await rename5(temporary, path);
8016
+ } finally {
8017
+ await rm4(temporary, { force: true });
8018
+ }
8019
+ }
8020
+ async function writePrivateJson2(path, value) {
8021
+ await writePrivateAtomic(path, `${JSON.stringify(value, null, 2)}
8022
+ `);
8023
+ }
8024
+ function producer(reference) {
8025
+ return reference.slice(0, reference.lastIndexOf("."));
8026
+ }
8027
+ function jsonl(rows2) {
8028
+ return rows2.map((row) => JSON.stringify(row)).join("\n") + "\n";
8029
+ }
8030
+ async function runDecisionPipeline(args2) {
8031
+ assertDecisionPlanSupported(args2.plan);
8032
+ const model = resolveDecisionModel(args2.spec.base_model);
8033
+ const specDir = dirname8(resolve7(args2.specPath));
8034
+ 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())));
8035
+ if (await lstat7(outputDir).catch(() => null)) {
8036
+ throw new Error(`Decision output directory already exists: ${outputDir}`);
8037
+ }
8038
+ await mkdir5(outputDir, { recursive: true, mode: 448 });
8039
+ const labels = decisionLabels(args2.spec.decision);
8040
+ const question = decisionQuestion(args2.spec);
8041
+ const examples = args2.spec.examples.map((example) => ({
8042
+ input: example.input,
8043
+ output: canonicalLabel(labels, example.output)
8044
+ }));
8045
+ const seed = deriveSampleSeed(args2.spec.id ?? args2.spec.name);
8046
+ const split = splitSpecExamples(examples, seed);
8047
+ if (!split.train.length || !split.holdout.length) {
8048
+ throw new Error("Decision runs need at least one training and one held-out example.");
8049
+ }
8050
+ const hp = args2.spec.hyperparameters ?? {};
8051
+ const device = hp.device ?? "auto";
8052
+ const evalIds = split.holdout.map((_, index) => `eval-${index}`);
8053
+ const dataDir = join12(outputDir, "data");
8054
+ await mkdir5(dataDir, { recursive: true, mode: 448 });
8055
+ const trainPath = join12(dataDir, "train.jsonl");
8056
+ const evalInputsPath = join12(dataDir, "eval-inputs.jsonl");
8057
+ await writePrivateAtomic(trainPath, jsonl(split.train));
8058
+ await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
8059
+ await writePrivateJson2(join12(outputDir, "resolved-workflow.json"), {
8060
+ spec: args2.spec,
8061
+ plan: args2.plan,
8062
+ model: { id: model.id, revision: model.revision },
8063
+ question,
8064
+ split: { seed, train: split.train.length, eval: split.holdout.length }
8065
+ });
8066
+ const spawnStep = args2.spawnStep ?? defaultDecisionSpawn;
8067
+ const producedModels = /* @__PURE__ */ new Map();
8068
+ const reports = /* @__PURE__ */ new Map();
8069
+ const steps = [];
8070
+ let tunedModelDir;
8071
+ for (const step of args2.plan.steps) {
8072
+ const stepDir = join12(outputDir, step.id);
8073
+ await mkdir5(stepDir, { recursive: true, mode: 448 });
8074
+ const configPath2 = join12(stepDir, "config.json");
8075
+ const logPath = join12(stepDir, "step.log");
8076
+ const metricsPath = join12(stepDir, "metrics.json");
8077
+ if (step.uses === "train") {
8078
+ const modelDir = join12(stepDir, "model");
8079
+ args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
8080
+ await writePrivateJson2(configPath2, {
8081
+ model: model.id,
8082
+ revision: model.revision,
8083
+ question,
8084
+ train_path: trainPath,
8085
+ output_dir: modelDir,
8086
+ metrics_path: metricsPath,
8087
+ epochs: hp.n_epochs ?? model.defaultEpochs,
8088
+ learning_rate: hp.learning_rate ?? model.defaultLearningRate,
8089
+ batch_size: hp.batch_size ?? model.defaultBatchSize,
8090
+ freeze_encoder: hp.freeze_encoder ?? false,
8091
+ shuffle_options: hp.shuffle_options ?? true,
8092
+ seed: hp.seed ?? 0,
8093
+ device
8094
+ });
8095
+ await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
8096
+ const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
8097
+ if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
8098
+ await requireRegularNonemptyFile2(join12(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
8099
+ await readJsonObject2(join12(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
8100
+ producedModels.set(step.id, modelDir);
8101
+ tunedModelDir = modelDir;
8102
+ steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
8103
+ continue;
8104
+ }
8105
+ if (step.uses === "evaluate") {
8106
+ const source = step.with.model;
8107
+ const tuned = source !== "base";
8108
+ const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
8109
+ if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
8110
+ const predictionsPath = join12(stepDir, "predictions.jsonl");
8111
+ args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
8112
+ await writePrivateJson2(configPath2, {
8113
+ model: modelDir ?? model.id,
8114
+ ...tuned ? {} : { revision: model.revision },
8115
+ question,
8116
+ inputs_path: evalInputsPath,
8117
+ output_path: predictionsPath,
8118
+ metrics_path: metricsPath,
8119
+ device
8120
+ });
8121
+ await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
8122
+ const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
8123
+ if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
8124
+ const reportPath2 = join12(stepDir, "report.json");
8125
+ const scored = scoreDecisionPredictions({
8126
+ kind: tuned ? "candidate" : "baseline",
8127
+ modelId: modelDir ? fileUri(modelDir) : model.id,
8128
+ labels,
8129
+ examples: split.holdout,
8130
+ predictions: await readPredictions(predictionsPath, evalIds, labels),
8131
+ outputPath: reportPath2,
8132
+ sampleSeed: seed
8133
+ });
8134
+ await writePrivateJson2(reportPath2, { ...scored.report, decision_metrics: scored.metrics });
8135
+ reports.set(step.id, scored);
8136
+ steps.push({ id: step.id, uses: step.uses, model: modelDir ?? model.id, report: scored.report, decision_metrics: scored.metrics });
8137
+ continue;
8138
+ }
8139
+ if (step.uses !== "compare") throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
8140
+ const before = reports.get(producer(step.with.before.from));
8141
+ const after = reports.get(producer(step.with.after.from));
8142
+ if (!before || !after) throw new Error(`Step ${step.id} needs both evaluation reports.`);
8143
+ const comparison = {
8144
+ ...compareEvalReports(before.report, after.report),
8145
+ log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
8146
+ brier_delta: after.metrics.brier - before.metrics.brier
8147
+ };
8148
+ await writePrivateJson2(join12(stepDir, "comparison.json"), comparison);
8149
+ steps.push({ id: step.id, uses: step.uses, comparison });
8150
+ }
8151
+ const reportPath = join12(outputDir, "report.json");
8152
+ const report = {
8153
+ status: "succeeded",
8154
+ engine: "decision",
8155
+ ...args2.plan.name ? { name: args2.plan.name } : {},
8156
+ spec: resolve7(args2.specPath),
8157
+ base_model: model.id,
8158
+ base_model_revision: model.revision,
8159
+ question: { type: question.type, labels },
8160
+ split: { seed, train: split.train.length, eval: split.holdout.length, eval_split: "spec_holdout" },
8161
+ ...tunedModelDir ? { model_dir: tunedModelDir } : {},
8162
+ steps,
8163
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
8164
+ };
8165
+ await writePrivateJson2(reportPath, report);
8166
+ return {
8167
+ status: "succeeded",
8168
+ engine: "decision",
8169
+ ...args2.plan.name ? { name: args2.plan.name } : {},
8170
+ run_dir: outputDir,
8171
+ report_path: reportPath,
8172
+ ...tunedModelDir ? { model_dir: tunedModelDir } : {},
8173
+ steps
8174
+ };
8175
+ }
8176
+
6859
8177
  // src/commands/pipeline.ts
6860
8178
  var DEFAULT_SPEC_FILE = "tunedtensor.json";
6861
8179
  function parseList(value) {
@@ -6865,7 +8183,7 @@ function parseList(value) {
6865
8183
  return ids;
6866
8184
  }
6867
8185
  function loadPipelineFile(path) {
6868
- const resolved = resolve6(path);
8186
+ const resolved = resolve8(path);
6869
8187
  if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
6870
8188
  try {
6871
8189
  return JSON.parse(readFileSync5(resolved, "utf8"));
@@ -6877,7 +8195,7 @@ function pipelineFromSpec(input) {
6877
8195
  return pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation);
6878
8196
  }
6879
8197
  function resolvePipelineDocument(options, specRequired) {
6880
- const specPath = resolve6(options.spec);
8198
+ const specPath = resolve8(options.spec);
6881
8199
  const requested = options.file ? loadPipelineFile(options.file) : void 0;
6882
8200
  if (!existsSync4(specPath)) {
6883
8201
  if (specRequired) throw new Error(`Behavior spec not found: ${options.spec}.`);
@@ -6890,10 +8208,14 @@ function resolvePipelineDocument(options, specRequired) {
6890
8208
  const spec = {
6891
8209
  path: specPath,
6892
8210
  sha256: specHash(source),
6893
- engine: input.kind === "foundation-spec" ? "foundation" : "adapter"
8211
+ engine: specEngine(input)
6894
8212
  };
6895
8213
  return { document, input, spec };
6896
8214
  }
8215
+ function specEngine(input) {
8216
+ if (input.kind === "foundation-spec") return "foundation";
8217
+ return input.kind === "decision-spec" ? "decision" : "adapter";
8218
+ }
6897
8219
  function outputPlan(plan, hostWarnings = []) {
6898
8220
  if (isJsonMode()) {
6899
8221
  return printJson(hostWarnings.length ? { ...plan, host_warnings: hostWarnings } : plan);
@@ -6910,15 +8232,34 @@ function outputPlan(plan, hostWarnings = []) {
6910
8232
  async function hostWarningsForPipeline(document, input) {
6911
8233
  const snapshot = await readHardwareSnapshot();
6912
8234
  if (!snapshot) return [];
8235
+ if (input?.kind === "decision-spec") return warningsFromSnapshot(snapshot.capabilities, { engine: "decision" });
6913
8236
  const engine = isFoundationPipeline(parsePipeline(document)) ? "foundation" : "adapter";
6914
8237
  const baseModel = input && input.kind !== "foundation-spec" ? input.request.spec_snapshot.base_model : void 0;
6915
8238
  return warningsFromSnapshot(snapshot.capabilities, { engine, baseModel });
6916
8239
  }
8240
+ function percent(value) {
8241
+ return `${(value * 100).toFixed(1)}%`;
8242
+ }
8243
+ function printDecisionSummary(result2) {
8244
+ for (const step of result2.steps) {
8245
+ if (step.decision_metrics) {
8246
+ const metrics = step.decision_metrics;
8247
+ console.log(`${step.id.padEnd(12)} accuracy ${percent(metrics.accuracy)} log-loss ${metrics.log_loss.toFixed(3)} brier ${metrics.brier.toFixed(3)} (${step.report?.total ?? 0} held-out)`);
8248
+ }
8249
+ if (step.comparison) {
8250
+ const comparison = step.comparison;
8251
+ const sign = (value) => value >= 0 ? "+" : "";
8252
+ console.log(`${step.id.padEnd(12)} accuracy ${sign(comparison.pass_rate_delta)}${percent(comparison.pass_rate_delta)} log-loss ${sign(comparison.log_loss_delta)}${comparison.log_loss_delta.toFixed(3)} improved ${comparison.improvements}, regressed ${comparison.regressions}`);
8253
+ }
8254
+ }
8255
+ if (result2.model_dir) console.log(`Tuned decision model: ${result2.model_dir}`);
8256
+ printSuccess(`Decision pipeline completed. Report: ${result2.report_path}`);
8257
+ }
6917
8258
  function registerPipelineCommands(parent) {
6918
8259
  const pipeline2 = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
6919
8260
  pipeline2.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Legacy standalone recipe output (default: embed in tunedtensor.json)").option("--engine <engine>", "adapter (default) or foundation").option("--spec <path>", "Foundation spec whose hyperparameters stamp the DAG").action(async (options) => {
6920
8261
  if (!options.file) {
6921
- const path2 = resolve6(options.spec ?? DEFAULT_SPEC_FILE);
8262
+ const path2 = resolve8(options.spec ?? DEFAULT_SPEC_FILE);
6922
8263
  const raw = JSON.parse(readFileSync5(path2, "utf8"));
6923
8264
  const input = parseLocalRunInput(raw, path2);
6924
8265
  if (input.kind === "request") throw new Error("Pipeline init requires a behavior spec.");
@@ -6937,12 +8278,12 @@ function registerPipelineCommands(parent) {
6937
8278
  if (engine !== "foundation" && options.spec) {
6938
8279
  throw new Error("--spec is only valid with --engine foundation.");
6939
8280
  }
6940
- const path = resolve6(options.file);
8281
+ const path = resolve8(options.file);
6941
8282
  if (existsSync4(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
6942
8283
  let recipe;
6943
8284
  if (engine === "foundation") {
6944
8285
  if (options.spec) {
6945
- const input = await loadLocalRunInput(resolve6(options.spec));
8286
+ const input = await loadLocalRunInput(resolve8(options.spec));
6946
8287
  if (input.kind !== "foundation-spec") {
6947
8288
  throw new Error("--spec must be a foundation tunedtensor.json.");
6948
8289
  }
@@ -6959,7 +8300,7 @@ function registerPipelineCommands(parent) {
6959
8300
  printSuccess(`Created ${options.file}`);
6960
8301
  });
6961
8302
  pipeline2.command("migrate").description("Move legacy configuration into tunedtensor.json, retaining .bak copies").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).action(async (options) => {
6962
- const result2 = await migrateProjectWorkflow(resolve6(options.spec));
8303
+ const result2 = await migrateProjectWorkflow(resolve8(options.spec));
6963
8304
  if (isJsonMode()) return printJson(result2);
6964
8305
  printSuccess(result2.migrated ? `Migrated ${result2.path}; originals retained as .bak files.` : "No legacy configuration found.");
6965
8306
  });
@@ -6983,7 +8324,7 @@ function registerPipelineCommands(parent) {
6983
8324
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
6984
8325
  outputPlan({ ...plan, ...spec ? { spec } : {} }, await hostWarningsForPipeline(document, input));
6985
8326
  });
6986
- pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
8327
+ pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation or decision run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
6987
8328
  if (options.output && options.resume) {
6988
8329
  throw new Error("--output and --resume are mutually exclusive.");
6989
8330
  }
@@ -7016,8 +8357,8 @@ function registerPipelineCommands(parent) {
7016
8357
  const result3 = await runFoundationPipeline({
7017
8358
  spec: input.spec,
7018
8359
  plan,
7019
- specPath: resolve6(options.spec),
7020
- ...options.output || options.resume ? { outputDir: resolve6(options.resume ?? options.output) } : {},
8360
+ specPath: resolve8(options.spec),
8361
+ ...options.output || options.resume ? { outputDir: resolve8(options.resume ?? options.output) } : {},
7021
8362
  resume: Boolean(options.resume),
7022
8363
  gpu: config.gpu
7023
8364
  });
@@ -7025,8 +8366,21 @@ function registerPipelineCommands(parent) {
7025
8366
  printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
7026
8367
  return;
7027
8368
  }
8369
+ if (input.kind === "decision-spec") {
8370
+ if (options.resume) throw new Error("--resume is only valid for foundation pipelines.");
8371
+ const result3 = await runDecisionPipeline({
8372
+ spec: input.spec,
8373
+ plan,
8374
+ specPath: resolve8(options.spec),
8375
+ ...options.output ? { outputDir: resolve8(options.output) } : {},
8376
+ onProgress: isJsonMode() ? void 0 : (message) => console.error(message)
8377
+ });
8378
+ if (isJsonMode()) return printJson({ ...result3, spec });
8379
+ printDecisionSummary(result3);
8380
+ return;
8381
+ }
7028
8382
  if (options.output || options.resume) {
7029
- throw new Error("--output and --resume are only valid for foundation pipelines.");
8383
+ throw new Error("--output is only valid for foundation and decision pipelines; --resume only for foundation.");
7030
8384
  }
7031
8385
  const localPipeline = {
7032
8386
  version: 1,
@@ -7043,7 +8397,7 @@ function registerPipelineCommands(parent) {
7043
8397
  import { createInterface as createInterface3 } from "readline/promises";
7044
8398
  import { stdin as stdin2, stdout as stdout2 } from "process";
7045
8399
  import { Writable as Writable2 } from "stream";
7046
- import chalk7 from "chalk";
8400
+ import chalk10 from "chalk";
7047
8401
  async function promptForApiKey(input = stdin2, output = stdout2) {
7048
8402
  if (input.isTTY !== true || output.isTTY !== true) {
7049
8403
  throw new Error(
@@ -7085,9 +8439,9 @@ function registerAuthCommands(parent) {
7085
8439
  const settingsUrl = `${getBaseUrl(parent.optsWithGlobals()).replace(/\/$/, "")}/dashboard/settings`;
7086
8440
  console.log();
7087
8441
  console.log(
7088
- ` To get your TT access token, go to ${chalk7.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
8442
+ ` To get your TT access token, go to ${chalk10.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
7089
8443
  );
7090
- console.log(` ${chalk7.hex("#A78BFA").underline(settingsUrl)}`);
8444
+ console.log(` ${chalk10.hex("#A78BFA").underline(settingsUrl)}`);
7091
8445
  console.log();
7092
8446
  apiKey = await promptForApiKey();
7093
8447
  }
@@ -7143,7 +8497,7 @@ function registerAuthCommands(parent) {
7143
8497
  }
7144
8498
 
7145
8499
  // src/commands/balance.ts
7146
- import chalk8 from "chalk";
8500
+ import chalk11 from "chalk";
7147
8501
  var KIND_LABELS = {
7148
8502
  signup_bonus: "Signup bonus",
7149
8503
  topup: "Top-up",
@@ -7158,8 +8512,8 @@ function formatCents(cents) {
7158
8512
  return `${sign}$${(abs / 100).toFixed(2)}`;
7159
8513
  }
7160
8514
  function formatSigned(cents) {
7161
- if (cents > 0) return chalk8.green(`+${formatCents(cents)}`);
7162
- if (cents < 0) return chalk8.red(formatCents(cents));
8515
+ if (cents > 0) return chalk11.green(`+${formatCents(cents)}`);
8516
+ if (cents < 0) return chalk11.red(formatCents(cents));
7163
8517
  return formatCents(cents);
7164
8518
  }
7165
8519
  function registerBalanceCommands(parent) {
@@ -7174,7 +8528,7 @@ function registerBalanceCommands(parent) {
7174
8528
  return printJson({ balance, transactions });
7175
8529
  }
7176
8530
  const lowBalance = balance.balance_cents < 100;
7177
- const balanceLine = lowBalance ? chalk8.red(formatCents(balance.balance_cents)) + chalk8.dim(" (low)") : chalk8.bold.green(formatCents(balance.balance_cents));
8531
+ const balanceLine = lowBalance ? chalk11.red(formatCents(balance.balance_cents)) + chalk11.dim(" (low)") : chalk11.bold.green(formatCents(balance.balance_cents));
7178
8532
  printDetail([
7179
8533
  ["Credits", balanceLine],
7180
8534
  [
@@ -7189,7 +8543,7 @@ function registerBalanceCommands(parent) {
7189
8543
  ]);
7190
8544
  if (transactions && transactions.length > 0) {
7191
8545
  console.log(`
7192
- ${chalk8.bold("Recent transactions")}`);
8546
+ ${chalk11.bold("Recent transactions")}`);
7193
8547
  printTable(
7194
8548
  ["Date", "Type", "Amount", "Balance", "Reference"],
7195
8549
  transactions.map((row) => [
@@ -7197,19 +8551,19 @@ ${chalk8.bold("Recent transactions")}`);
7197
8551
  KIND_LABELS[row.kind] ?? row.kind,
7198
8552
  formatSigned(row.amount_cents),
7199
8553
  formatCents(row.balance_after_cents),
7200
- row.reference_id ? shortId(row.reference_id) : chalk8.dim("\u2014")
8554
+ row.reference_id ? shortId(row.reference_id) : chalk11.dim("\u2014")
7201
8555
  ])
7202
8556
  );
7203
8557
  } else {
7204
8558
  console.log(
7205
8559
  `
7206
- ${chalk8.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
8560
+ ${chalk11.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
7207
8561
  );
7208
8562
  }
7209
8563
  if (lowBalance) {
7210
8564
  console.log(
7211
8565
  `
7212
- ${formatStatus("preparing")} ${chalk8.yellow(
8566
+ ${formatStatus("preparing")} ${chalk11.yellow(
7213
8567
  "Run `tt topup` to add more credits before starting a run."
7214
8568
  )}`
7215
8569
  );
@@ -7218,7 +8572,7 @@ ${formatStatus("preparing")} ${chalk8.yellow(
7218
8572
  }
7219
8573
 
7220
8574
  // src/commands/topup.ts
7221
- import chalk9 from "chalk";
8575
+ import chalk12 from "chalk";
7222
8576
  import open4 from "open";
7223
8577
  var PRESETS_USD = [10, 25, 50, 100];
7224
8578
  var MIN_USD = 5;
@@ -7257,9 +8611,9 @@ function registerTopupCommands(parent) {
7257
8611
  "amount is required in --json mode (use --amount <usd>)"
7258
8612
  );
7259
8613
  }
7260
- console.log(chalk9.bold("Add credits via Stripe Checkout"));
8614
+ console.log(chalk12.bold("Add credits via Stripe Checkout"));
7261
8615
  console.log(
7262
- chalk9.dim(
8616
+ chalk12.dim(
7263
8617
  `Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
7264
8618
  )
7265
8619
  );
@@ -7286,8 +8640,8 @@ function registerTopupCommands(parent) {
7286
8640
  }
7287
8641
  const url = data3.checkout_url;
7288
8642
  console.log(
7289
- `${chalk9.bold("Checkout URL:")} ${chalk9.hex("#A78BFA")(url)}
7290
- ` + chalk9.dim("Complete the payment to credit your account.")
8643
+ `${chalk12.bold("Checkout URL:")} ${chalk12.hex("#A78BFA")(url)}
8644
+ ` + chalk12.dim("Complete the payment to credit your account.")
7291
8645
  );
7292
8646
  if (options.open !== false) {
7293
8647
  try {
@@ -7295,7 +8649,7 @@ function registerTopupCommands(parent) {
7295
8649
  printSuccess("Opened Stripe Checkout in your browser.");
7296
8650
  } catch {
7297
8651
  console.log(
7298
- chalk9.yellow(
8652
+ chalk12.yellow(
7299
8653
  "Could not open browser automatically \u2014 copy the URL above."
7300
8654
  )
7301
8655
  );
@@ -7307,21 +8661,21 @@ function registerTopupCommands(parent) {
7307
8661
  // src/commands/publish.ts
7308
8662
  import { createInterface as createInterface4 } from "readline/promises";
7309
8663
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
7310
- import { homedir as homedir2 } from "os";
7311
- import { isAbsolute as isAbsolute5, join as join12, resolve as resolve7 } from "path";
8664
+ import { homedir as homedir3 } from "os";
8665
+ import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
7312
8666
  import { stdin as defaultStdin, stdout as defaultStdout } from "process";
7313
8667
  var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
7314
8668
  function expandPath2(value, baseDirectory, homeDirectory) {
7315
8669
  if (value === "~") return homeDirectory;
7316
- if (value.startsWith("~/")) return resolve7(homeDirectory, value.slice(2));
7317
- return isAbsolute5(value) ? resolve7(value) : resolve7(baseDirectory, value);
8670
+ if (value.startsWith("~/")) return resolve9(homeDirectory, value.slice(2));
8671
+ return isAbsolute5(value) ? resolve9(value) : resolve9(baseDirectory, value);
7318
8672
  }
7319
8673
  function resolvePublishStoreRoot(options) {
7320
8674
  const cwd = options?.cwd ?? process.cwd();
7321
8675
  const env = options?.env ?? process.env;
7322
- const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve7(env.HOME) : homedir2());
7323
- const localConfigPath = join12(cwd, "local-runner.json");
7324
- const projectPath = join12(cwd, "tunedtensor.json");
8676
+ const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir3());
8677
+ const localConfigPath = join14(cwd, "local-runner.json");
8678
+ const projectPath = join14(cwd, "tunedtensor.json");
7325
8679
  if (existsSync5(projectPath)) {
7326
8680
  const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync6(projectPath, "utf8")));
7327
8681
  if (existsSync5(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
@@ -8034,7 +9388,7 @@ function formatCents2(cents) {
8034
9388
  return `$${(cents / 100).toFixed(2)}`;
8035
9389
  }
8036
9390
  function sleep(ms) {
8037
- return new Promise((resolve11) => setTimeout(resolve11, ms));
9391
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
8038
9392
  }
8039
9393
  function parseCsvHeader(line) {
8040
9394
  const fields = [];
@@ -8876,17 +10230,17 @@ import {
8876
10230
  readdirSync,
8877
10231
  rmSync
8878
10232
  } from "fs";
8879
- import { homedir as homedir3 } from "os";
8880
- import { dirname as dirname9, join as join13, resolve as resolve9, basename as basename5, normalize, isAbsolute as isAbsolute6 } from "path";
10233
+ import { homedir as homedir4 } from "os";
10234
+ import { dirname as dirname11, join as join15, resolve as resolve11, basename as basename5, normalize, isAbsolute as isAbsolute6 } from "path";
8881
10235
  import { Readable, Transform } from "stream";
8882
10236
  import { pipeline } from "stream/promises";
8883
10237
 
8884
10238
  // src/commands/init.ts
8885
10239
  import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
8886
- import { resolve as resolve8 } from "path";
10240
+ import { resolve as resolve10 } from "path";
8887
10241
  var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
8888
10242
  function loadSpec(filePath) {
8889
- const resolved = resolve8(filePath || DEFAULT_SPEC_FILE2);
10243
+ const resolved = resolve10(filePath || DEFAULT_SPEC_FILE2);
8890
10244
  if (!existsSync8(resolved)) {
8891
10245
  printError(
8892
10246
  `Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
@@ -8900,23 +10254,23 @@ Run \`tt init\` to create one.`
8900
10254
 
8901
10255
  // src/serve-manager.ts
8902
10256
  import { spawn as spawn2 } from "child_process";
8903
- import { randomUUID as randomUUID9 } from "crypto";
10257
+ import { randomUUID as randomUUID10 } from "crypto";
8904
10258
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
8905
10259
  import { createServer } from "http";
8906
10260
  import { createServer as createNetServer } from "net";
8907
- import { dirname as dirname8 } from "path";
10261
+ import { dirname as dirname10 } from "path";
8908
10262
  var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
8909
10263
  function parsePositiveInteger(value, fallback) {
8910
10264
  return Number.isInteger(value) && value >= 0 ? value : fallback;
8911
10265
  }
8912
10266
  function sleep2(ms) {
8913
- return new Promise((resolve11) => setTimeout(resolve11, ms));
10267
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
8914
10268
  }
8915
10269
  function readRequestBody(req) {
8916
- return new Promise((resolve11, reject) => {
10270
+ return new Promise((resolve13, reject) => {
8917
10271
  const chunks = [];
8918
10272
  req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
8919
- req.on("end", () => resolve11(Buffer.concat(chunks)));
10273
+ req.on("end", () => resolve13(Buffer.concat(chunks)));
8920
10274
  req.on("error", reject);
8921
10275
  });
8922
10276
  }
@@ -8984,7 +10338,7 @@ function errorSummary(response, fallback) {
8984
10338
  return typeof message === "string" ? message : fallback;
8985
10339
  }
8986
10340
  async function allocatePort(host) {
8987
- return new Promise((resolve11, reject) => {
10341
+ return new Promise((resolve13, reject) => {
8988
10342
  const server = createNetServer();
8989
10343
  server.on("error", reject);
8990
10344
  server.listen(0, host, () => {
@@ -8994,7 +10348,7 @@ async function allocatePort(host) {
8994
10348
  return;
8995
10349
  }
8996
10350
  const port = address.port;
8997
- server.close(() => resolve11(port));
10351
+ server.close(() => resolve13(port));
8998
10352
  });
8999
10353
  });
9000
10354
  }
@@ -9034,11 +10388,11 @@ var ManagedModelServer = class {
9034
10388
  sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
9035
10389
  });
9036
10390
  });
9037
- await new Promise((resolve11, reject) => {
10391
+ await new Promise((resolve13, reject) => {
9038
10392
  this.server?.once("error", reject);
9039
10393
  this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
9040
10394
  this.server?.off("error", reject);
9041
- resolve11();
10395
+ resolve13();
9042
10396
  });
9043
10397
  });
9044
10398
  }
@@ -9048,15 +10402,15 @@ var ManagedModelServer = class {
9048
10402
  if (!this.server) return;
9049
10403
  const server = this.server;
9050
10404
  this.server = null;
9051
- await new Promise((resolve11, reject) => {
9052
- server.close((err) => err ? reject(err) : resolve11());
10405
+ await new Promise((resolve13, reject) => {
10406
+ server.close((err) => err ? reject(err) : resolve13());
9053
10407
  });
9054
10408
  }
9055
10409
  async run() {
9056
10410
  await this.start();
9057
- await new Promise((resolve11, reject) => {
10411
+ await new Promise((resolve13, reject) => {
9058
10412
  this.server?.once("error", reject);
9059
- this.server?.once("close", resolve11);
10413
+ this.server?.once("close", resolve13);
9060
10414
  });
9061
10415
  }
9062
10416
  async handle(req, res) {
@@ -9124,7 +10478,7 @@ var ManagedModelServer = class {
9124
10478
  });
9125
10479
  }
9126
10480
  async processGeneration(route, body, res, receivedAt) {
9127
- const requestId = `ttreq-${randomUUID9()}`;
10481
+ const requestId = `ttreq-${randomUUID10()}`;
9128
10482
  const startedAt = Date.now();
9129
10483
  const queuedMs = startedAt - receivedAt;
9130
10484
  this.activeRequests += 1;
@@ -9230,7 +10584,7 @@ var ManagedModelServer = class {
9230
10584
  try {
9231
10585
  child.kill("SIGTERM");
9232
10586
  await Promise.race([
9233
- new Promise((resolve11) => child.once("exit", () => resolve11())),
10587
+ new Promise((resolve13) => child.once("exit", () => resolve13())),
9234
10588
  sleep2(5e3).then(() => {
9235
10589
  if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
9236
10590
  })
@@ -9308,7 +10662,7 @@ var ManagedModelServer = class {
9308
10662
  const line = `${JSON.stringify(record4)}
9309
10663
  `;
9310
10664
  if (this.options.logFile) {
9311
- mkdirSync3(dirname8(this.options.logFile), { recursive: true });
10665
+ mkdirSync3(dirname10(this.options.logFile), { recursive: true });
9312
10666
  appendFileSync(this.options.logFile, line, "utf8");
9313
10667
  return;
9314
10668
  }
@@ -9856,7 +11210,7 @@ if __name__ == "__main__":
9856
11210
  function resolveOutputPath(output, filename) {
9857
11211
  if (!output) return filename;
9858
11212
  if (existsSync9(output) && statSync4(output).isDirectory()) {
9859
- return join13(output, filename);
11213
+ return join15(output, filename);
9860
11214
  }
9861
11215
  return output;
9862
11216
  }
@@ -9932,7 +11286,7 @@ async function downloadUrlToFile(url, outputPath) {
9932
11286
  if (!res.body) {
9933
11287
  throw new Error("Download failed: response body was empty");
9934
11288
  }
9935
- mkdirSync4(dirname9(outputPath), { recursive: true });
11289
+ mkdirSync4(dirname11(outputPath), { recursive: true });
9936
11290
  const file = createWriteStream(outputPath);
9937
11291
  const contentLength = parseContentLength(res.headers.get("content-length"));
9938
11292
  const progress = createDownloadProgress(
@@ -9963,15 +11317,15 @@ async function downloadUrlToFile(url, outputPath) {
9963
11317
  return contentLength;
9964
11318
  }
9965
11319
  function defaultCacheDir() {
9966
- const root = process.env.XDG_CACHE_HOME || join13(homedir3(), ".cache");
9967
- return join13(root, "tuned-tensor", "models");
11320
+ const root = process.env.XDG_CACHE_HOME || join15(homedir4(), ".cache");
11321
+ return join15(root, "tuned-tensor", "models");
9968
11322
  }
9969
11323
  function runtimeDir(cacheDir) {
9970
- return join13(cacheDir, "_runtime");
11324
+ return join15(cacheDir, "_runtime");
9971
11325
  }
9972
11326
  function runtimePythonPath(cacheDir) {
9973
11327
  const dir = runtimeDir(cacheDir);
9974
- return process.platform === "win32" ? join13(dir, "Scripts", "python.exe") : join13(dir, "bin", "python");
11328
+ return process.platform === "win32" ? join15(dir, "Scripts", "python.exe") : join15(dir, "bin", "python");
9975
11329
  }
9976
11330
  function resolveServePython(cacheDir, explicitPython) {
9977
11331
  if (explicitPython) return explicitPython;
@@ -10044,7 +11398,7 @@ print(json.dumps({"missing": missing}))
10044
11398
  }
10045
11399
  }
10046
11400
  function setupRuntimeCommands(python, venvDir, deps) {
10047
- const venvPython = process.platform === "win32" ? join13(venvDir, "Scripts", "python.exe") : join13(venvDir, "bin", "python");
11401
+ const venvPython = process.platform === "win32" ? join15(venvDir, "Scripts", "python.exe") : join15(venvDir, "bin", "python");
10048
11402
  return [
10049
11403
  [python, "-m", "venv", venvDir],
10050
11404
  [venvPython, "-m", "pip", "install", "--upgrade", "pip"],
@@ -10082,9 +11436,9 @@ function extractArchive(archivePath, targetDir, force) {
10082
11436
  return targetDir;
10083
11437
  }
10084
11438
  function localArchiveCacheDir(archivePath, cacheDir) {
10085
- const resolved = resolve9(archivePath);
11439
+ const resolved = resolve11(archivePath);
10086
11440
  const digest = createHash6("sha256").update(resolved).digest("hex").slice(0, 12);
10087
- return join13(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
11441
+ return join15(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
10088
11442
  }
10089
11443
  function buildSystemPrompt(spec) {
10090
11444
  const parts = [];
@@ -10100,9 +11454,9 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
10100
11454
  return parts.join("\n\n");
10101
11455
  }
10102
11456
  function findSpecPath(explicitPath, modelPath) {
10103
- const candidates = explicitPath ? [explicitPath] : [join13(process.cwd(), DEFAULT_SPEC_FILE2), join13(modelPath, DEFAULT_SPEC_FILE2)];
11457
+ const candidates = explicitPath ? [explicitPath] : [join15(process.cwd(), DEFAULT_SPEC_FILE2), join15(modelPath, DEFAULT_SPEC_FILE2)];
10104
11458
  for (const candidate of candidates) {
10105
- const resolved = resolve9(candidate);
11459
+ const resolved = resolve11(candidate);
10106
11460
  if (existsSync9(resolved) && statSync4(resolved).isFile()) return resolved;
10107
11461
  }
10108
11462
  return null;
@@ -10112,7 +11466,7 @@ function loadSystemPromptFromSpec(specPath) {
10112
11466
  return buildSystemPrompt(spec);
10113
11467
  }
10114
11468
  function loadJsonSchemaForServe(schemaPath) {
10115
- const resolved = resolve9(schemaPath);
11469
+ const resolved = resolve11(schemaPath);
10116
11470
  if (!existsSync9(resolved) || !statSync4(resolved).isFile()) {
10117
11471
  throw new Error(`JSON schema file not found: ${schemaPath}`);
10118
11472
  }
@@ -10124,15 +11478,15 @@ function loadJsonSchemaForServe(schemaPath) {
10124
11478
  }
10125
11479
  }
10126
11480
  function writeReferenceServerScript(cacheDir) {
10127
- const scriptDir = join13(cacheDir, "_server");
11481
+ const scriptDir = join15(cacheDir, "_server");
10128
11482
  mkdirSync4(scriptDir, { recursive: true });
10129
- const scriptPath = join13(scriptDir, "openai_reference_server.py");
11483
+ const scriptPath = join15(scriptDir, "openai_reference_server.py");
10130
11484
  writeFileSync5(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
10131
11485
  return scriptPath;
10132
11486
  }
10133
11487
  async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10134
11488
  if (existsSync9(target)) {
10135
- const resolved = resolve9(target);
11489
+ const resolved = resolve11(target);
10136
11490
  const stats = statSync4(resolved);
10137
11491
  if (stats.isDirectory()) {
10138
11492
  return {
@@ -10142,7 +11496,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10142
11496
  };
10143
11497
  }
10144
11498
  if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
10145
- const extractDir2 = join13(localArchiveCacheDir(resolved, cacheDir), "model");
11499
+ const extractDir2 = join15(localArchiveCacheDir(resolved, cacheDir), "model");
10146
11500
  return {
10147
11501
  modelPath: extractArchive(resolved, extractDir2, forceDownload),
10148
11502
  modelName: basename5(resolved).replace(/\.t(ar\.)?gz$/, ""),
@@ -10154,9 +11508,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10154
11508
  const fullId = await resolveModelId(target, opts);
10155
11509
  const { data: model } = await get(`/models/${fullId}`, void 0, opts);
10156
11510
  const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
10157
- const modelCacheDir = join13(cacheDir, fullId);
10158
- const archivePath = join13(modelCacheDir, data3.filename);
10159
- const extractDir = join13(modelCacheDir, "model");
11511
+ const modelCacheDir = join15(cacheDir, fullId);
11512
+ const archivePath = join15(modelCacheDir, data3.filename);
11513
+ const extractDir = join15(modelCacheDir, "model");
10160
11514
  if (!existsSync9(archivePath) || forceDownload) {
10161
11515
  await downloadUrlToFile(data3.url, archivePath);
10162
11516
  }
@@ -10262,7 +11616,7 @@ function ollamaSlug(name) {
10262
11616
  function firstExisting(candidates) {
10263
11617
  for (const candidate of candidates) {
10264
11618
  if (candidate && existsSync9(candidate) && statSync4(candidate).isFile()) {
10265
- return resolve9(candidate);
11619
+ return resolve11(candidate);
10266
11620
  }
10267
11621
  }
10268
11622
  return null;
@@ -10272,14 +11626,14 @@ function llamaCppDir(explicit) {
10272
11626
  }
10273
11627
  function resolveConvertScript(opts, required) {
10274
11628
  if (opts.convertScript) {
10275
- const resolved = resolve9(opts.convertScript);
11629
+ const resolved = resolve11(opts.convertScript);
10276
11630
  if (required && !existsSync9(resolved)) {
10277
11631
  throw new Error(`Conversion script not found: ${opts.convertScript}`);
10278
11632
  }
10279
11633
  return resolved;
10280
11634
  }
10281
11635
  const dir = llamaCppDir(opts.llamaCpp);
10282
- const candidates = dir ? [join13(dir, "convert_hf_to_gguf.py"), join13(dir, "convert-hf-to-gguf.py")] : [];
11636
+ const candidates = dir ? [join15(dir, "convert_hf_to_gguf.py"), join15(dir, "convert-hf-to-gguf.py")] : [];
10283
11637
  const found = firstExisting(candidates);
10284
11638
  if (found) return found;
10285
11639
  if (required) {
@@ -10287,11 +11641,11 @@ function resolveConvertScript(opts, required) {
10287
11641
  "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."
10288
11642
  );
10289
11643
  }
10290
- return dir ? join13(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
11644
+ return dir ? join15(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
10291
11645
  }
10292
11646
  function resolveQuantizeBin(opts, required) {
10293
11647
  if (opts.quantizeBin) {
10294
- const resolved = resolve9(opts.quantizeBin);
11648
+ const resolved = resolve11(opts.quantizeBin);
10295
11649
  if (required && !existsSync9(resolved)) {
10296
11650
  throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
10297
11651
  }
@@ -10300,10 +11654,10 @@ function resolveQuantizeBin(opts, required) {
10300
11654
  const dir = llamaCppDir(opts.llamaCpp);
10301
11655
  if (dir) {
10302
11656
  const candidates = [
10303
- join13(dir, "build", "bin", "llama-quantize"),
10304
- join13(dir, "build", "bin", "quantize"),
10305
- join13(dir, "llama-quantize"),
10306
- join13(dir, "quantize")
11657
+ join15(dir, "build", "bin", "llama-quantize"),
11658
+ join15(dir, "build", "bin", "quantize"),
11659
+ join15(dir, "llama-quantize"),
11660
+ join15(dir, "quantize")
10307
11661
  ];
10308
11662
  const found = firstExisting(candidates);
10309
11663
  if (found) return found;
@@ -10451,7 +11805,7 @@ function registerModelsCommands(parent) {
10451
11805
  }
10452
11806
  const quantPlan = planQuant(String(cmdOpts.quant));
10453
11807
  const printOnly = Boolean(cmdOpts.printCommand);
10454
- const cacheDir = resolve9(cmdOpts.cacheDir || defaultCacheDir());
11808
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10455
11809
  mkdirSync4(cacheDir, { recursive: true });
10456
11810
  const serveTarget = await resolveServeTarget(
10457
11811
  target,
@@ -10460,9 +11814,9 @@ function registerModelsCommands(parent) {
10460
11814
  Boolean(cmdOpts.forceDownload)
10461
11815
  );
10462
11816
  const slug = ollamaSlug(serveTarget.modelName);
10463
- const outputDir = resolve9(cmdOpts.output || join13(process.cwd(), `${slug}-gguf`));
10464
- const finalPath = join13(outputDir, `${slug}.${quantPlan.quant}.gguf`);
10465
- const intermediatePath = quantPlan.requiresQuantize ? join13(outputDir, `${slug}.f16.gguf`) : void 0;
11817
+ const outputDir = resolve11(cmdOpts.output || join15(process.cwd(), `${slug}-gguf`));
11818
+ const finalPath = join15(outputDir, `${slug}.${quantPlan.quant}.gguf`);
11819
+ const intermediatePath = quantPlan.requiresQuantize ? join15(outputDir, `${slug}.f16.gguf`) : void 0;
10466
11820
  const convertScript = resolveConvertScript(cmdOpts, !printOnly);
10467
11821
  const convertOutfile = intermediatePath ?? finalPath;
10468
11822
  const steps = [
@@ -10500,7 +11854,7 @@ function registerModelsCommands(parent) {
10500
11854
  }
10501
11855
  if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
10502
11856
  }
10503
- modelfilePath = join13(outputDir, "Modelfile");
11857
+ modelfilePath = join15(outputDir, "Modelfile");
10504
11858
  modelfileContent = buildModelfile(basename5(finalPath), systemPrompt2);
10505
11859
  const createModel = cmdOpts.ollamaCreate !== false;
10506
11860
  if (createModel) {
@@ -10587,7 +11941,7 @@ function registerModelsCommands(parent) {
10587
11941
  }
10588
11942
  });
10589
11943
  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) => {
10590
- const cacheDir = resolve9(cmdOpts.cacheDir || defaultCacheDir());
11944
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10591
11945
  const venvDir = runtimeDir(cacheDir);
10592
11946
  const venvPython = runtimePythonPath(cacheDir);
10593
11947
  const python = pickRuntimePython(cmdOpts.python);
@@ -10633,7 +11987,7 @@ function registerModelsCommands(parent) {
10633
11987
  });
10634
11988
  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) => {
10635
11989
  const opts = parent.optsWithGlobals();
10636
- const cacheDir = resolve9(cmdOpts.cacheDir || defaultCacheDir());
11990
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10637
11991
  mkdirSync4(cacheDir, { recursive: true });
10638
11992
  const python = resolveServePython(cacheDir, cmdOpts.python);
10639
11993
  if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
@@ -10682,7 +12036,7 @@ function registerModelsCommands(parent) {
10682
12036
  idle_timeout_seconds: idleTimeoutSeconds,
10683
12037
  restart_after_requests: restartAfterRequests,
10684
12038
  gate_field: String(cmdOpts.gateField),
10685
- log_file: cmdOpts.logFile ? resolve9(cmdOpts.logFile) : void 0
12039
+ log_file: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
10686
12040
  } : void 0;
10687
12041
  if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
10688
12042
  if (!isJsonMode()) {
@@ -10719,7 +12073,7 @@ function registerModelsCommands(parent) {
10719
12073
  idleTimeoutSeconds,
10720
12074
  restartAfterRequests,
10721
12075
  gateField: String(cmdOpts.gateField),
10722
- logFile: cmdOpts.logFile ? resolve9(cmdOpts.logFile) : void 0
12076
+ logFile: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
10723
12077
  });
10724
12078
  return;
10725
12079
  }
@@ -10746,7 +12100,7 @@ function registerModelsCommands(parent) {
10746
12100
 
10747
12101
  // src/commands/push.ts
10748
12102
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
10749
- import { resolve as resolve10 } from "path";
12103
+ import { resolve as resolve12 } from "path";
10750
12104
  function persistRemoteId(filePath, id) {
10751
12105
  const raw = JSON.parse(readFileSync12(filePath, "utf-8"));
10752
12106
  raw.id = id;
@@ -10755,7 +12109,7 @@ function persistRemoteId(filePath, id) {
10755
12109
  function registerPushCommand(parent) {
10756
12110
  parent.command("push").description("Push local spec to the Tuned Tensor API").option("-f, --file <path>", "Spec file path", DEFAULT_SPEC_FILE2).action(async (cmdOpts) => {
10757
12111
  const opts = parent.optsWithGlobals();
10758
- const filePath = resolve10(cmdOpts.file);
12112
+ const filePath = resolve12(cmdOpts.file);
10759
12113
  const spec = loadSpec(cmdOpts.file);
10760
12114
  const evalCaseErrors = validateEvalCases(spec);
10761
12115
  if (evalCaseErrors.length > 0) {
@@ -10901,10 +12255,10 @@ async function checkForCliUpdate(currentVersion, options = {}) {
10901
12255
  return null;
10902
12256
  }
10903
12257
  })();
10904
- const deadline = new Promise((resolve11) => {
12258
+ const deadline = new Promise((resolve13) => {
10905
12259
  timeout = setTimeout(() => {
10906
12260
  controller.abort();
10907
- resolve11(null);
12261
+ resolve13(null);
10908
12262
  }, timeoutMs);
10909
12263
  });
10910
12264
  try {
@@ -11029,7 +12383,7 @@ async function runSelfCommand(args2, options = {}) {
11029
12383
  if (!entrypoint) {
11030
12384
  throw new Error("Cannot locate the tt CLI entrypoint.");
11031
12385
  }
11032
- return await new Promise((resolve11, reject) => {
12386
+ return await new Promise((resolve13, reject) => {
11033
12387
  const child = spawn4(process.execPath, [entrypoint, ...args2], {
11034
12388
  cwd: options.cwd,
11035
12389
  env: options.env ?? process.env,
@@ -11069,7 +12423,7 @@ async function runSelfCommand(args2, options = {}) {
11069
12423
  child.once("close", (exitCode, signal) => {
11070
12424
  cleanup();
11071
12425
  const signalNumber = signal ? osConstants2.signals[signal] : void 0;
11072
- resolve11({
12426
+ resolve13({
11073
12427
  exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
11074
12428
  signal
11075
12429
  });
@@ -11132,7 +12486,7 @@ function createProgram(version, runtime = {}) {
11132
12486
  if (root.json) setJsonMode(true);
11133
12487
  if (root.color === false) {
11134
12488
  process.env.FORCE_COLOR = "0";
11135
- chalk10.level = 0;
12489
+ chalk13.level = 0;
11136
12490
  }
11137
12491
  });
11138
12492
  registerLocalCommands(program, {
@@ -11170,12 +12524,12 @@ function createProgram(version, runtime = {}) {
11170
12524
  `);
11171
12525
  return;
11172
12526
  }
11173
- output.write(`${chalk10.bold.hex("#8B5CF6")("Tuned Tensor status")}
12527
+ output.write(`${chalk13.bold.hex("#8B5CF6")("Tuned Tensor status")}
11174
12528
  `);
11175
12529
  output.write(`${formatShellStatus(context).join("\n")}
11176
12530
 
11177
12531
  `);
11178
- output.write(`${chalk10.bold("Context")}
12532
+ output.write(`${chalk13.bold("Context")}
11179
12533
  `);
11180
12534
  output.write(`${formatShellContext(context).join("\n")}
11181
12535
  `);
@@ -11276,7 +12630,7 @@ async function runCli(version, runtime = {}) {
11276
12630
  }
11277
12631
 
11278
12632
  // src/index.ts
11279
- runCli("0.20.0").catch((err) => {
12633
+ runCli("0.21.0").catch((err) => {
11280
12634
  reportError(err);
11281
12635
  process.exit(1);
11282
12636
  });