@tuned-tensor/cli 0.20.0 → 0.22.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,34 @@
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
+ auditRun,
12
+ buildDecisionPythonCommand,
9
13
  buildFoundationPythonCommand,
10
14
  buildSystemMessage,
11
15
  canonicalFoundationPipeline,
12
16
  canonicalJson,
13
17
  canonicalPipeline,
18
+ compareEvalReports,
14
19
  configForSpec,
15
20
  createExecutionPlan,
16
21
  createLocalStore,
22
+ createRunConsoleReporter,
23
+ decisionLabels,
17
24
  defaultLocalHome,
18
25
  defaultStoreRoot,
26
+ deriveSampleSeed,
27
+ ensureDatasetCached,
28
+ evalReportSchema,
29
+ evaluateCapabilities,
19
30
  expandUserPath,
31
+ fileUri,
20
32
  formatAgentHostBlock,
21
33
  formatHostStatusLine,
22
34
  getAgentConfigDir,
@@ -32,24 +44,27 @@ import {
32
44
  pipelineForRunInput,
33
45
  pipelineFromFoundationHyperparameters,
34
46
  readHardwareSnapshot,
47
+ resolveDecisionModel,
35
48
  resolveProjectConfig,
36
49
  runGpuProcess,
37
50
  runLocalPipeline,
38
51
  runLoggedProcess,
39
52
  runReportSchema,
40
53
  specHash,
54
+ splitSpecExamples,
41
55
  validateBehaviorSpec,
42
56
  validateEvalCases,
43
57
  validatePipeline,
44
58
  warningsFromSnapshot,
59
+ withDecisionPythonEnvironment,
45
60
  withFoundationPythonEnvironment
46
- } from "./chunk-4Q54NHKR.js";
61
+ } from "./chunk-4SUXBHIY.js";
47
62
 
48
63
  // src/cli.ts
49
64
  import { spawn as spawn4 } from "child_process";
50
65
  import { constants as osConstants2 } from "os";
51
66
  import { Command } from "commander";
52
- import chalk10 from "chalk";
67
+ import chalk13 from "chalk";
53
68
 
54
69
  // src/local-agent-client.ts
55
70
  import { randomUUID as randomUUID5 } from "crypto";
@@ -75,15 +90,18 @@ var FolderName = Type.String({
75
90
  description: "One new folder name directly beneath the current workspace"
76
91
  });
77
92
  var LocalBaseModel = Type.String({
78
- type: "string",
79
- enum: TRAINING_MODELS.map((model) => model.id)
93
+ minLength: 3,
94
+ maxLength: 193,
95
+ pattern: "^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$",
96
+ 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
97
  });
81
98
  var NonEmptyText = Type.String({ minLength: 1, maxLength: 1e3 });
82
99
  var LocalProjectSpecSchema = Type.Object({
83
100
  name: Type.String({ minLength: 1, maxLength: 255 }),
84
101
  description: Type.Optional(Type.String({ maxLength: 5e3 })),
85
102
  base_model: Type.Optional(LocalBaseModel),
86
- engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation")])),
103
+ engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation"), Type.Literal("decision")])),
104
+ decision: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
87
105
  hyperparameters: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
88
106
  foundation: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
89
107
  dataset_prebuilt: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
@@ -335,6 +353,7 @@ var EDIT_KEYS = /* @__PURE__ */ new Set([
335
353
  "base_model",
336
354
  "hyperparameters",
337
355
  "foundation",
356
+ "decision",
338
357
  "dataset_prebuilt",
339
358
  "runtime",
340
359
  "evaluation",
@@ -774,7 +793,7 @@ async function prepareLocalPipelineAction(args2) {
774
793
  specPath: spec.displayPath,
775
794
  specSha256: createHash2("sha256").update(spec.source).digest("hex"),
776
795
  dryRun,
777
- engine: input.kind === "foundation-spec" ? "foundation" : "adapter",
796
+ engine: input.kind === "foundation-spec" ? "foundation" : input.kind === "decision-spec" ? "decision" : "adapter",
778
797
  resolvedSpec: input.spec,
779
798
  resolvedConfig,
780
799
  ...config ? {
@@ -1534,9 +1553,16 @@ function canonicalizeHostedBaseModel(model) {
1534
1553
  }
1535
1554
  return canonical;
1536
1555
  }
1556
+ 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}$/;
1557
+ function canonicalizeLocalBaseModel(model) {
1558
+ if (typeof model === "string" && HUGGING_FACE_REPO_ID.test(model.trim())) {
1559
+ return BASE_MODEL_ALIASES.get(model.trim().toLowerCase()) ?? model.trim();
1560
+ }
1561
+ return canonicalizeBaseModel(model);
1562
+ }
1537
1563
  function canonicalizeSpecBaseModel(body) {
1538
1564
  if ("base_model" in body && body.base_model !== void 0) {
1539
- return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
1565
+ return { ...body, base_model: canonicalizeLocalBaseModel(body.base_model) };
1540
1566
  }
1541
1567
  return body;
1542
1568
  }
@@ -1958,8 +1984,8 @@ function createTunedTensorTools(api, options = {}) {
1958
1984
  });
1959
1985
  }
1960
1986
  ),
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")])),
1987
+ 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({
1988
+ engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation"), Type2.Literal("decision")])),
1963
1989
  target: Type2.Optional(Type2.Union([Type2.Literal("local"), Type2.Literal("cloud")]))
1964
1990
  }, { additionalProperties: false }), async (p) => {
1965
1991
  const engine = p.engine ?? "adapter";
@@ -1980,6 +2006,24 @@ function createTunedTensorTools(api, options = {}) {
1980
2006
  },
1981
2007
  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
2008
  } : void 0;
2009
+ if (engine === "decision") {
2010
+ return {
2011
+ version: 1,
2012
+ engine,
2013
+ scope: { execution: "local", devices: ["cpu", "mps", "cuda"] },
2014
+ canonical: canonicalPipeline("local"),
2015
+ models: DECISION_MODELS.map((model) => ({ id: model.id, revision: model.revision, parameters: model.parameters, license: model.license })),
2016
+ 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.',
2017
+ commands: {
2018
+ init: "tt init --engine decision",
2019
+ validate: "tt validate",
2020
+ dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
2021
+ run: "tt pipeline run --spec tunedtensor.json"
2022
+ },
2023
+ 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>).",
2024
+ ...host ? { host } : {}
2025
+ };
2026
+ }
1983
2027
  if (engine === "foundation") {
1984
2028
  const foundation = {
1985
2029
  version: 1,
@@ -2097,7 +2141,7 @@ function createTunedTensorTools(api, options = {}) {
2097
2141
  define(
2098
2142
  "prepare_update_local_spec",
2099
2143
  "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.",
2144
+ "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
2145
  Type2.Object({
2102
2146
  spec_path: Type2.Optional(Type2.String({ maxLength: 1e3 })),
2103
2147
  expected_sha256: Type2.String({ pattern: "^[a-f0-9]{64}$" }),
@@ -2118,10 +2162,13 @@ function createTunedTensorTools(api, options = {}) {
2118
2162
  }, { additionalProperties: false }), { minItems: 1 })),
2119
2163
  hyperparameters: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2120
2164
  foundation: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2165
+ decision: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
2121
2166
  runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2122
2167
  evaluation: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2123
2168
  pipeline: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
2124
- dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown()))
2169
+ dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown(), {
2170
+ description: "Local chat JSONL files ({training, validation|test}), or a Hugging Face dataset: {huggingface: {repo, revision?: 40-char commit, columns?: {input, output}}, training: 'repo/path.jsonl', test|validation: ...}. The user downloads it with `tt datasets prefetch tunedtensor.json`."
2171
+ }))
2125
2172
  }, { additionalProperties: false })
2126
2173
  }, { additionalProperties: false }),
2127
2174
  async (p) => {
@@ -2246,11 +2293,13 @@ function createTunedTensorTools(api, options = {}) {
2246
2293
  // src/local-agent-client.ts
2247
2294
  var MAX_TOOL_CALLS_PER_TURN = 12;
2248
2295
  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.
2296
+ 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
2297
  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
2298
  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.
2299
+ For accurate adapter, foundation and decision workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
2253
2300
  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.
2301
+ 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.
2302
+ 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
2303
  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
2304
  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
2305
  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 +2827,79 @@ function del(path, opts) {
2778
2827
  }
2779
2828
 
2780
2829
  // src/agent.ts
2781
- import chalk5 from "chalk";
2830
+ import chalk8 from "chalk";
2831
+
2832
+ // src/mascot.ts
2833
+ import chalk from "chalk";
2834
+ var MASCOT_NAME = "tt";
2835
+ var MASCOT_WIDTH = 11;
2836
+ var PALETTE = {
2837
+ a: "#B39DFB",
2838
+ // diagonal, top-left
2839
+ b: "#9B72F8",
2840
+ // diagonal, center
2841
+ c: "#7C3AED",
2842
+ // diagonal, bottom-right
2843
+ d: "#2B323D"
2844
+ // slate tiles
2845
+ };
2846
+ var MARK = [
2847
+ "aa.dd...",
2848
+ "aa.dd...",
2849
+ "........",
2850
+ "dd.bb.dd",
2851
+ "dd.bb.dd",
2852
+ "........",
2853
+ "...dd.cc",
2854
+ "...dd.cc"
2855
+ ];
2856
+ function pixelRow(upper, lower) {
2857
+ let line = "";
2858
+ for (let column = 0; column < upper.length; column += 1) {
2859
+ const up = upper[column];
2860
+ const down = lower[column];
2861
+ if (up === "." && down === ".") line += " ";
2862
+ else if (up === ".") line += chalk.hex(PALETTE[down])("\u2584");
2863
+ else if (down === ".") line += chalk.hex(PALETTE[up])("\u2580");
2864
+ else if (up === down) line += chalk.hex(PALETTE[up])("\u2588");
2865
+ else line += chalk.hex(PALETTE[up]).bgHex(PALETTE[down])("\u2580");
2866
+ }
2867
+ return line;
2868
+ }
2869
+ function renderMascot() {
2870
+ if (chalk.level === 0) {
2871
+ return ["", " \u25A0 \u25A1", " \u25A1 \u25A0 \u25A1", " \u25A1 \u25A0"].map((row) => row.padEnd(MASCOT_WIDTH));
2872
+ }
2873
+ const rows2 = [];
2874
+ for (let row = 0; row < MARK.length; row += 2) {
2875
+ rows2.push(` ${pixelRow(MARK[row], MARK[row + 1])} `);
2876
+ }
2877
+ return rows2;
2878
+ }
2879
+ function mascotMark() {
2880
+ if (chalk.level === 0) return "\u259A";
2881
+ return `${chalk.hex(PALETTE.a)("\u2580")}${chalk.hex(PALETTE.c)("\u2584")}`;
2882
+ }
2883
+ function mascotSays(message) {
2884
+ return `${mascotMark()} ${chalk.hex(PALETTE.b).bold(MASCOT_NAME)} ${chalk.dim("\u203A")} ${message}`;
2885
+ }
2886
+ var FAREWELLS = [
2887
+ "See you next epoch.",
2888
+ "Bye! Your checkpoints are safe with me.",
2889
+ "Run tt to pick up where you left off."
2890
+ ];
2891
+ function mascotFarewell(seed = Date.now()) {
2892
+ return mascotSays(FAREWELLS[Math.abs(seed) % FAREWELLS.length]);
2893
+ }
2782
2894
 
2783
2895
  // src/shell.ts
2784
- import chalk4 from "chalk";
2896
+ import chalk7 from "chalk";
2785
2897
 
2786
2898
  // src/terminal-markdown.ts
2787
- import chalk from "chalk";
2899
+ import chalk2 from "chalk";
2788
2900
  import { stripVTControlCharacters } from "util";
2789
- var accent = chalk.hex("#8B5CF6");
2790
- var code = chalk.hex("#A78BFA");
2901
+ var accent = chalk2.hex("#8B5CF6");
2902
+ var code = chalk2.hex("#A78BFA");
2791
2903
  function sanitizeTerminalText(text2) {
2792
2904
  return stripVTControlCharacters(text2).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
2793
2905
  }
@@ -2806,7 +2918,7 @@ function renderInlineMarkdown(text2) {
2806
2918
  if (text2[index] === "[") {
2807
2919
  const link = text2.slice(index).match(/^\[([^\]]+)]\(([^)]+)\)/);
2808
2920
  if (link) {
2809
- rendered += `${chalk.underline(renderInlineMarkdown(link[1]))}${chalk.dim(` (${link[2]})`)}`;
2921
+ rendered += `${chalk2.underline(renderInlineMarkdown(link[1]))}${chalk2.dim(` (${link[2]})`)}`;
2810
2922
  index += link[0].length;
2811
2923
  continue;
2812
2924
  }
@@ -2815,7 +2927,7 @@ function renderInlineMarkdown(text2) {
2815
2927
  if (strong) {
2816
2928
  const close = closingDelimiter(text2, strong, index);
2817
2929
  if (close > index + strong.length) {
2818
- rendered += chalk.bold(
2930
+ rendered += chalk2.bold(
2819
2931
  renderInlineMarkdown(text2.slice(index + strong.length, close))
2820
2932
  );
2821
2933
  index = close + strong.length;
@@ -2834,7 +2946,7 @@ function renderInlineMarkdown(text2) {
2834
2946
  if (emphasis) {
2835
2947
  const close = closingDelimiter(text2, emphasis, index);
2836
2948
  if (close > index + 1) {
2837
- rendered += chalk.italic(
2949
+ rendered += chalk2.italic(
2838
2950
  renderInlineMarkdown(text2.slice(index + 1, close))
2839
2951
  );
2840
2952
  index = close + 1;
@@ -2849,7 +2961,7 @@ function renderInlineMarkdown(text2) {
2849
2961
  function renderMarkdownLine(line, inCodeBlock) {
2850
2962
  if (inCodeBlock) return code(` ${line}`);
2851
2963
  const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
2852
- if (heading) return chalk.bold(renderInlineMarkdown(heading[1]));
2964
+ if (heading) return chalk2.bold(renderInlineMarkdown(heading[1]));
2853
2965
  const bullet = line.match(/^(\s*)[-+*]\s+(.+)$/);
2854
2966
  if (bullet) {
2855
2967
  return `${bullet[1]}${accent("\u2022")} ${renderInlineMarkdown(bullet[2])}`;
@@ -2859,9 +2971,9 @@ function renderMarkdownLine(line, inCodeBlock) {
2859
2971
  return `${numbered[1]}${accent(`${numbered[2]}.`)} ${renderInlineMarkdown(numbered[3])}`;
2860
2972
  }
2861
2973
  const quote = line.match(/^\s*>\s?(.*)$/);
2862
- if (quote) return `${chalk.dim("\u2502")} ${chalk.italic(renderInlineMarkdown(quote[1]))}`;
2974
+ if (quote) return `${chalk2.dim("\u2502")} ${chalk2.italic(renderInlineMarkdown(quote[1]))}`;
2863
2975
  if (/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
2864
- return chalk.dim("\u2500".repeat(32));
2976
+ return chalk2.dim("\u2500".repeat(32));
2865
2977
  }
2866
2978
  return renderInlineMarkdown(line);
2867
2979
  }
@@ -2937,9 +3049,9 @@ function wrapTerminalLine(line, width) {
2937
3049
  }
2938
3050
 
2939
3051
  // src/output.ts
2940
- import chalk2 from "chalk";
3052
+ import chalk3 from "chalk";
2941
3053
  import Table from "cli-table3";
2942
- var labelStyle = chalk2.bold.hex("#A78BFA");
3054
+ var labelStyle = chalk3.bold.hex("#A78BFA");
2943
3055
  var jsonMode = false;
2944
3056
  function setJsonMode(enabled) {
2945
3057
  jsonMode = enabled;
@@ -2962,7 +3074,7 @@ function printTable(headers, rows2, meta) {
2962
3074
  if (meta) {
2963
3075
  const totalPages = Math.ceil(meta.total / meta.per_page);
2964
3076
  console.log(
2965
- chalk2.dim(
3077
+ chalk3.dim(
2966
3078
  `
2967
3079
  Page ${meta.page}/${totalPages} (${meta.total} total)`
2968
3080
  )
@@ -2973,17 +3085,17 @@ function printDetail(fields) {
2973
3085
  const maxLabel = Math.max(...fields.map(([l]) => l.length));
2974
3086
  for (const [label, value] of fields) {
2975
3087
  const paddedLabel = label.padEnd(maxLabel);
2976
- console.log(`${labelStyle(paddedLabel)} ${value ?? chalk2.dim("\u2014")}`);
3088
+ console.log(`${labelStyle(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
2977
3089
  }
2978
3090
  }
2979
3091
  function printSuccess(message) {
2980
- console.log(chalk2.green("\u2713") + " " + message);
3092
+ console.log(chalk3.green("\u2713") + " " + message);
2981
3093
  }
2982
3094
  function printWarning(message) {
2983
- console.log(chalk2.yellow("!") + " " + message);
3095
+ console.log(chalk3.yellow("!") + " " + message);
2984
3096
  }
2985
3097
  function printError(message) {
2986
- console.error(chalk2.red("\u2717") + " " + message);
3098
+ console.error(chalk3.red("\u2717") + " " + message);
2987
3099
  }
2988
3100
  function reportError(err) {
2989
3101
  const isApi = err instanceof ApiError;
@@ -3008,19 +3120,19 @@ function formatDate(iso) {
3008
3120
  }
3009
3121
  function formatStatus(status2) {
3010
3122
  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
3123
+ completed: chalk3.green,
3124
+ running: chalk3.hex("#A78BFA"),
3125
+ training: chalk3.hex("#A78BFA"),
3126
+ evaluating: chalk3.hex("#A78BFA"),
3127
+ preparing: chalk3.yellow,
3128
+ pending: chalk3.yellow,
3129
+ uploading: chalk3.yellow,
3130
+ failed: chalk3.red,
3131
+ cancelled: chalk3.dim,
3132
+ validated: chalk3.green,
3133
+ invalid: chalk3.red
3022
3134
  };
3023
- const colorFn = map[status2] || chalk2.white;
3135
+ const colorFn = map[status2] || chalk3.white;
3024
3136
  return colorFn(status2);
3025
3137
  }
3026
3138
  function truncate(str, max) {
@@ -3031,6 +3143,400 @@ function shortId(id) {
3031
3143
  return id.slice(0, 8);
3032
3144
  }
3033
3145
 
3146
+ // src/spec-view.ts
3147
+ import chalk4 from "chalk";
3148
+ var accent2 = chalk4.hex("#8B5CF6");
3149
+ var soft = chalk4.hex("#A78BFA");
3150
+ var keyColor = chalk4.hex("#C4B5FD");
3151
+ var stringColor = chalk4.hex("#86EFAC");
3152
+ var numberColor = chalk4.hex("#FCD34D");
3153
+ var literalColor = chalk4.hex("#F9A8D4");
3154
+ var SPEC_SECTIONS = [
3155
+ "identity",
3156
+ "behavior",
3157
+ "examples",
3158
+ "training",
3159
+ "evaluation",
3160
+ "runtime",
3161
+ "pipeline"
3162
+ ];
3163
+ var SECTION_ALIASES = {
3164
+ id: "identity",
3165
+ about: "identity",
3166
+ prompt: "behavior",
3167
+ system: "behavior",
3168
+ guidelines: "behavior",
3169
+ constraints: "behavior",
3170
+ example: "examples",
3171
+ data: "examples",
3172
+ hyperparameters: "training",
3173
+ hparams: "training",
3174
+ foundation: "training",
3175
+ train: "training",
3176
+ eval: "evaluation",
3177
+ gpu: "runtime"
3178
+ };
3179
+ function resolveSpecSection(name) {
3180
+ const normalized = name.trim().toLowerCase();
3181
+ if (SPEC_SECTIONS.includes(normalized)) return normalized;
3182
+ return SECTION_ALIASES[normalized];
3183
+ }
3184
+ var SECTION_BLURBS = {
3185
+ identity: "what this model is",
3186
+ behavior: "what the model should do",
3187
+ examples: "teaches by demonstration",
3188
+ training: "how it is trained",
3189
+ evaluation: "how results are scored",
3190
+ runtime: "where it runs",
3191
+ pipeline: "custom step recipe"
3192
+ };
3193
+ var SECTION_KEYS = {
3194
+ identity: ["name", "description", "engine", "base_model", "id", "dataset_prebuilt"],
3195
+ behavior: ["system_prompt", "guidelines", "constraints", "decision"],
3196
+ examples: ["examples"],
3197
+ training: ["hyperparameters", "foundation"],
3198
+ evaluation: ["evaluation"],
3199
+ runtime: ["runtime"],
3200
+ pipeline: ["pipeline"]
3201
+ };
3202
+ var KNOWN_KEYS = new Set(Object.values(SECTION_KEYS).flat());
3203
+ function isObject2(value) {
3204
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3205
+ }
3206
+ function clean(value) {
3207
+ return sanitizeTerminalText(value);
3208
+ }
3209
+ function truncateText(value, width) {
3210
+ const flat = clean(value).replace(/\s+/g, " ").trim();
3211
+ if (terminalWidth(flat) <= width) return flat;
3212
+ let out = "";
3213
+ for (const char of flat) {
3214
+ if (terminalWidth(out + char) > width - 1) break;
3215
+ out += char;
3216
+ }
3217
+ return `${out}\u2026`;
3218
+ }
3219
+ function hintLines(items, width) {
3220
+ return joinFitting(items, width).map((line) => chalk4.dim(line));
3221
+ }
3222
+ function joinFitting(items, width) {
3223
+ const lines = [];
3224
+ let current = "";
3225
+ for (const item of items) {
3226
+ const next = current ? `${current} \xB7 ${item}` : item;
3227
+ if (current && terminalWidth(next) > width) {
3228
+ lines.push(current);
3229
+ current = item;
3230
+ } else {
3231
+ current = next;
3232
+ }
3233
+ }
3234
+ if (current) lines.push(current);
3235
+ return lines;
3236
+ }
3237
+ function formatScalar(value) {
3238
+ if (typeof value === "string") return stringColor(clean(value));
3239
+ if (typeof value === "number") return numberColor(String(value));
3240
+ if (typeof value === "boolean" || value === null) return literalColor(String(value));
3241
+ return highlightJson(JSON.stringify(value));
3242
+ }
3243
+ function flattenSettings(value, prefix = "") {
3244
+ if (!isObject2(value)) return [[prefix || "value", value]];
3245
+ const rows2 = [];
3246
+ for (const [key, child] of Object.entries(value)) {
3247
+ const path = prefix ? `${prefix}.${key}` : key;
3248
+ if (isObject2(child) && Object.keys(child).length > 0) rows2.push(...flattenSettings(child, path));
3249
+ else rows2.push([path, child]);
3250
+ }
3251
+ return rows2;
3252
+ }
3253
+ function highlightJson(source) {
3254
+ const token = /("(?:[^"\\]|\\.)*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false|null)\b|([{}[\],])/g;
3255
+ return clean(source).replace(token, (match, string, colon, number2, literal, punctuation) => {
3256
+ if (string !== void 0) {
3257
+ return colon !== void 0 ? `${keyColor(string)}${chalk4.dim(colon)}` : stringColor(string);
3258
+ }
3259
+ if (number2 !== void 0) return numberColor(number2);
3260
+ if (literal !== void 0) return literalColor(literal);
3261
+ if (punctuation !== void 0) return chalk4.dim(punctuation);
3262
+ return match;
3263
+ });
3264
+ }
3265
+ function highlightSpecDiff(diff) {
3266
+ return diff.split("\n").map((line) => {
3267
+ if (line.startsWith("@@")) return accent2.bold(line);
3268
+ if (line.startsWith("+")) return chalk4.green(line);
3269
+ if (line.startsWith("-")) return chalk4.red(line);
3270
+ return line;
3271
+ }).join("\n");
3272
+ }
3273
+ var ViewBuilder = class {
3274
+ constructor(width) {
3275
+ this.width = width;
3276
+ }
3277
+ lines = [];
3278
+ push(line = "") {
3279
+ this.lines.push(line);
3280
+ }
3281
+ /** Wrap text under a fixed indent and optional gutter marker. */
3282
+ paragraph(text2, indent, style = (value) => value, maxLines) {
3283
+ const body = this.width - terminalWidth(indent);
3284
+ const wrapped = clean(text2).split(/\r?\n/).flatMap((line) => line.trim() ? wrapTerminalLine(line, Math.max(20, body)) : [""]);
3285
+ const shown = maxLines && wrapped.length > maxLines ? wrapped.slice(0, maxLines) : wrapped;
3286
+ for (const line of shown) this.push(`${indent}${style(line)}`);
3287
+ if (shown.length < wrapped.length) {
3288
+ this.push(`${indent}${chalk4.dim(`\u2026 ${wrapped.length - shown.length} more lines`)}`);
3289
+ }
3290
+ }
3291
+ heading(section, detail) {
3292
+ const title = section.toUpperCase();
3293
+ const room = this.width - title.length - 3;
3294
+ const extra = detail && room >= 8 ? ` ${chalk4.dim(truncateText(detail, room))}` : "";
3295
+ this.push("");
3296
+ this.push(`${accent2("\u258D")}${accent2.bold(title)}${extra}`);
3297
+ }
3298
+ settingsGrid(entries, indent = " ") {
3299
+ if (entries.length === 0) return;
3300
+ const rows2 = entries.map(([key, value]) => [clean(key), value]);
3301
+ const compact = rows2.every(([, value]) => value === null || ["number", "boolean"].includes(typeof value) || typeof value === "string" && value.length <= 16);
3302
+ if (compact && rows2.length > 4) {
3303
+ const keyWidth2 = Math.max(...rows2.map(([key]) => key.length));
3304
+ const valueWidth = Math.max(...rows2.map(([, value]) => String(value).length));
3305
+ const cell = keyWidth2 + 2 + valueWidth;
3306
+ const columns = Math.min(3, Math.floor((this.width - indent.length + 4) / (cell + 4)));
3307
+ if (columns >= 2) {
3308
+ const height = Math.ceil(rows2.length / columns);
3309
+ for (let row = 0; row < height; row += 1) {
3310
+ const cells = [];
3311
+ for (let column = 0; column < columns; column += 1) {
3312
+ const entry = rows2[column * height + row];
3313
+ if (!entry) continue;
3314
+ const [key, value] = entry;
3315
+ const text2 = String(value);
3316
+ cells.push(`${keyColor(key.padEnd(keyWidth2))} ${formatScalar(typeof value === "string" ? clean(value) : value)}${" ".repeat(valueWidth - text2.length)}`);
3317
+ }
3318
+ this.push(`${indent}${cells.join(" ").trimEnd()}`);
3319
+ }
3320
+ return;
3321
+ }
3322
+ }
3323
+ const keyWidth = Math.min(28, Math.max(...rows2.map(([key]) => key.length)));
3324
+ for (const [key, value] of rows2) {
3325
+ const label = key.length > keyWidth ? truncateText(key, keyWidth) : key.padEnd(keyWidth);
3326
+ const rendered = typeof value === "string" ? stringColor(truncateText(value, Math.max(20, this.width - keyWidth - indent.length - 2))) : formatScalar(value);
3327
+ this.push(`${indent}${keyColor(label)} ${rendered}`);
3328
+ }
3329
+ }
3330
+ };
3331
+ function stringList(value) {
3332
+ return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item : JSON.stringify(item)) : [];
3333
+ }
3334
+ function exampleText(value) {
3335
+ if (typeof value === "string") return value;
3336
+ if (value === void 0) return "";
3337
+ return JSON.stringify(value);
3338
+ }
3339
+ function renderIdentity(view, spec, full) {
3340
+ if (!full) return;
3341
+ view.heading("identity", SECTION_BLURBS.identity);
3342
+ view.settingsGrid(SECTION_KEYS.identity.filter((key) => spec[key] !== void 0).map((key) => [key, spec[key]]));
3343
+ if (spec.engine === void 0) view.push(` ${chalk4.dim("engine defaults to adapter (LoRA on base_model)")}`);
3344
+ }
3345
+ function renderBehavior(view, spec, full) {
3346
+ const guidelines = stringList(spec.guidelines);
3347
+ const constraints = stringList(spec.constraints);
3348
+ view.heading("behavior", SECTION_BLURBS.behavior);
3349
+ view.push(` ${chalk4.bold("System prompt")}`);
3350
+ if (typeof spec.system_prompt === "string" && spec.system_prompt.trim()) {
3351
+ view.paragraph(spec.system_prompt, ` ${soft("\u2502")} `, chalk4.italic, full ? void 0 : 6);
3352
+ } else {
3353
+ view.push(` ${soft("\u2502")} ${chalk4.dim("(none)")}`);
3354
+ }
3355
+ const renderList = (label, items, mark) => {
3356
+ view.push(` ${chalk4.bold(label)} ${chalk4.dim(String(items.length))}`);
3357
+ if (items.length === 0) {
3358
+ view.push(` ${chalk4.dim("(none)")}`);
3359
+ return;
3360
+ }
3361
+ const limit = full ? items.length : 5;
3362
+ for (const item of items.slice(0, limit)) {
3363
+ const before = view.lines.length;
3364
+ view.paragraph(item, " ", void 0, full ? void 0 : 2);
3365
+ view.lines[before] = ` ${mark} ${view.lines[before].slice(6)}`;
3366
+ }
3367
+ if (items.length > limit) view.push(` ${chalk4.dim(`\u2026 ${items.length - limit} more \xB7 /spec behavior`)}`);
3368
+ };
3369
+ if (isObject2(spec.decision)) {
3370
+ view.push(` ${chalk4.bold("Decision question")}`);
3371
+ view.settingsGrid(flattenSettings(spec.decision));
3372
+ }
3373
+ renderList("Guidelines", guidelines, chalk4.green("\u2713"));
3374
+ renderList("Constraints", constraints, chalk4.red("\u2717"));
3375
+ }
3376
+ function renderExamples(view, spec, full) {
3377
+ const examples = Array.isArray(spec.examples) ? spec.examples : [];
3378
+ const count = examples.length;
3379
+ const size = count === 0 ? "none yet" : `${count} example${count === 1 ? "" : "s"}`;
3380
+ view.heading("examples", `${size} \xB7 ${SECTION_BLURBS.examples}`);
3381
+ if (count === 0) {
3382
+ view.push(` ${chalk4.dim("Ask TT to draft examples, or add input/output pairs to tunedtensor.json.")}`);
3383
+ return;
3384
+ }
3385
+ const limit = full ? count : 3;
3386
+ const number2 = String(Math.min(count, limit)).length;
3387
+ const textWidth = Math.max(20, view.width - number2 - 10);
3388
+ examples.slice(0, limit).forEach((example, index) => {
3389
+ const record4 = isObject2(example) ? example : { input: example };
3390
+ const label = chalk4.dim(String(index + 1).padStart(number2));
3391
+ const pad = " ".repeat(number2);
3392
+ if (full) {
3393
+ const before = view.lines.length;
3394
+ view.paragraph(exampleText(record4.input), ` ${pad} ${chalk4.cyan("in ")} `);
3395
+ view.lines[before] = view.lines[before].replace(` ${pad}`, ` ${label}`);
3396
+ view.paragraph(exampleText(record4.output), ` ${pad} ${chalk4.green("out")} `, stringColor);
3397
+ view.push("");
3398
+ } else {
3399
+ view.push(` ${label} ${chalk4.cyan("in ")} ${truncateText(exampleText(record4.input), textWidth)}`);
3400
+ view.push(` ${pad} ${chalk4.green("out")} ${stringColor(truncateText(exampleText(record4.output), textWidth))}`);
3401
+ }
3402
+ });
3403
+ if (full && view.lines.at(-1) === "") view.lines.pop();
3404
+ if (count > limit) view.push(` ${chalk4.dim(`\u2026 ${count - limit} more \xB7 /spec examples shows all`)}`);
3405
+ }
3406
+ function renderTraining(view, spec) {
3407
+ const foundation = spec.engine === "foundation";
3408
+ const settings = foundation ? spec.foundation : spec.hyperparameters;
3409
+ 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"}`);
3410
+ const rows2 = isObject2(settings) ? flattenSettings(settings) : [];
3411
+ if (rows2.length === 0) {
3412
+ view.push(` ${chalk4.dim("Defaults \u2014 no overrides set.")}`);
3413
+ return;
3414
+ }
3415
+ view.settingsGrid(rows2);
3416
+ const other = foundation ? spec.hyperparameters : spec.foundation;
3417
+ if (isObject2(other)) view.push(` ${chalk4.yellow("!")} ${chalk4.dim(`${foundation ? "hyperparameters" : "foundation"} is set but unused by this engine`)}`);
3418
+ }
3419
+ function renderSettingsSection(view, spec, section, full) {
3420
+ const value = spec[section];
3421
+ if (value === void 0) {
3422
+ if (full) {
3423
+ view.heading(section, SECTION_BLURBS[section]);
3424
+ 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.")}`);
3425
+ }
3426
+ return;
3427
+ }
3428
+ if (section === "pipeline" && !full) {
3429
+ const steps = isObject2(value) && Array.isArray(value.steps) ? value.steps.length : void 0;
3430
+ view.heading(section, SECTION_BLURBS[section]);
3431
+ view.push(` ${steps === void 0 ? "custom recipe" : `${steps} custom step${steps === 1 ? "" : "s"}`} ${chalk4.dim("\xB7 /spec pipeline")}`);
3432
+ return;
3433
+ }
3434
+ view.heading(section, SECTION_BLURBS[section]);
3435
+ if (section === "pipeline") {
3436
+ for (const line of JSON.stringify(value, null, 2).split("\n")) view.push(` ${highlightJson(line)}`);
3437
+ return;
3438
+ }
3439
+ view.settingsGrid(flattenSettings(value));
3440
+ }
3441
+ function renderOther(view, spec) {
3442
+ const rows2 = Object.entries(spec).filter(([key]) => !KNOWN_KEYS.has(key));
3443
+ if (rows2.length === 0) return;
3444
+ view.push("");
3445
+ view.push(`${accent2("\u258D")}${accent2.bold("OTHER")}`);
3446
+ view.settingsGrid(rows2);
3447
+ }
3448
+ function renderHeader(view, input, spec) {
3449
+ const name = typeof spec.name === "string" && spec.name.trim() ? clean(spec.name) : "Unnamed spec";
3450
+ const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red(`\u2717 ${input.validation.errors.length} issue${input.validation.errors.length === 1 ? "" : "s"}`);
3451
+ const gap = Math.max(2, view.width - terminalWidth(name) - terminalWidth(status2));
3452
+ view.push(`${chalk4.bold(name)}${" ".repeat(gap)}${status2}`);
3453
+ if (typeof spec.description === "string" && spec.description.trim()) {
3454
+ view.paragraph(spec.description, "", chalk4.dim, 3);
3455
+ }
3456
+ const examples = Array.isArray(spec.examples) ? spec.examples.length : 0;
3457
+ const facts = [
3458
+ spec.engine === "foundation" ? "foundation" : spec.engine === "decision" ? "decision" : "adapter",
3459
+ typeof spec.base_model === "string" ? truncateText(spec.base_model, view.width) : void 0,
3460
+ `${examples} example${examples === 1 ? "" : "s"}`,
3461
+ isObject2(spec.runtime) && isObject2(spec.runtime.gpu) ? "remote GPU" : void 0
3462
+ ].filter((fact) => Boolean(fact));
3463
+ view.lines.push(...joinFitting(facts, view.width));
3464
+ view.lines.push(...hintLines([truncateText(input.displayPath, view.width), `sha256 ${input.sha256.slice(0, 12)}`], view.width));
3465
+ }
3466
+ function renderDiagnostics(view, input) {
3467
+ const { errors, warnings, valid } = input.validation;
3468
+ if (valid && warnings.length === 0) return;
3469
+ view.push("");
3470
+ const marked = (text2, mark, style) => {
3471
+ const before = view.lines.length;
3472
+ view.paragraph(text2, " ", style);
3473
+ view.lines[before] = `${mark} ${view.lines[before].slice(2)}`;
3474
+ };
3475
+ for (const error of errors) marked(error, chalk4.red("\u2717"), chalk4.red);
3476
+ for (const warning of warnings) marked(warning, chalk4.yellow("!"), chalk4.yellow);
3477
+ }
3478
+ function renderInvalid(view, input) {
3479
+ view.push(`${chalk4.red("\u2717")} ${chalk4.bold(clean(input.displayPath))} ${chalk4.dim("could not be parsed")}`);
3480
+ for (const error of input.validation.errors) view.paragraph(error, " ", chalk4.red);
3481
+ view.push("");
3482
+ for (const line of clean(input.source).split("\n")) view.push(` ${chalk4.dim(line)}`);
3483
+ return `${view.lines.join("\n")}
3484
+ `;
3485
+ }
3486
+ function renderSpecView(input, options = {}) {
3487
+ const width = Math.max(40, Math.min(options.columns ?? 88, 88));
3488
+ const view = new ViewBuilder(width);
3489
+ if (!isObject2(input.document)) return renderInvalid(view, input);
3490
+ const spec = input.document;
3491
+ const section = options.section;
3492
+ if (!section) {
3493
+ renderHeader(view, input, spec);
3494
+ renderBehavior(view, spec, false);
3495
+ renderExamples(view, spec, false);
3496
+ renderTraining(view, spec);
3497
+ renderSettingsSection(view, spec, "evaluation", false);
3498
+ renderSettingsSection(view, spec, "runtime", false);
3499
+ renderSettingsSection(view, spec, "pipeline", false);
3500
+ renderOther(view, spec);
3501
+ renderDiagnostics(view, input);
3502
+ view.push("");
3503
+ view.lines.push(...hintLines(["/spec <section> for detail", "/spec show for JSON", "/spec diff", "/spec history"], width));
3504
+ view.lines.push(...wrapTerminalLine(`sections: ${SPEC_SECTIONS.join(", ")}`, width).map((line) => chalk4.dim(line)));
3505
+ return `${view.lines.join("\n")}
3506
+ `;
3507
+ }
3508
+ switch (section) {
3509
+ case "identity":
3510
+ renderIdentity(view, spec, true);
3511
+ break;
3512
+ case "behavior":
3513
+ renderBehavior(view, spec, true);
3514
+ break;
3515
+ case "examples":
3516
+ renderExamples(view, spec, true);
3517
+ break;
3518
+ case "training":
3519
+ renderTraining(view, spec);
3520
+ break;
3521
+ default:
3522
+ renderSettingsSection(view, spec, section, true);
3523
+ }
3524
+ view.lines.shift();
3525
+ return `${view.lines.join("\n")}
3526
+ `;
3527
+ }
3528
+ function renderSpecSource(input) {
3529
+ const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red("\u2717 invalid");
3530
+ const lines = [
3531
+ `${chalk4.bold(clean(input.displayPath))} ${chalk4.dim(`\xB7 sha256 ${input.sha256.slice(0, 12)}`)} ${status2}`,
3532
+ highlightJson(input.source.replace(/\s+$/, "")),
3533
+ ...input.validation.errors.map((error) => chalk4.red(`\u2717 ${clean(error)}`)),
3534
+ ...input.validation.warnings.map((warning) => chalk4.yellow(`! ${clean(warning)}`))
3535
+ ];
3536
+ return `${lines.join("\n")}
3537
+ `;
3538
+ }
3539
+
3034
3540
  // src/commands/spec.ts
3035
3541
  async function reviewSpec(workspaceRoot, operation = "show", specPath = "tunedtensor.json") {
3036
3542
  if (!["show", "validate", "diff", "history"].includes(operation)) {
@@ -3083,8 +3589,14 @@ ${label}
3083
3589
  ${diff}` };
3084
3590
  }
3085
3591
  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);
3592
+ 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) => {
3593
+ const section = resolveSpecSection(operation);
3594
+ if ((section || operation === "view") && !isJsonMode()) {
3595
+ const spec = await inspectLocalSpec(cwd, path);
3596
+ process.stdout.write(renderSpecView(spec, { section, columns: process.stdout.columns }));
3597
+ return;
3598
+ }
3599
+ const result2 = await reviewSpec(cwd, section || operation === "view" ? "show" : operation, path);
3088
3600
  if (isJsonMode()) {
3089
3601
  const { text: _text, ...data3 } = result2;
3090
3602
  printJson(data3);
@@ -3097,10 +3609,487 @@ function registerSpecCommands(program, cwd) {
3097
3609
  });
3098
3610
  }
3099
3611
 
3612
+ // src/shell-view.ts
3613
+ import chalk5 from "chalk";
3614
+ var accent3 = chalk5.hex("#8B5CF6");
3615
+ var GIB = 1024 ** 3;
3616
+ var BAR_WIDTH = 10;
3617
+ var MAX_PANEL_WIDTH = 88;
3618
+ function panelWidth(columns) {
3619
+ return Math.max(40, Math.min(columns ?? 80, MAX_PANEL_WIDTH));
3620
+ }
3621
+ function formatGiB(bytes) {
3622
+ const amount = bytes / GIB;
3623
+ return amount >= 100 ? amount.toFixed(0) : amount.toFixed(1);
3624
+ }
3625
+ function loadColor(percent2) {
3626
+ if (percent2 >= 85) return chalk5.red;
3627
+ if (percent2 >= 60) return chalk5.yellow;
3628
+ return chalk5.green;
3629
+ }
3630
+ function usageBar(percent2, width = BAR_WIDTH) {
3631
+ if (percent2 === void 0) return chalk5.dim("\xB7".repeat(width));
3632
+ const clamped = Math.max(0, Math.min(100, percent2));
3633
+ const filled = Math.round(clamped / 100 * width);
3634
+ return `${loadColor(clamped)("\u2588".repeat(filled))}${chalk5.dim("\u2591".repeat(width - filled))}`;
3635
+ }
3636
+ function percentLabel(percent2) {
3637
+ if (percent2 === void 0) return chalk5.dim(" n/a");
3638
+ return loadColor(percent2)(`${String(Math.round(percent2)).padStart(3)}%`).padStart(4);
3639
+ }
3640
+ function sectionRule(title, width, detail) {
3641
+ const label = `${accent3.bold(title)}${detail ? ` ${chalk5.dim(detail)}` : ""}`;
3642
+ const rule = Math.max(3, width - terminalWidth(label) - 1);
3643
+ return `${label} ${chalk5.dim("\u2500".repeat(rule))}`;
3644
+ }
3645
+ function inventoryFromUsage(usage) {
3646
+ const nvidia = usage.gpus.filter((gpu) => gpu.vendor === "nvidia");
3647
+ return {
3648
+ collected_at: usage.sampled_at,
3649
+ quick: true,
3650
+ node: { version: process.versions.node, major: Number(process.versions.node.split(".")[0]), ok: true },
3651
+ os: {
3652
+ platform: process.platform,
3653
+ arch: process.arch,
3654
+ type: process.platform,
3655
+ cpu_count: usage.cpu.cores,
3656
+ total_memory_bytes: usage.memory.total_bytes,
3657
+ free_memory_bytes: usage.memory.total_bytes - usage.memory.used_bytes
3658
+ },
3659
+ gpus: nvidia.map((gpu) => ({
3660
+ index: gpu.index,
3661
+ name: gpu.name,
3662
+ memory_total_bytes: gpu.memory_total_bytes,
3663
+ 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,
3664
+ unified_memory: gpu.unified_memory
3665
+ })),
3666
+ nvidia_smi: { ok: nvidia.length > 0, message: usage.gpu_note ?? "" },
3667
+ disks: []
3668
+ };
3669
+ }
3670
+ function shortModel(id, width = 24) {
3671
+ return truncateText(id.split("/").at(-1) ?? id, width);
3672
+ }
3673
+ var STATUS_WORD = {
3674
+ ready: "ready",
3675
+ tight: "tight",
3676
+ not_possible: "not possible"
3677
+ };
3678
+ function describeFineTuneFit(usage, context) {
3679
+ const spec = context.spec;
3680
+ if (spec?.remoteGpu) {
3681
+ return {
3682
+ status: "remote",
3683
+ text: `training runs on AWS ${spec.remoteGpu} (runtime.gpu)`,
3684
+ source: "tt doctor checks it"
3685
+ };
3686
+ }
3687
+ const snapshot = context.host && !context.host.stale ? context.host.capabilities : void 0;
3688
+ let report = snapshot;
3689
+ const source = snapshot ? "from tt hardware" : "quick check \xB7 tt hardware for a full probe";
3690
+ if (!report && usage) report = evaluateCapabilities(inventoryFromUsage(usage));
3691
+ if (!report) return { status: "unknown", text: "not checked yet", source: "run tt hardware" };
3692
+ if (spec?.engine === "decision") {
3693
+ return {
3694
+ status: "unknown",
3695
+ text: "decision training runs locally on CPU, MPS or CUDA",
3696
+ source: "device selected by hyperparameters.device"
3697
+ };
3698
+ }
3699
+ const noCuda = !report.cuda_available;
3700
+ if (spec?.engine === "foundation") {
3701
+ const verdict = report.foundation.train;
3702
+ return {
3703
+ status: verdict.status,
3704
+ 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}`,
3705
+ source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
3706
+ };
3707
+ }
3708
+ if (spec?.baseModel) {
3709
+ const adapter = report.adapters.find((item) => item.id.toLowerCase() === spec.baseModel.toLowerCase());
3710
+ if (!adapter) {
3711
+ return {
3712
+ status: "unknown",
3713
+ text: `no certified memory profile for ${shortModel(spec.baseModel, 32)}`,
3714
+ source: "tt hardware lists supported base models"
3715
+ };
3716
+ }
3717
+ const verdict = adapter.finetune;
3718
+ return {
3719
+ status: verdict.status,
3720
+ text: `LoRA ${shortModel(adapter.id)} ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}`,
3721
+ source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
3722
+ };
3723
+ }
3724
+ const ready = report.adapters.filter((item) => item.finetune.status !== "not_possible");
3725
+ if (ready.length === 0) {
3726
+ return {
3727
+ status: "not_possible",
3728
+ text: noCuda ? "no local CUDA GPU for LoRA training" : "no certified base model fits this GPU",
3729
+ source: noCuda ? "runtime.gpu can use your AWS GPU" : source
3730
+ };
3731
+ }
3732
+ const best = ready.some((item) => item.finetune.status === "ready") ? "ready" : "tight";
3733
+ return {
3734
+ status: best,
3735
+ text: `LoRA ${best} for ${ready.map((item) => shortModel(item.id, 20)).join(", ")}`,
3736
+ source
3737
+ };
3738
+ }
3739
+ function fitMark(status2) {
3740
+ switch (status2) {
3741
+ case "ready":
3742
+ return chalk5.green("\u2713");
3743
+ case "tight":
3744
+ return chalk5.yellow("!");
3745
+ case "not_possible":
3746
+ return chalk5.red("\u2717");
3747
+ case "remote":
3748
+ return accent3("\u2197");
3749
+ default:
3750
+ return chalk5.dim("?");
3751
+ }
3752
+ }
3753
+ function gpuDetail(gpu) {
3754
+ const parts = [];
3755
+ if (gpu.vendor === "apple") {
3756
+ parts.push(`${formatGiB(gpu.memory_total_bytes ?? 0)} GiB unified \xB7 Metal`);
3757
+ } else if (gpu.memory_total_bytes !== void 0) {
3758
+ const used = gpu.memory_used_bytes;
3759
+ parts.push(used === void 0 ? `${formatGiB(gpu.memory_total_bytes)} GiB VRAM` : `VRAM ${formatGiB(used)}/${formatGiB(gpu.memory_total_bytes)} GiB`);
3760
+ } else if (gpu.unified_memory) {
3761
+ parts.push("unified with system RAM");
3762
+ }
3763
+ if (gpu.temperature_c !== void 0) parts.push(`${Math.round(gpu.temperature_c)}\xB0C`);
3764
+ return parts.join(" ");
3765
+ }
3766
+ function renderMeterRows(rows2, width) {
3767
+ const detailWidth = Math.max(0, ...rows2.map((row) => terminalWidth(row.detail)));
3768
+ const fixed = 6 + 1 + 1 + BAR_WIDTH + 1 + 4 + 2;
3769
+ const nameWidth = Math.max(6, Math.min(28, width - fixed - detailWidth));
3770
+ const detailRoom = Math.max(8, width - fixed - nameWidth);
3771
+ return rows2.map((row) => {
3772
+ const nameCell = truncateText(row.name, nameWidth).padEnd(nameWidth);
3773
+ return `${chalk5.bold(row.label.padEnd(6))} ${nameCell} ${usageBar(row.percent)} ${percentLabel(row.percent)} ${chalk5.dim(truncateText(row.detail, detailRoom))}`;
3774
+ });
3775
+ }
3776
+ function renderMachinePanel(usage, context, options = {}) {
3777
+ const width = panelWidth(options.columns);
3778
+ const lines = [sectionRule("MACHINE", width)];
3779
+ if (usage.gpus.length === 0) {
3780
+ const reason = usage.gpu_note ? ` (${usage.gpu_note})` : "";
3781
+ lines.push(`${chalk5.bold("GPU".padEnd(6))} ${chalk5.dim(truncateText(`no NVIDIA GPU detected${reason}`, width - 7))}`);
3782
+ }
3783
+ const meters = usage.gpus.map((gpu) => ({
3784
+ label: usage.gpus.length > 1 ? `GPU ${gpu.index}` : "GPU",
3785
+ // "NVIDIA GeForce RTX 4090" reads better as "GeForce RTX 4090" in a narrow column.
3786
+ name: sanitizeTerminalText(gpu.name).replace(/^NVIDIA\s+(?=\S)/, ""),
3787
+ percent: gpu.utilization_percent,
3788
+ detail: gpuDetail(gpu)
3789
+ }));
3790
+ meters.push({
3791
+ label: "CPU",
3792
+ name: `${usage.cpu.cores}\xD7 ${sanitizeTerminalText(usage.cpu.model)}`,
3793
+ percent: usage.cpu.utilization_percent,
3794
+ detail: `load ${usage.cpu.load_average[0].toFixed(2)}`
3795
+ });
3796
+ meters.push({
3797
+ label: "RAM",
3798
+ name: "system memory",
3799
+ percent: usage.memory.total_bytes > 0 ? usage.memory.used_bytes / usage.memory.total_bytes * 100 : void 0,
3800
+ detail: `${formatGiB(usage.memory.used_bytes)}/${formatGiB(usage.memory.total_bytes)} GiB`
3801
+ });
3802
+ if (usage.disk && usage.disk.total_bytes > 0) {
3803
+ meters.push({
3804
+ label: "Disk",
3805
+ name: "model cache",
3806
+ percent: (usage.disk.total_bytes - usage.disk.free_bytes) / usage.disk.total_bytes * 100,
3807
+ detail: `${formatGiB(usage.disk.free_bytes)} GiB free`
3808
+ });
3809
+ }
3810
+ lines.push(...renderMeterRows(meters, width));
3811
+ const fit = describeFineTuneFit(usage, context);
3812
+ lines.push(`${chalk5.bold("Tune".padEnd(6))} ${fitMark(fit.status)} ${truncateText(fit.text, width - 9)}`);
3813
+ if (fit.source) lines.push(`${" ".repeat(9)}${chalk5.dim(truncateText(fit.source, width - 9))}`);
3814
+ if (options.detailed) {
3815
+ const report = context.host && !context.host.stale ? context.host.capabilities : evaluateCapabilities(inventoryFromUsage(usage));
3816
+ if (report) {
3817
+ lines.push("", sectionRule("CERTIFIED BASE MODELS", width, "LoRA fine-tune"));
3818
+ const idWidth = Math.min(44, Math.floor(width * 0.55));
3819
+ const reasonWidth = Math.max(10, width - idWidth - 3);
3820
+ const row = (status2, label, reason) => `${fitMark(status2)} ${truncateText(label, idWidth).padEnd(idWidth)} ${chalk5.dim(truncateText(reason, reasonWidth))}`;
3821
+ for (const adapter of report.adapters) {
3822
+ lines.push(row(adapter.finetune.status, adapter.id, adapter.finetune.reason));
3823
+ }
3824
+ const foundation = report.foundation.train;
3825
+ lines.push(row(
3826
+ foundation.status,
3827
+ "foundation engine (from scratch)",
3828
+ foundation.status === "not_possible" ? foundation.reason : `max depth ${report.foundation.suggested_max_depth}`
3829
+ ));
3830
+ }
3831
+ lines.push("");
3832
+ 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";
3833
+ lines.push(...wrapTerminalLine(note, width).map((line) => chalk5.dim(line)));
3834
+ }
3835
+ return lines;
3836
+ }
3837
+ function agentLabel(context) {
3838
+ const agent = context.agent;
3839
+ if (!agent?.provider || !agent.model) return void 0;
3840
+ return `${agent.provider}/${agent.model}`;
3841
+ }
3842
+ function bannerTip(input) {
3843
+ const { context, usage } = input;
3844
+ const agent = agentLabel(context);
3845
+ if (!agent) {
3846
+ return "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now.";
3847
+ }
3848
+ if (!context.spec) {
3849
+ return "No tunedtensor.json here yet. Describe the model you want and I'll draft a spec, or run init.";
3850
+ }
3851
+ if (context.spec.parseError) {
3852
+ return "tunedtensor.json doesn't parse. /spec show points at the problem.";
3853
+ }
3854
+ const busy = usage?.gpus.find((gpu) => (gpu.utilization_percent ?? 0) >= 85);
3855
+ if (busy) {
3856
+ return `${sanitizeTerminalText(busy.name)} is ${busy.utilization_percent}% busy; training now may be slow.`;
3857
+ }
3858
+ const examples = context.spec.exampleCount ?? 0;
3859
+ if (examples < 20) {
3860
+ return `${examples} example${examples === 1 ? "" : "s"} is a small set. Ask me to draft more, or /spec examples to review them.`;
3861
+ }
3862
+ return "Ask TT anything. Known commands run directly.";
3863
+ }
3864
+ function specLine(context) {
3865
+ const spec = context.spec;
3866
+ if (!spec) return `${chalk5.dim("spec")} ${chalk5.yellow("none here")} ${chalk5.dim("\xB7 init creates one")}`;
3867
+ if (spec.parseError) return `${chalk5.dim("spec")} ${chalk5.red("tunedtensor.json is invalid JSON")}`;
3868
+ const name = sanitizeTerminalText(spec.name ?? "unnamed");
3869
+ const facts = [
3870
+ spec.engine === "foundation" ? "foundation" : spec.baseModel ? sanitizeTerminalText(spec.baseModel) : void 0,
3871
+ spec.exampleCount === void 0 ? void 0 : `${spec.exampleCount} examples`
3872
+ ].filter(Boolean).join(" \xB7 ");
3873
+ return `${chalk5.dim("spec")} ${chalk5.bold(name)}${facts ? chalk5.dim(` \xB7 ${facts}`) : ""}`;
3874
+ }
3875
+ function renderBanner(input) {
3876
+ const width = panelWidth(input.columns);
3877
+ const { context } = input;
3878
+ const tip = bannerTip(input);
3879
+ const heading = [
3880
+ `${accent3.bold("tt")}${input.version ? ` ${chalk5.dim(`v${input.version}`)}` : ""} ${chalk5.dim("Tuned Tensor")}`,
3881
+ specLine(context),
3882
+ chalk5.dim(`agent ${agentLabel(context) ?? "not configured"} \xB7 workflow model ${input.activeModel}`)
3883
+ ];
3884
+ const mascot = renderMascot();
3885
+ const textWidth = width - MASCOT_WIDTH - 1;
3886
+ const offset = mascot.length - heading.length;
3887
+ const lines = mascot.map((row, index) => {
3888
+ const text2 = heading[index - offset] ?? "";
3889
+ return `${row} ${terminalWidth(text2) > textWidth ? truncateText(text2, textWidth) : text2}`;
3890
+ });
3891
+ if (input.usage) {
3892
+ lines.push("", ...renderMachinePanel(input.usage, context, { columns: input.columns }));
3893
+ }
3894
+ const prefix = mascotSays("");
3895
+ const hang = terminalWidth(prefix);
3896
+ const wrapped = wrapTerminalLine(tip, Math.max(20, width - hang));
3897
+ lines.push("", `${prefix}${wrapped[0]}`, ...wrapped.slice(1).map((line) => `${" ".repeat(hang)}${line}`));
3898
+ lines.push(...hintLines([
3899
+ "/spec review",
3900
+ "/system machine",
3901
+ "/help commands",
3902
+ "tab complete",
3903
+ "ctrl+c stop/clear",
3904
+ "ctrl+d exit"
3905
+ ], width));
3906
+ return `${lines.join("\n")}
3907
+
3908
+ `;
3909
+ }
3910
+
3911
+ // src/local-runtime/live-usage.ts
3912
+ import { execFile } from "child_process";
3913
+ import { readFileSync as readFileSync5 } from "fs";
3914
+ import { statfs } from "fs/promises";
3915
+ import { cpus, freemem, homedir, loadavg, totalmem } from "os";
3916
+ import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
3917
+ function cpuTimes() {
3918
+ let idle = 0;
3919
+ let total = 0;
3920
+ for (const cpu of cpus()) {
3921
+ const times = cpu.times;
3922
+ idle += times.idle;
3923
+ total += times.user + times.nice + times.sys + times.irq + times.idle;
3924
+ }
3925
+ return { idle, total };
3926
+ }
3927
+ function cpuUtilization(before, after) {
3928
+ const total = after.total - before.total;
3929
+ if (!(total > 0)) return void 0;
3930
+ const busy = total - (after.idle - before.idle);
3931
+ return Math.min(100, Math.max(0, Math.round(busy / total * 100)));
3932
+ }
3933
+ function optionalNumber(value) {
3934
+ if (value === void 0) return void 0;
3935
+ const trimmed = value.trim();
3936
+ if (!trimmed || /^\[?n\/a\]?$/i.test(trimmed) || /not supported/i.test(trimmed)) return void 0;
3937
+ const parsed = Number(trimmed);
3938
+ return Number.isFinite(parsed) ? parsed : void 0;
3939
+ }
3940
+ var MIB = 1024 * 1024;
3941
+ var NVIDIA_USAGE_QUERY = [
3942
+ "--query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
3943
+ "--format=csv,noheader,nounits"
3944
+ ];
3945
+ function parseNvidiaUsage(stdout3) {
3946
+ const gpus = [];
3947
+ for (const line of stdout3.split(/\r?\n/).map((item) => item.trim()).filter(Boolean)) {
3948
+ const parts = line.split(",").map((part) => part.trim());
3949
+ if (parts.length < 2 || !/^\d+$/.test(parts[0] ?? "")) continue;
3950
+ const name = parts[1] || "NVIDIA GPU";
3951
+ const used = optionalNumber(parts[3]);
3952
+ const total = optionalNumber(parts[4]);
3953
+ gpus.push({
3954
+ index: Number(parts[0]),
3955
+ name,
3956
+ utilization_percent: optionalNumber(parts[2]),
3957
+ memory_used_bytes: used === void 0 ? void 0 : Math.round(used * MIB),
3958
+ memory_total_bytes: total === void 0 || total <= 0 ? void 0 : Math.round(total * MIB),
3959
+ temperature_c: optionalNumber(parts[5]),
3960
+ unified_memory: /spark|gb10/i.test(name),
3961
+ vendor: "nvidia"
3962
+ });
3963
+ }
3964
+ return gpus;
3965
+ }
3966
+ function queryNvidia(env, timeoutMs) {
3967
+ return new Promise((resolve13) => {
3968
+ execFile("nvidia-smi", NVIDIA_USAGE_QUERY, { env, timeout: timeoutMs, windowsHide: true }, (error, stdout3) => {
3969
+ if (error) {
3970
+ const code2 = error.code;
3971
+ resolve13({
3972
+ gpus: [],
3973
+ note: code2 === "ENOENT" ? "nvidia-smi not found" : error.killed ? "nvidia-smi timed out" : "nvidia-smi failed"
3974
+ });
3975
+ return;
3976
+ }
3977
+ const gpus = parseNvidiaUsage(String(stdout3));
3978
+ resolve13(gpus.length > 0 ? { gpus } : { gpus, note: "nvidia-smi listed no GPUs" });
3979
+ });
3980
+ });
3981
+ }
3982
+ function tidyCpuModel(model) {
3983
+ 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";
3984
+ }
3985
+ var ARM_CORES = {
3986
+ "0xd03": "Cortex-A53",
3987
+ "0xd05": "Cortex-A55",
3988
+ "0xd08": "Cortex-A72",
3989
+ "0xd0b": "Cortex-A76",
3990
+ "0xd0c": "Neoverse-N1",
3991
+ "0xd40": "Neoverse-V1",
3992
+ "0xd41": "Cortex-A78",
3993
+ "0xd44": "Cortex-X1",
3994
+ "0xd47": "Cortex-A710",
3995
+ "0xd48": "Cortex-X2",
3996
+ "0xd49": "Neoverse-N2",
3997
+ "0xd4d": "Cortex-A715",
3998
+ "0xd4e": "Cortex-X3",
3999
+ "0xd4f": "Neoverse-V2",
4000
+ "0xd80": "Cortex-A520",
4001
+ "0xd81": "Cortex-A720",
4002
+ "0xd82": "Cortex-X4",
4003
+ "0xd84": "Neoverse-V3",
4004
+ "0xd85": "Cortex-X925",
4005
+ "0xd87": "Cortex-A725",
4006
+ "0xd8e": "Neoverse-N3"
4007
+ };
4008
+ function armCpuModel(cpuinfo) {
4009
+ const names = [];
4010
+ let implementer;
4011
+ for (const line of cpuinfo.split(/\r?\n/)) {
4012
+ const [key, value] = line.split(":").map((item) => item.trim().toLowerCase());
4013
+ if (key === "cpu implementer") implementer = value;
4014
+ if (key !== "cpu part" || !value) continue;
4015
+ const name = implementer === "0x41" ? ARM_CORES[value] : void 0;
4016
+ if (name && !names.includes(name)) names.push(name);
4017
+ }
4018
+ return names.length > 0 ? names.join(" + ") : void 0;
4019
+ }
4020
+ function cpuModel(reported, platform, arch) {
4021
+ const model = tidyCpuModel(reported ?? "");
4022
+ if (platform !== "linux" || !/^(?:unknown|CPU)$/i.test(model)) return model;
4023
+ try {
4024
+ return armCpuModel(readFileSync5("/proc/cpuinfo", "utf8")) ?? (arch === "arm64" ? "Arm CPU" : model);
4025
+ } catch {
4026
+ return arch === "arm64" ? "Arm CPU" : model;
4027
+ }
4028
+ }
4029
+ async function modelCacheDisk(env) {
4030
+ const path = resolve2(env.HF_HOME?.trim() || join7(homedir(), ".cache", "huggingface"));
4031
+ for (let probe = path, depth = 0; depth < 8; depth += 1) {
4032
+ try {
4033
+ const fs = await statfs(probe);
4034
+ return {
4035
+ path,
4036
+ free_bytes: Number(fs.bavail) * Number(fs.bsize),
4037
+ total_bytes: Number(fs.blocks) * Number(fs.bsize)
4038
+ };
4039
+ } catch {
4040
+ const parent = dirname3(probe);
4041
+ if (parent === probe) break;
4042
+ probe = parent;
4043
+ }
4044
+ }
4045
+ return void 0;
4046
+ }
4047
+ function appleGpu(cpuModel2, platform, arch) {
4048
+ if (platform !== "darwin" || arch !== "arm64") return void 0;
4049
+ const chip = /apple/i.test(cpuModel2) ? cpuModel2.trim() : "Apple Silicon";
4050
+ return {
4051
+ index: 0,
4052
+ name: `${chip} GPU`,
4053
+ memory_total_bytes: totalmem(),
4054
+ unified_memory: true,
4055
+ vendor: "apple"
4056
+ };
4057
+ }
4058
+ async function sampleLiveUsage(options = {}) {
4059
+ const env = options.env ?? process.env;
4060
+ const platform = options.platform ?? process.platform;
4061
+ const arch = options.arch ?? process.arch;
4062
+ const before = cpuTimes();
4063
+ const [nvidia, disk] = await Promise.all([
4064
+ queryNvidia(env, options.gpuTimeoutMs ?? 2e3),
4065
+ modelCacheDisk(env).catch(() => void 0),
4066
+ new Promise((resolve13) => setTimeout(resolve13, options.cpuSampleMs ?? 200))
4067
+ ]);
4068
+ const after = cpuTimes();
4069
+ const list = cpus();
4070
+ const model = cpuModel(list[0]?.model, platform, arch);
4071
+ const total = totalmem();
4072
+ const apple = nvidia.gpus.length === 0 ? appleGpu(model, platform, arch) : void 0;
4073
+ const [one, five, fifteen] = loadavg();
4074
+ return {
4075
+ sampled_at: (/* @__PURE__ */ new Date()).toISOString(),
4076
+ cpu: {
4077
+ model,
4078
+ cores: list.length,
4079
+ utilization_percent: cpuUtilization(before, after),
4080
+ load_average: [one ?? 0, five ?? 0, fifteen ?? 0]
4081
+ },
4082
+ memory: { used_bytes: Math.max(0, total - freemem()), total_bytes: total },
4083
+ disk,
4084
+ gpus: apple ? [apple] : nvidia.gpus,
4085
+ gpu_note: apple ? void 0 : nvidia.note
4086
+ };
4087
+ }
4088
+
3100
4089
  // src/shell.ts
3101
4090
  import { stat as stat3 } from "fs/promises";
3102
- import { homedir } from "os";
3103
- import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
4091
+ import { homedir as homedir2 } from "os";
4092
+ import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
3104
4093
  import { createInterface as createInterface2 } from "readline";
3105
4094
  import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
3106
4095
 
@@ -3108,6 +4097,7 @@ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
3108
4097
  var LOCAL = ["local"];
3109
4098
  var COMMAND_CATALOG = [
3110
4099
  { path: "spec", description: "Review the current behavior spec.", group: "Workflow", modes: LOCAL },
4100
+ { path: "spec view", description: "Readable, highlighted behavior spec; add a section name for detail.", group: "Workflow", modes: LOCAL },
3111
4101
  ...["show", "diff", "validate", "history"].map((operation) => ({ path: `spec ${operation}`, description: `Review behavior spec ${operation}.`, group: "Workflow", modes: LOCAL })),
3112
4102
  { path: "init", description: "Create a local behaviour-spec project.", group: "Workflow", modes: LOCAL },
3113
4103
  { path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
@@ -3123,12 +4113,15 @@ var COMMAND_CATALOG = [
3123
4113
  { path: "runs report", description: "Show a local run report.", group: "Inspect", modes: LOCAL },
3124
4114
  { path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
3125
4115
  { path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
4116
+ { path: "runs audit", description: "Verify a run's event log and report, with timeline and provenance.", group: "Inspect", modes: LOCAL },
3126
4117
  { path: "models list", description: "List local models.", group: "Inspect", modes: LOCAL },
3127
4118
  { path: "models get", description: "Show a local model.", group: "Inspect", modes: LOCAL },
3128
4119
  { path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: LOCAL },
3129
4120
  { path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
3130
4121
  { path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
3131
4122
  { path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
4123
+ { path: "datasets prefetch", description: "Download the spec's Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
4124
+ { path: "datasets verify", description: "Verify the cached Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
3132
4125
  { path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
3133
4126
  { path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
3134
4127
  { path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
@@ -3184,7 +4177,8 @@ var COMMAND_CATALOG = [
3184
4177
  { path: "agent status", description: "Show the laptop-local TT agent selection.", group: "Inspect", modes: LOCAL }
3185
4178
  ];
3186
4179
  var SLASH_COMMANDS = [
3187
- { path: "/spec", description: "Review the behavior spec; add diff, validate or history." },
4180
+ { path: "/spec", description: "Readable behavior spec; add a section, show, diff, validate or history." },
4181
+ { path: "/system", description: "Live GPU, CPU and memory use, and what this machine can fine-tune." },
3188
4182
  { path: "/help", description: "Show commands; add a word to filter." },
3189
4183
  { path: "/status", description: "Show lightweight workflow status." },
3190
4184
  { path: "/context", description: "Show the current project context." },
@@ -3246,7 +4240,7 @@ function createCommandCompleter(getMode = () => "local") {
3246
4240
 
3247
4241
  // src/shell-context.ts
3248
4242
  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";
4243
+ import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "path";
3250
4244
  async function readJsonObject(path) {
3251
4245
  try {
3252
4246
  const parsed = JSON.parse(await readFile3(path, "utf8"));
@@ -3273,7 +4267,7 @@ function expandPath(value, baseDirectory, env) {
3273
4267
  return expandUserPath(value, env, baseDirectory);
3274
4268
  }
3275
4269
  function configPath(env) {
3276
- return join7(getConfigDir(env), "config.json");
4270
+ return join8(getConfigDir(env), "config.json");
3277
4271
  }
3278
4272
  function agentSelectionFrom(env, configAgent, hasToken) {
3279
4273
  const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
@@ -3291,14 +4285,14 @@ async function adjacentFile(path) {
3291
4285
  }
3292
4286
  }
3293
4287
  async function readActiveModelId(storeRoot) {
3294
- const result2 = await readJsonObject(join7(storeRoot, "active-model.json"));
4288
+ const result2 = await readJsonObject(join8(storeRoot, "active-model.json"));
3295
4289
  if (!result2.found || result2.invalid) return void 0;
3296
4290
  const modelId = result2.value?.model_id;
3297
4291
  if (modelId === null) return "base";
3298
4292
  return typeof modelId === "string" && modelId ? modelId : void 0;
3299
4293
  }
3300
4294
  async function readLatestRun(storeRoot) {
3301
- const runsRoot = join7(storeRoot, "runs");
4295
+ const runsRoot = join8(storeRoot, "runs");
3302
4296
  let names;
3303
4297
  try {
3304
4298
  names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -3306,7 +4300,7 @@ async function readLatestRun(storeRoot) {
3306
4300
  return void 0;
3307
4301
  }
3308
4302
  const states = await Promise.all(names.map(async (name) => {
3309
- const result2 = await readJsonObject(join7(runsRoot, name, "state.json"));
4303
+ const result2 = await readJsonObject(join8(runsRoot, name, "state.json"));
3310
4304
  if (!result2.found || result2.invalid || !result2.value) return void 0;
3311
4305
  const id = stringField(result2.value, "id") ?? name;
3312
4306
  const state = {
@@ -3334,14 +4328,15 @@ function hostFromSnapshot(snapshot) {
3334
4328
  summary: snapshot.summary,
3335
4329
  line: formatHostStatusLine(snapshot),
3336
4330
  cuda: snapshot.capabilities.cuda_available,
3337
- gpuName: snapshot.capabilities.gpu?.name
4331
+ gpuName: snapshot.capabilities.gpu?.name,
4332
+ capabilities: snapshot.capabilities
3338
4333
  };
3339
4334
  }
3340
4335
  async function discoverShellContext(options = {}) {
3341
4336
  const env = options.env ?? process.env;
3342
- const cwd = resolve2(options.cwd ?? process.cwd());
4337
+ const cwd = resolve3(options.cwd ?? process.cwd());
3343
4338
  const warnings = [];
3344
- const specPath = join7(cwd, "tunedtensor.json");
4339
+ const specPath = join8(cwd, "tunedtensor.json");
3345
4340
  const specJson = await readJsonObject(specPath);
3346
4341
  let spec;
3347
4342
  if (specJson.found) {
@@ -3350,31 +4345,35 @@ async function discoverShellContext(options = {}) {
3350
4345
  warnings.push("tunedtensor.json could not be parsed.");
3351
4346
  } else {
3352
4347
  const examples = specJson.value.examples;
4348
+ const runtime2 = specJson.value.runtime;
4349
+ const gpu = runtime2 && typeof runtime2 === "object" && !Array.isArray(runtime2) ? runtime2.gpu : void 0;
3353
4350
  spec = {
3354
4351
  path: specPath,
3355
4352
  id: stringField(specJson.value, "id"),
3356
4353
  name: stringField(specJson.value, "name"),
3357
4354
  baseModel: stringField(specJson.value, "base_model"),
4355
+ engine: stringField(specJson.value, "engine"),
3358
4356
  exampleCount: Array.isArray(examples) ? examples.length : void 0,
4357
+ remoteGpu: gpu && typeof gpu === "object" && !Array.isArray(gpu) ? stringField(gpu, "instanceId") : void 0,
3359
4358
  parseError: false
3360
4359
  };
3361
4360
  }
3362
4361
  }
3363
- const localConfigPath = join7(cwd, "local-runner.json");
4362
+ const localConfigPath = join8(cwd, "local-runner.json");
3364
4363
  const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
3365
4364
  const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
3366
4365
  if (localConfigJson.invalid) {
3367
4366
  warnings.push("local-runner.json could not be parsed.");
3368
4367
  }
3369
- const localConfigDirectory = hasAdjacentLocalConfig ? dirname3(localConfigPath) : cwd;
4368
+ const localConfigDirectory = hasAdjacentLocalConfig ? dirname4(localConfigPath) : cwd;
3370
4369
  const runtime = specJson.value?.runtime;
3371
4370
  const projectRuntime = runtime && typeof runtime === "object" && !Array.isArray(runtime) ? runtime : void 0;
3372
4371
  if (hasAdjacentLocalConfig && (projectRuntime || specJson.value?.evaluation)) warnings.push("Conflicting configuration sources; run tt pipeline migrate after reconciling the spec and local-runner.json.");
3373
4372
  const config = projectRuntime ?? localConfigJson.value;
3374
4373
  const configuredArtifactRoot = stringField(config, "artifactRoot");
3375
4374
  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);
4375
+ const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) : resolve3(cwd, DEFAULT_ARTIFACT_ROOT);
4376
+ const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ? resolve3(cwd, DEFAULT_PROJECT_STORE_ROOT) : defaultStoreRoot(env);
3378
4377
  const storedConfigPath = configPath(env);
3379
4378
  const storedConfigJson = await readJsonObject(storedConfigPath);
3380
4379
  if (storedConfigJson.invalid) {
@@ -3408,7 +4407,7 @@ async function discoverShellContext(options = {}) {
3408
4407
  function valueOrDash(value) {
3409
4408
  return value === void 0 || value === "" ? "\u2014" : String(value);
3410
4409
  }
3411
- function agentLabel(context) {
4410
+ function agentLabel2(context) {
3412
4411
  const agent = context.agent;
3413
4412
  if (!agent?.provider && !agent?.model) return "not configured";
3414
4413
  const id = [agent.provider, agent.model].filter(Boolean).join("/");
@@ -3423,7 +4422,7 @@ function formatShellContext(context) {
3423
4422
  `Spec ID ${valueOrDash(context.spec?.id)}`,
3424
4423
  `Base model ${valueOrDash(context.spec?.baseModel)}`,
3425
4424
  `Examples ${valueOrDash(context.spec?.exampleCount)}`,
3426
- `Agent model ${agentLabel(context)}`,
4425
+ `Agent model ${agentLabel2(context)}`,
3427
4426
  `Local config ${context.local.configPath ?? "not found"}`,
3428
4427
  `Artifact root ${context.local.artifactRoot}`,
3429
4428
  `Store root ${context.local.storeRoot}`,
@@ -3435,7 +4434,7 @@ function formatShellContext(context) {
3435
4434
  function formatShellStatus(context) {
3436
4435
  return [
3437
4436
  `Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`,
3438
- `Agent model ${agentLabel(context)}`,
4437
+ `Agent model ${agentLabel2(context)}`,
3439
4438
  `Local config ${context.local.configPath ?? "not found"}`,
3440
4439
  `Active model ${context.local.activeModelId ?? "base"}`,
3441
4440
  `Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
@@ -3612,7 +4611,7 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
3612
4611
  }
3613
4612
 
3614
4613
  // src/secret-prompt.ts
3615
- import chalk3 from "chalk";
4614
+ import chalk6 from "chalk";
3616
4615
  import { emitKeypressEvents } from "readline";
3617
4616
  import { createInterface } from "readline/promises";
3618
4617
  import { stdin, stdout } from "process";
@@ -3690,16 +4689,16 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
3690
4689
  emitKeypressEvents(input);
3691
4690
  return await withDetachedKeypress(input, async () => {
3692
4691
  const wasRaw = terminal.isRaw === true;
3693
- terminal.setRawMode(true);
3694
- input.resume();
3695
- output.write(`${message} ${hint()}`);
3696
4692
  try {
3697
- const choice = await new Promise((resolve11) => {
4693
+ terminal.setRawMode(true);
4694
+ input.resume();
4695
+ output.write(`${message} ${hint()}`);
4696
+ const choice = await new Promise((resolve13) => {
3698
4697
  const finish = (decision) => {
3699
4698
  input.removeListener("keypress", onKeypress);
3700
4699
  input.removeListener("end", onEnd);
3701
4700
  input.removeListener("close", onEnd);
3702
- resolve11(decision);
4701
+ resolve13(decision);
3703
4702
  };
3704
4703
  const onKeypress = (text2, key) => {
3705
4704
  const decided = keyChoiceFor(key, text2);
@@ -3719,7 +4718,7 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
3719
4718
  });
3720
4719
  }
3721
4720
  function hint() {
3722
- return chalk3.dim("y yes \xB7 n no \xB7 esc decide later ");
4721
+ return chalk6.dim("y yes \xB7 n no \xB7 esc decide later ");
3723
4722
  }
3724
4723
 
3725
4724
  // src/shell.ts
@@ -3880,6 +4879,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
3880
4879
  "help",
3881
4880
  "status",
3882
4881
  "context",
4882
+ "system",
3883
4883
  "model",
3884
4884
  "login",
3885
4885
  "clear",
@@ -3949,9 +4949,9 @@ function parseSlashCommand(input) {
3949
4949
  function errorMessage(error) {
3950
4950
  return error instanceof Error ? error.message : String(error);
3951
4951
  }
3952
- var successMark = () => chalk4.green("\u2713");
3953
- var errorMark = () => chalk4.red("\u2717");
3954
- var accent2 = chalk4.hex("#8B5CF6");
4952
+ var successMark = () => chalk7.green("\u2713");
4953
+ var errorMark = () => chalk7.red("\u2717");
4954
+ var accent4 = chalk7.hex("#8B5CF6");
3955
4955
  var ANSI_BOLD = "\x1B[1m";
3956
4956
  var ANSI_BOLD_OFF = "\x1B[22m";
3957
4957
  var ANSI_CLEAR_LINE = "\x1B[2K";
@@ -3960,15 +4960,15 @@ var ANSI_CLEAR_DOWN = "\x1B[J";
3960
4960
  var ANSI_CURSOR_UP = "\x1B[1A";
3961
4961
  var ANSI_RESET = "\x1B[0m";
3962
4962
  function inputColorCodes() {
3963
- if (chalk4.level === 0) return null;
3964
- if (chalk4.level === 1) {
4963
+ if (chalk7.level === 0) return null;
4964
+ if (chalk7.level === 1) {
3965
4965
  return {
3966
4966
  background: "\x1B[100m",
3967
4967
  accent: "\x1B[95m",
3968
4968
  text: "\x1B[97m"
3969
4969
  };
3970
4970
  }
3971
- if (chalk4.level === 2) {
4971
+ if (chalk7.level === 2) {
3972
4972
  return {
3973
4973
  background: "\x1B[48;5;238m",
3974
4974
  accent: "\x1B[38;5;183m",
@@ -3981,13 +4981,14 @@ function inputColorCodes() {
3981
4981
  text: "\x1B[38;2;245;243;255m"
3982
4982
  };
3983
4983
  }
4984
+ var SPEC_USAGE = `Usage: /spec [${SPEC_SECTIONS.join("|")}|show|diff|validate|history] [path/to/tunedtensor.json]`;
3984
4985
  var DETAIL_LABEL_WIDTH = 15;
3985
4986
  function styleDetailLines(lines) {
3986
4987
  return lines.map((line) => {
3987
4988
  if (line.length <= DETAIL_LABEL_WIDTH) return line;
3988
4989
  const label = line.slice(0, DETAIL_LABEL_WIDTH);
3989
4990
  if (!label.trim()) return line;
3990
- const color = label.startsWith("Warning") ? chalk4.yellow : accent2.bold;
4991
+ const color = label.startsWith("Warning") ? chalk7.yellow : accent4.bold;
3991
4992
  return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
3992
4993
  });
3993
4994
  }
@@ -4000,42 +5001,47 @@ function assertNoArgs(command, args2) {
4000
5001
  }
4001
5002
  }
4002
5003
  function expandDirectory(value, cwd, env) {
4003
- const home = env.HOME ? resolve3(env.HOME) : homedir();
5004
+ const home = env.HOME ? resolve4(env.HOME) : homedir2();
4004
5005
  if (value === "~") return home;
4005
- if (value.startsWith("~/")) return resolve3(home, value.slice(2));
4006
- return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
5006
+ if (value.startsWith("~/")) return resolve4(home, value.slice(2));
5007
+ return isAbsolute3(value) ? resolve4(value) : resolve4(cwd, value);
4007
5008
  }
4008
5009
  function helpText(mode, query, palette = false) {
4009
5010
  const groups = groupedCatalog(mode, query);
4010
5011
  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)}` : ""}`));
5012
+ 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
5013
  if (groups.size === 0) {
4013
- lines.push(chalk4.dim(" No matching commands."));
5014
+ lines.push(chalk7.dim(" No matching commands."));
4014
5015
  } else {
4015
5016
  for (const [group, commands] of groups) {
4016
5017
  lines.push("");
4017
- lines.push(chalk4.bold(group));
5018
+ lines.push(chalk7.bold(group));
4018
5019
  for (const command of commands) {
4019
- lines.push(` ${accent2(command.path.padEnd(22))} ${command.description}`);
5020
+ lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
4020
5021
  }
4021
5022
  }
4022
5023
  }
4023
5024
  if (!query) {
4024
5025
  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.`);
5026
+ lines.push(chalk7.bold("Conversation"));
5027
+ lines.push(` ${accent4("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
5028
+ lines.push(` ${accent4("/new".padEnd(22))} Start a new agent conversation.`);
5029
+ lines.push(` ${accent4("/threads".padEnd(22))} List recent conversations.`);
5030
+ lines.push(` ${accent4("/resume <id>".padEnd(22))} Resume a conversation.`);
5031
+ lines.push(` ${accent4("/approve [id]".padEnd(22))} Approve an action left pending.`);
5032
+ lines.push(` ${accent4("/reject [id]".padEnd(22))} Reject an action left pending.`);
4032
5033
  lines.push("");
4033
- lines.push(chalk4.bold("Session"));
5034
+ lines.push(chalk7.bold("Session"));
4034
5035
  for (const command of SLASH_COMMANDS) {
4035
- lines.push(` ${accent2(command.path.padEnd(22))} ${command.description}`);
5036
+ lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
4036
5037
  }
4037
- lines.push(` ${accent2("?".padEnd(22))} Alias for /help.`);
4038
- lines.push(chalk4.dim(
5038
+ lines.push(` ${accent4("?".padEnd(22))} Alias for /help.`);
5039
+ lines.push("");
5040
+ lines.push(chalk7.bold("Behavior spec"));
5041
+ lines.push(` ${accent4("/spec".padEnd(22))} Readable overview of tunedtensor.json.`);
5042
+ lines.push(` ${accent4("/spec <section>".padEnd(22))} One section in full: ${SPEC_SECTIONS.join(", ")}.`);
5043
+ lines.push(` ${accent4("/spec show".padEnd(22))} The raw JSON, highlighted.`);
5044
+ lines.push(chalk7.dim(
4039
5045
  "\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
4040
5046
  ));
4041
5047
  }
@@ -4045,33 +5051,17 @@ function helpText(mode, query, palette = false) {
4045
5051
  function activeModelLabel(snapshot) {
4046
5052
  return snapshot.context.local.activeModelId ?? "base";
4047
5053
  }
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
- `;
5054
+ function renderShellBanner(snapshot, columns) {
5055
+ return renderBanner({
5056
+ version: snapshot.version,
5057
+ context: snapshot.context,
5058
+ activeModel: activeModelLabel(snapshot),
5059
+ usage: snapshot.usage,
5060
+ columns
5061
+ });
4072
5062
  }
4073
5063
  function renderShellPrompt() {
4074
- return `${accent2.bold("\u203A")} `;
5064
+ return `${accent4.bold("\u203A")} `;
4075
5065
  }
4076
5066
  function renderSubmittedShellInput(input, columns) {
4077
5067
  const colors = inputColorCodes();
@@ -4104,7 +5094,7 @@ function resetShellPromptStyle() {
4104
5094
  return "";
4105
5095
  }
4106
5096
  var TunedTensorShellSession = class _TunedTensorShellSession {
4107
- constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, initialContext) {
5097
+ constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, systemProbe, columns, initialContext) {
4108
5098
  this.runner = runner;
4109
5099
  this.io = io;
4110
5100
  this.agent = agent;
@@ -4112,6 +5102,8 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4112
5102
  this.contextProvider = contextProvider;
4113
5103
  this.version = version;
4114
5104
  this.agentModelRuntime = agentModelRuntime;
5105
+ this.systemProbe = systemProbe;
5106
+ this.columns = columns;
4115
5107
  this.cwd = initialContext.cwd;
4116
5108
  this.context = initialContext;
4117
5109
  }
@@ -4119,12 +5111,13 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4119
5111
  modeSource = "default-local";
4120
5112
  cwd;
4121
5113
  context;
5114
+ usage;
4122
5115
  static async create(options) {
4123
5116
  const env = options.env ?? process.env;
4124
- const cwd = resolve3(options.cwd ?? process.cwd());
5117
+ const cwd = resolve4(options.cwd ?? process.cwd());
4125
5118
  const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
4126
5119
  const initialContext = await contextProvider({ cwd, env });
4127
- return new _TunedTensorShellSession(
5120
+ const session = new _TunedTensorShellSession(
4128
5121
  options.runner,
4129
5122
  options.io,
4130
5123
  options.agent,
@@ -4132,8 +5125,22 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4132
5125
  contextProvider,
4133
5126
  options.version,
4134
5127
  options.agentModelRuntime,
5128
+ options.systemProbe,
5129
+ options.columns ?? (() => void 0),
4135
5130
  initialContext
4136
5131
  );
5132
+ await session.sampleSystem();
5133
+ return session;
5134
+ }
5135
+ /** Refresh live machine usage; a failed probe only hides the panel. */
5136
+ async sampleSystem() {
5137
+ if (!this.systemProbe) return void 0;
5138
+ try {
5139
+ this.usage = await this.systemProbe();
5140
+ } catch {
5141
+ this.usage = void 0;
5142
+ }
5143
+ return this.usage;
4137
5144
  }
4138
5145
  interruptAgent() {
4139
5146
  return this.agent?.interrupt() ?? false;
@@ -4144,14 +5151,25 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4144
5151
  modeSource: this.modeSource,
4145
5152
  cwd: this.cwd,
4146
5153
  context: this.context,
4147
- version: this.version
5154
+ version: this.version,
5155
+ usage: this.usage
4148
5156
  };
4149
5157
  }
4150
5158
  prompt() {
4151
5159
  return renderShellPrompt();
4152
5160
  }
4153
5161
  banner() {
4154
- return renderShellBanner(this.snapshot());
5162
+ return renderShellBanner(this.snapshot(), this.columns());
5163
+ }
5164
+ async inspectSpecForView(path) {
5165
+ try {
5166
+ return await inspectLocalSpec(this.cwd, path);
5167
+ } catch (error) {
5168
+ if (error.code === "ENOENT") {
5169
+ throw new ShellParseError(`No ${path} in ${this.cwd}. Run init, or ask TT to draft a behavior spec.`);
5170
+ }
5171
+ throw error;
5172
+ }
4155
5173
  }
4156
5174
  async refreshContext() {
4157
5175
  this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
@@ -4187,42 +5205,42 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4187
5205
  const provider = query ? findAgentProvider(runtime, query) : void 0;
4188
5206
  if (provider) {
4189
5207
  const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
4190
- lines.push("", chalk4.bold(`Models from ${provider.id}`));
5208
+ lines.push("", chalk7.bold(`Models from ${provider.id}`));
4191
5209
  this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
4192
5210
  if (matches.length === 0) {
4193
- lines.push(chalk4.dim(" no models"));
5211
+ lines.push(chalk7.dim(" no models"));
4194
5212
  }
4195
5213
  return lines;
4196
5214
  }
4197
5215
  if (query) {
4198
5216
  const matches = listAgentModels(runtime, { ...catalog, query });
4199
- lines.push("", chalk4.bold(`Models matching ${JSON.stringify(query)}`));
5217
+ lines.push("", chalk7.bold(`Models matching ${JSON.stringify(query)}`));
4200
5218
  this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
4201
5219
  if (matches.length === 0) {
4202
- lines.push(chalk4.dim(" no matches"));
5220
+ lines.push(chalk7.dim(" no matches"));
4203
5221
  }
4204
5222
  return lines;
4205
5223
  }
4206
5224
  const providers = listAgentProviders(runtime, { featuredOnly: true });
4207
- lines.push("", chalk4.bold("Providers"));
5225
+ lines.push("", chalk7.bold("Providers"));
4208
5226
  if (providers.length === 0) {
4209
- lines.push(chalk4.dim(" no providers in the catalog"));
5227
+ lines.push(chalk7.dim(" no providers in the catalog"));
4210
5228
  } else {
4211
5229
  for (const choice of providers) {
4212
5230
  lines.push(this.formatAgentProviderChoice(choice));
4213
5231
  }
4214
- lines.push(chalk4.dim(" Other providers: /login <id> or /model <id>"));
5232
+ lines.push(chalk7.dim(" Other providers: /login <id> or /model <id>"));
4215
5233
  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."));
5234
+ lines.push(chalk7.dim(" Use /login tunedtensor for your TT token, or /login <provider> for a BYO key."));
4217
5235
  }
4218
5236
  }
4219
5237
  const suggestions = recommendAgentModels(runtime, featuredCatalog);
4220
- lines.push("", chalk4.bold("Suggestions"));
5238
+ lines.push("", chalk7.bold("Suggestions"));
4221
5239
  this.appendAgentModelChoices(lines, suggestions, summary);
4222
5240
  if (suggestions.length === 0) {
4223
- lines.push(chalk4.dim(" no models in the catalog"));
5241
+ lines.push(chalk7.dim(" no models in the catalog"));
4224
5242
  } else {
4225
- lines.push(chalk4.dim(" Use /model <provider> to list models, /model <query> to search"));
5243
+ lines.push(chalk7.dim(" Use /model <provider> to list models, /model <query> to search"));
4226
5244
  }
4227
5245
  return lines;
4228
5246
  }
@@ -4233,25 +5251,35 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4233
5251
  }
4234
5252
  if (limit && models.length > shown.length) {
4235
5253
  const hint2 = moreHint ? ` \u2014 ${moreHint}` : "";
4236
- lines.push(chalk4.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
5254
+ lines.push(chalk7.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
4237
5255
  }
4238
5256
  }
4239
5257
  formatAgentProviderChoice(provider) {
4240
5258
  const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
4241
- const auth = provider.authenticated ? "" : chalk4.dim(" auth required");
4242
- return ` ${accent2(provider.id)}${displayName}${auth}`;
5259
+ const auth = provider.authenticated ? "" : chalk7.dim(" auth required");
5260
+ return ` ${accent4(provider.id)}${displayName}${auth}`;
4243
5261
  }
4244
5262
  formatAgentModelChoice(model, summary) {
4245
5263
  const active = summary?.provider === model.provider && summary?.model === model.id;
4246
5264
  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}`;
5265
+ const auth = model.authenticated ? "" : chalk7.dim(" auth required");
5266
+ return ` ${accent4(`${model.provider}/${model.id}`)}${displayName}${active ? chalk7.dim(" (active)") : ""}${auth}`;
4249
5267
  }
4250
5268
  async handleSlash(command) {
4251
5269
  switch (command.name) {
4252
5270
  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;
5271
+ if (command.args.length > 2) throw new ShellParseError(SPEC_USAGE);
5272
+ let [operation = "view", path = "tunedtensor.json"] = command.args;
5273
+ if (command.args.length === 1 && /(^|\/)tunedtensor\.json$/.test(operation)) {
5274
+ [operation, path] = ["view", operation];
5275
+ }
5276
+ const section = resolveSpecSection(operation);
5277
+ if (section || operation === "view" || operation === "show") {
5278
+ const spec = await this.inspectSpecForView(path);
5279
+ this.io.write(operation === "show" ? renderSpecSource(spec) : renderSpecView(spec, { section, columns: this.columns() }));
5280
+ return "continue";
5281
+ }
5282
+ if (!["diff", "validate", "history"].includes(operation)) throw new ShellParseError(SPEC_USAGE);
4255
5283
  if (operation === "diff") {
4256
5284
  const current = await inspectLocalSpec(this.cwd, path);
4257
5285
  const pending = this.agent?.snapshot?.().pendingActions.filter((action) => {
@@ -4262,18 +5290,27 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
4262
5290
  for (const action of pending) {
4263
5291
  const preview = action.preview;
4264
5292
  const input = action.arguments;
4265
- const text2 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
5293
+ const text3 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
4266
5294
  ${preview.diff}
4267
5295
  `;
4268
- this.io.write(sanitizeTerminalText(text2));
5296
+ this.io.write(highlightSpecDiff(sanitizeTerminalText(text3)));
4269
5297
  }
4270
5298
  return "continue";
4271
5299
  }
4272
5300
  }
4273
- this.io.write(`${sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text)}
5301
+ const text2 = sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text);
5302
+ this.io.write(`${operation === "diff" ? highlightSpecDiff(text2) : text2}
4274
5303
  `);
4275
5304
  return "continue";
4276
5305
  }
5306
+ case "system": {
5307
+ assertNoArgs("system", command.args);
5308
+ const usage = await this.sampleSystem();
5309
+ if (!usage) throw new ShellParseError("Live machine usage is unavailable in this session.");
5310
+ await this.refreshContext();
5311
+ this.writeLines(renderMachinePanel(usage, this.context, { columns: this.columns(), detailed: true }));
5312
+ return "continue";
5313
+ }
4277
5314
  case "palette":
4278
5315
  this.io.write(helpText(this.mode, void 0, true));
4279
5316
  return "continue";
@@ -4336,9 +5373,9 @@ ${preview.diff}
4336
5373
  }
4337
5374
  this.writeLines([
4338
5375
  "",
4339
- chalk4.bold("Providers"),
5376
+ chalk7.bold("Providers"),
4340
5377
  ...providers.map((choice) => this.formatAgentProviderChoice(choice)),
4341
- chalk4.dim(" Other providers: /login <id>")
5378
+ chalk7.dim(" Other providers: /login <id>")
4342
5379
  ]);
4343
5380
  if (!this.io.promptLine) {
4344
5381
  throw new ShellParseError("Usage: /login <provider>");
@@ -4526,7 +5563,9 @@ async function startInteractiveShell(options) {
4526
5563
  cwd: options.cwd,
4527
5564
  env: options.env,
4528
5565
  version: options.version,
4529
- agentModelRuntime: options.agentModelRuntime
5566
+ agentModelRuntime: options.agentModelRuntime,
5567
+ systemProbe: options.systemProbe ?? (() => sampleLiveUsage({ env: { ...process.env, ...options.env } })),
5568
+ columns: () => output.columns
4530
5569
  });
4531
5570
  const completer = createCommandCompleter(() => session.snapshot().mode);
4532
5571
  readline = createInterface2({
@@ -4577,6 +5616,9 @@ async function startInteractiveShell(options) {
4577
5616
  } finally {
4578
5617
  output.write(resetShellPromptStyle());
4579
5618
  readline.close();
5619
+ output.write(`
5620
+ ${mascotFarewell()}
5621
+ `);
4580
5622
  }
4581
5623
  }
4582
5624
  function ciEnabled(value) {
@@ -4589,9 +5631,9 @@ function shouldStartInteractiveShell(options) {
4589
5631
  }
4590
5632
 
4591
5633
  // src/agent.ts
4592
- var accent3 = chalk5.hex("#8B5CF6");
4593
- var successMark2 = () => chalk5.green("\u2713");
4594
- var errorMark2 = () => chalk5.red("\u2717");
5634
+ var accent5 = chalk8.hex("#8B5CF6");
5635
+ var successMark2 = () => chalk8.green("\u2713");
5636
+ var errorMark2 = () => chalk8.red("\u2717");
4595
5637
  var ANSI_CLEAR_LINE2 = "\r\x1B[2K";
4596
5638
  var GUTTER = " ";
4597
5639
  function isRecord3(value) {
@@ -4602,25 +5644,25 @@ function stringValue(value) {
4602
5644
  }
4603
5645
  function helpText2() {
4604
5646
  return [
4605
- accent3.bold("Agent commands"),
5647
+ accent5.bold("Agent commands"),
4606
5648
  "",
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.`,
5649
+ ` ${accent5("/new".padEnd(22))} Start a new conversation.`,
5650
+ ` ${accent5("/threads".padEnd(22))} List recent conversations.`,
5651
+ ` ${accent5("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
5652
+ ` ${accent5("/approve [id]".padEnd(22))} Approve an action left pending.`,
5653
+ ` ${accent5("/reject [id]".padEnd(22))} Reject an action left pending.`,
5654
+ ` ${accent5("/status".padEnd(22))} Show the active conversation.`,
5655
+ ` ${accent5("/clear".padEnd(22))} Clear the terminal.`,
5656
+ ` ${accent5("/exit".padEnd(22))} Exit the agent.`,
4615
5657
  "",
4616
- chalk5.dim(
5658
+ chalk8.dim(
4617
5659
  "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
5660
  ),
4619
5661
  ""
4620
5662
  ].join("\n");
4621
5663
  }
4622
5664
  function formatThread(thread, activeId) {
4623
- const active = thread.id === activeId ? accent3("\u25CF") : chalk5.dim("\u25CB");
5665
+ const active = thread.id === activeId ? accent5("\u25CF") : chalk8.dim("\u25CB");
4624
5666
  const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
4625
5667
  const date = new Date(time);
4626
5668
  const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
@@ -4629,7 +5671,7 @@ function formatThread(thread, activeId) {
4629
5671
  hour: "2-digit",
4630
5672
  minute: "2-digit"
4631
5673
  });
4632
- return `${active} ${chalk5.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk5.dim(dateLabel)}`;
5674
+ return `${active} ${chalk8.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk8.dim(dateLabel)}`;
4633
5675
  }
4634
5676
  function actionFromPayload(payload) {
4635
5677
  const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
@@ -4660,14 +5702,14 @@ function formatJson(value) {
4660
5702
  }
4661
5703
  function riskLabel(risk) {
4662
5704
  const safe = sanitizeTerminalText(risk);
4663
- const color = safe === "low" ? chalk5.green : safe === "high" ? chalk5.red : chalk5.yellow;
5705
+ const color = safe === "low" ? chalk8.green : safe === "high" ? chalk8.red : chalk8.yellow;
4664
5706
  return color(`${safe} risk`);
4665
5707
  }
4666
5708
  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;
5709
+ if (line.startsWith("+++") || line.startsWith("---")) return chalk8.dim;
5710
+ if (line.startsWith("+")) return chalk8.green;
5711
+ if (line.startsWith("-")) return chalk8.red;
5712
+ if (line.startsWith("@@")) return chalk8.cyan;
4671
5713
  return (text2) => text2;
4672
5714
  }
4673
5715
  function approvalQuestion(action) {
@@ -4805,7 +5847,7 @@ var TunedTensorAgentSession = class {
4805
5847
  wrapped.forEach((part, partIndex) => {
4806
5848
  if (partIndex > 0) this.io.write("\n");
4807
5849
  if (!this.lineOpen || partIndex > 0) {
4808
- const marker = this.blockStart ? `${accent3("\u25CF")} ` : GUTTER;
5850
+ const marker = this.blockStart ? `${accent5("\u25CF")} ` : GUTTER;
4809
5851
  if (this.blockStart && this.wroteInTurn) this.io.write("\n");
4810
5852
  this.blockStart = false;
4811
5853
  this.wroteInTurn = true;
@@ -4822,11 +5864,11 @@ var TunedTensorAgentSession = class {
4822
5864
  renderAction(action) {
4823
5865
  this.flushResponse();
4824
5866
  this.endOpenLine();
4825
- const bar = chalk5.yellow("\u2502");
5867
+ const bar = chalk8.yellow("\u2502");
4826
5868
  const out = [
4827
5869
  "",
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)}`
5870
+ `${GUTTER}${chalk8.yellow.bold("\u25C6 Approval needed")} ${chalk8.dim(action.id.slice(0, 8))}`,
5871
+ `${GUTTER}${bar} ${chalk8.bold(sanitizeTerminalText(action.title))} ${chalk8.dim("\xB7")} ${riskLabel(action.risk)}`
4830
5872
  ];
4831
5873
  const width = this.wrapWidth();
4832
5874
  const push = (text2, style = (line) => line) => {
@@ -4835,7 +5877,7 @@ var TunedTensorAgentSession = class {
4835
5877
  for (const part of parts) out.push(`${GUTTER}${bar} ${style(part)}`);
4836
5878
  }
4837
5879
  };
4838
- if (action.summary) push(action.summary, chalk5.dim);
5880
+ if (action.summary) push(action.summary, chalk8.dim);
4839
5881
  if (action.operation === "update_local_spec") {
4840
5882
  const preview = action.preview;
4841
5883
  out.push(`${GUTTER}${bar}`);
@@ -4846,7 +5888,7 @@ var TunedTensorAgentSession = class {
4846
5888
  out.push(`${GUTTER}${bar} ${style(index === 0 ? part : ` ${part}`)}`);
4847
5889
  });
4848
5890
  }
4849
- for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk5.yellow);
5891
+ for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk8.yellow);
4850
5892
  } else {
4851
5893
  const request2 = [action.method, action.path].filter(Boolean).join(" ");
4852
5894
  const technical = {
@@ -4856,14 +5898,14 @@ var TunedTensorAgentSession = class {
4856
5898
  preview: action.preview ?? null
4857
5899
  };
4858
5900
  out.push(`${GUTTER}${bar}`);
4859
- push(formatJson(technical), chalk5.dim);
5901
+ push(formatJson(technical), chalk8.dim);
4860
5902
  }
4861
5903
  this.io.write(`${out.join("\n")}
4862
5904
  `);
4863
5905
  }
4864
5906
  writePendingHint() {
4865
5907
  this.io.write(
4866
- chalk5.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
5908
+ chalk8.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
4867
5909
  `)
4868
5910
  );
4869
5911
  }
@@ -4880,14 +5922,14 @@ var TunedTensorAgentSession = class {
4880
5922
  const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
4881
5923
  if (!delta) return;
4882
5924
  if (this.reasoningActive) return;
4883
- this.writeStatus(chalk5.dim("\u25CB Thinking\u2026"), { transient: true });
5925
+ this.writeStatus(`${mascotMark()} ${chalk8.dim("Thinking\u2026")}`, { transient: true });
4884
5926
  this.reasoningActive = true;
4885
5927
  return;
4886
5928
  }
4887
5929
  if (event.type === "tool_call") {
4888
5930
  this.reasoningActive = false;
4889
5931
  const label = toolLabel(payload);
4890
- this.writeStatus(chalk5.dim(`\u25CB ${label}\u2026`), { transient: true });
5932
+ this.writeStatus(chalk8.dim(`\u25CB ${label}\u2026`), { transient: true });
4891
5933
  const id = stringValue(payload.toolUseId);
4892
5934
  if (id) this.pendingToolLabels.set(id, label);
4893
5935
  else this.pendingUnkeyedToolLabel = label;
@@ -4900,7 +5942,7 @@ var TunedTensorAgentSession = class {
4900
5942
  if (id) this.pendingToolLabels.delete(id);
4901
5943
  else this.pendingUnkeyedToolLabel = null;
4902
5944
  this.writeStatus(
4903
- `${failed ? errorMark2() : successMark2()} ${chalk5.dim(failed ? `${label} failed` : label)}`
5945
+ `${failed ? errorMark2() : successMark2()} ${chalk8.dim(failed ? `${label} failed` : label)}`
4904
5946
  );
4905
5947
  return;
4906
5948
  }
@@ -4912,7 +5954,7 @@ var TunedTensorAgentSession = class {
4912
5954
  return;
4913
5955
  }
4914
5956
  if (event.type === "action_started") {
4915
- this.writeStatus(chalk5.dim("\u25CB Running approved action\u2026"));
5957
+ this.writeStatus(chalk8.dim("\u25CB Running approved action\u2026"));
4916
5958
  return;
4917
5959
  }
4918
5960
  if (event.type === "action_result") {
@@ -4959,7 +6001,7 @@ var TunedTensorAgentSession = class {
4959
6001
  }
4960
6002
  }
4961
6003
  if (controller.signal.aborted || result2.status === "cancelled") {
4962
- this.io.write(chalk5.dim(`${GUTTER}Response stopped.
6004
+ this.io.write(chalk8.dim(`${GUTTER}Response stopped.
4963
6005
  `));
4964
6006
  return null;
4965
6007
  }
@@ -4968,7 +6010,7 @@ var TunedTensorAgentSession = class {
4968
6010
  this.flushResponse();
4969
6011
  this.endOpenLine();
4970
6012
  if (controller.signal.aborted) {
4971
- this.io.write(chalk5.dim(`${GUTTER}Response stopped.
6013
+ this.io.write(chalk8.dim(`${GUTTER}Response stopped.
4972
6014
  `));
4973
6015
  return null;
4974
6016
  }
@@ -5041,13 +6083,13 @@ var TunedTensorAgentSession = class {
5041
6083
  }
5042
6084
  for (const action of actions) {
5043
6085
  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)}`);
6086
+ const label = actions.length > 1 ? `${approvalQuestion(action)} ${chalk8.dim(`(${action.title})`)}` : approvalQuestion(action);
6087
+ const decision = await this.prompter(`${GUTTER}${chalk8.yellow("?")} ${chalk8.bold(label)}`);
5046
6088
  try {
5047
6089
  if (decision === "approve") await this.approve(action.id, context);
5048
6090
  else if (decision === "reject") await this.reject(action.id);
5049
6091
  else {
5050
- this.io.write(chalk5.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
6092
+ this.io.write(chalk8.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
5051
6093
  `));
5052
6094
  }
5053
6095
  } catch (error) {
@@ -5120,7 +6162,7 @@ var TunedTensorAgentSession = class {
5120
6162
  if (args2.length > 0) throw new Error("/threads does not accept arguments.");
5121
6163
  const threads = await this.options.client.listThreads();
5122
6164
  if (threads.length === 0) {
5123
- this.options.io.write(chalk5.dim("No conversations yet.\n"));
6165
+ this.options.io.write(chalk8.dim("No conversations yet.\n"));
5124
6166
  return "continue";
5125
6167
  }
5126
6168
  this.options.io.write(
@@ -5158,10 +6200,10 @@ var TunedTensorAgentSession = class {
5158
6200
  if (args2.length > 0) throw new Error("/status does not accept arguments.");
5159
6201
  this.options.io.write(
5160
6202
  this.thread ? `${formatThread(this.thread, this.thread.id)}
5161
- ` : chalk5.dim("New conversation; send a message to create it.\n")
6203
+ ` : chalk8.dim("New conversation; send a message to create it.\n")
5162
6204
  );
5163
6205
  this.options.io.write(
5164
- chalk5.dim(`${this.pendingActions.size} pending approval(s).
6206
+ chalk8.dim(`${this.pendingActions.size} pending approval(s).
5165
6207
  `)
5166
6208
  );
5167
6209
  return "continue";
@@ -5205,10 +6247,10 @@ import {
5205
6247
  } from "fs/promises";
5206
6248
  import {
5207
6249
  basename as basename3,
5208
- dirname as dirname4,
6250
+ dirname as dirname5,
5209
6251
  isAbsolute as isAbsolute4,
5210
- join as join8,
5211
- resolve as resolve4
6252
+ join as join9,
6253
+ resolve as resolve5
5212
6254
  } from "path";
5213
6255
 
5214
6256
  // src/project-spec.ts
@@ -5227,6 +6269,7 @@ var LOCAL_ONLY_SPEC_KEYS = [
5227
6269
  "dataset_prebuilt",
5228
6270
  "engine",
5229
6271
  "foundation",
6272
+ "decision",
5230
6273
  "evaluation",
5231
6274
  "runtime",
5232
6275
  "pipeline"
@@ -5268,7 +6311,7 @@ function projectCloudSpec(raw) {
5268
6311
  return projectSpec(raw, CLOUD_SPEC_KEY_SET, canonicalizeHostedSpecBaseModel);
5269
6312
  }
5270
6313
  function projectLocalSpec(raw) {
5271
- return projectSpec(raw, LOCAL_SPEC_KEY_SET, canonicalizeSpecBaseModel);
6314
+ return projectSpec(raw, LOCAL_SPEC_KEY_SET, raw.engine === "decision" ? (body) => body : canonicalizeSpecBaseModel);
5272
6315
  }
5273
6316
  function hasLocalOnlySpecFields(raw) {
5274
6317
  return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
@@ -5383,6 +6426,7 @@ function renderInit(value) {
5383
6426
  ["Path", text(value.path)],
5384
6427
  ["Name", text(value.name)],
5385
6428
  ["ID", text(value.id)],
6429
+ ["Engine", text(value.engine)],
5386
6430
  ["Base model", text(value.base_model)],
5387
6431
  ["Config", text(value.config_path)]
5388
6432
  ]);
@@ -5391,14 +6435,27 @@ function renderValidate(value) {
5391
6435
  printSuccess("Local behavior spec is valid");
5392
6436
  printDetail([
5393
6437
  ["Input", text(value.input_path)],
6438
+ ["Engine", text(value.engine)],
5394
6439
  ["Config", text(value.config_path)],
5395
6440
  ["Spec", text(value.behavior_spec_id)],
5396
6441
  ["Base model", text(value.base_model)],
5397
6442
  ["Dataset", text(value.dataset_format)],
6443
+ ["Hugging Face", huggingFaceDatasetLabel(value)],
5398
6444
  ["Artifacts", text(value.artifact_root)],
5399
6445
  ["Store", text(value.store_root)],
5400
6446
  ["Dry run", text(value.dry_run)]
5401
6447
  ]);
6448
+ if (value.dataset_status === "not_downloaded") {
6449
+ printWarning(`Dataset files are not downloaded yet; dataset contents were not checked. Run \`${text(value.next_step) ?? "tt datasets prefetch"}\`.`);
6450
+ }
6451
+ }
6452
+ function huggingFaceDatasetLabel(value) {
6453
+ const source = record3(value.dataset_source) ?? record3(value.dataset_huggingface);
6454
+ const repo = text(source?.repo);
6455
+ if (!repo) return void 0;
6456
+ const revision = text(source?.revision) ?? "main";
6457
+ const status2 = value.dataset_status === "not_downloaded" ? "not downloaded" : "cached";
6458
+ return `${repo}@${revision} (${status2}${source?.pinned === false || !text(source?.revision) ? ", unpinned" : ""})`;
5402
6459
  }
5403
6460
  function renderRun(value) {
5404
6461
  const runStatus = text(value.status) ?? "unknown";
@@ -5453,6 +6510,68 @@ function renderRunEvents(value) {
5453
6510
  ])
5454
6511
  );
5455
6512
  }
6513
+ function formatSeconds(value) {
6514
+ if (typeof value !== "number" || !Number.isFinite(value)) return "running";
6515
+ const seconds = Math.round(value);
6516
+ const hours = Math.floor(seconds / 3600);
6517
+ const minutes = Math.floor(seconds % 3600 / 60);
6518
+ const rest = seconds % 60;
6519
+ if (hours) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
6520
+ if (minutes) return `${minutes}m ${String(rest).padStart(2, "0")}s`;
6521
+ return `${rest}s`;
6522
+ }
6523
+ function shortHash(value) {
6524
+ const hash = text(value);
6525
+ return hash ? hash.slice(0, 12) : void 0;
6526
+ }
6527
+ function renderRunAudit(value) {
6528
+ const verdict = text(value.verdict);
6529
+ const chain = record3(value.chain);
6530
+ const reportInfo = record3(value.report);
6531
+ const findings = Array.isArray(value.findings) ? value.findings.map(String) : [];
6532
+ if (verdict === "verified") printSuccess("Run evidence verified: event log intact and report unchanged since completion");
6533
+ else if (verdict === "tampered") printError("Run evidence does not verify");
6534
+ else printWarning("Run evidence is incomplete");
6535
+ for (const finding of findings) console.log(` - ${finding}`);
6536
+ printDetail([
6537
+ ["Run", text(value.run_id)],
6538
+ ["Status", status(value.status)],
6539
+ ["Event chain", `${text(chain?.status) ?? "\u2014"} (${text(chain?.chained_events) ?? 0}/${text(chain?.events) ?? 0} chained, head ${shortHash(chain?.head_hash) ?? "\u2014"})`],
6540
+ ["Report sha256", shortHash(reportInfo?.recorded_sha256) ?? "not recorded"]
6541
+ ]);
6542
+ const timeline = rows(value.timeline);
6543
+ if (timeline.length > 0) {
6544
+ console.log("");
6545
+ printTable(
6546
+ ["Started", "Stage", "Duration", "Status"],
6547
+ timeline.map((entry) => [
6548
+ formatDate(text(entry.started_at)),
6549
+ text(entry.stage) ?? "\u2014",
6550
+ formatSeconds(entry.duration_seconds),
6551
+ status(entry.status)
6552
+ ])
6553
+ );
6554
+ }
6555
+ const provenance = record3(value.provenance);
6556
+ if (provenance) {
6557
+ const software = record3(provenance.software);
6558
+ const execution = record3(provenance.execution);
6559
+ const spec = record3(provenance.spec);
6560
+ const baseModel = record3(provenance.base_model);
6561
+ const data3 = record3(provenance.data);
6562
+ const files = record3(data3?.file_sha256) ?? {};
6563
+ console.log("");
6564
+ printDetail([
6565
+ ["TT", `${text(software?.tt_version) ?? "\u2014"} (node ${text(software?.node_version) ?? "\u2014"}, ${text(software?.platform) ?? "\u2014"}/${text(software?.arch) ?? "\u2014"})`],
6566
+ ["Execution", `${text(execution?.target) ?? "\u2014"}${text(execution?.instance_id) ? ` ${text(execution?.instance_id)}` : ""} via ${text(execution?.training_backend) ?? "\u2014"}`],
6567
+ ["Spec", `${text(spec?.file_path) ?? text(spec?.behavior_spec_id) ?? "\u2014"} (sha256 ${shortHash(spec?.file_sha256) ?? shortHash(spec?.snapshot_sha256) ?? "\u2014"})`],
6568
+ ["Base model", `${text(baseModel?.id) ?? "\u2014"}@${shortHash(baseModel?.revision) ?? "unpinned"}`],
6569
+ ...Object.entries(files).map(([split, hash]) => [`Data ${split}`, `sha256 ${shortHash(hash)}`]),
6570
+ ["Training data", `sha256 ${shortHash(data3?.compiled_training_sha256) ?? "\u2014"} (${text(data3?.training_examples) ?? "\u2014"} examples)`],
6571
+ ["Evaluation", `${text(data3?.eval_examples_used) ?? "\u2014"}/${text(data3?.eval_examples_total) ?? "\u2014"} ${text(data3?.eval_split) ?? ""} (seed ${text(data3?.eval_sample_seed) ?? "\u2014"})`]
6572
+ ]);
6573
+ }
6574
+ }
5456
6575
  function renderRunRecord(value) {
5457
6576
  printDetail([
5458
6577
  ["Run", text(value.id) ?? text(value.run_id)],
@@ -5558,6 +6677,22 @@ function renderModelPrefetch(value) {
5558
6677
  ["Cache", text(value.model_cache) ?? text(value.hf_home)]
5559
6678
  ]);
5560
6679
  }
6680
+ function renderDatasetPrefetch(value) {
6681
+ printSuccess(
6682
+ value.local_files_only === true ? "Verified the cached Hugging Face dataset" : "Dataset is ready locally"
6683
+ );
6684
+ const files = record3(value.files) ?? {};
6685
+ printDetail([
6686
+ ["Dataset", text(value.repo)],
6687
+ ["Revision", `${text(value.revision) ?? "\u2014"}${value.pinned === false ? " (unpinned: set dataset_prebuilt.huggingface.revision to pin it)" : ""}`],
6688
+ ...Object.entries(files).map(([split, file]) => {
6689
+ const entry = record3(file);
6690
+ return [split[0].toUpperCase() + split.slice(1), `${text(entry?.path) ?? "\u2014"} (${displayBytes(entry?.size_bytes) ?? "\u2014"}, sha256 ${String(text(entry?.sha256) ?? "").slice(0, 12)})`];
6691
+ }),
6692
+ ["Snapshot", text(value.snapshot_path)],
6693
+ ["Cache", text(value.hub_cache)]
6694
+ ]);
6695
+ }
5561
6696
  function renderActiveModel(value) {
5562
6697
  const active = text(value.active) ?? "base";
5563
6698
  const pointer = record3(value.pointer);
@@ -5659,6 +6794,10 @@ function renderKnownJson(args2, value) {
5659
6794
  renderRunEvents(value);
5660
6795
  return true;
5661
6796
  }
6797
+ if (subcommand === "audit" && isRecord4(value)) {
6798
+ renderRunAudit(value);
6799
+ return true;
6800
+ }
5662
6801
  if (subcommand === "get" && isRecord4(value)) {
5663
6802
  renderRunRecord(value);
5664
6803
  return true;
@@ -5695,6 +6834,10 @@ function renderKnownJson(args2, value) {
5695
6834
  return true;
5696
6835
  }
5697
6836
  }
6837
+ if (command === "datasets" && isRecord4(value)) {
6838
+ renderDatasetPrefetch(value);
6839
+ return true;
6840
+ }
5698
6841
  if (command === "serve" && isRecord4(value)) {
5699
6842
  renderServePlan(value);
5700
6843
  return true;
@@ -5867,10 +7010,18 @@ function positionalSpecArgument(args2, start, defaultPath) {
5867
7010
  function selectedSpecArgument(args2, cwd) {
5868
7011
  const command = args2[0];
5869
7012
  if (["doctor", "validate", "run"].includes(command ?? "")) {
5870
- return positionalSpecArgument(args2, 1, join8(cwd, DEFAULT_SPEC_NAME));
7013
+ return positionalSpecArgument(args2, 1, join9(cwd, DEFAULT_SPEC_NAME));
5871
7014
  }
5872
7015
  if (command === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
5873
- return positionalSpecArgument(args2, 2, join8(cwd, DEFAULT_SPEC_NAME));
7016
+ return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
7017
+ }
7018
+ if (command === "datasets") {
7019
+ if (args2[1] === void 0 || args2[1].startsWith("-") || args2[1].endsWith(".json")) {
7020
+ args2.splice(1, 0, "prefetch");
7021
+ }
7022
+ if (["prefetch", "verify"].includes(args2[1] ?? "")) {
7023
+ return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
7024
+ }
5874
7025
  }
5875
7026
  const serves = command === "serve" || command === "models" && args2[1] === "serve";
5876
7027
  if (!serves) return null;
@@ -5883,7 +7034,7 @@ async function pathIsFile(path) {
5883
7034
  }
5884
7035
  function resolveFromCwd(path, cwd) {
5885
7036
  if (isAbsolute4(path)) return path;
5886
- return resolve4(cwd, path);
7037
+ return resolve5(cwd, path);
5887
7038
  }
5888
7039
  async function projectSpecArguments(originalArgs, cwd) {
5889
7040
  const args2 = [...originalArgs];
@@ -5946,8 +7097,8 @@ async function projectSpecArguments(originalArgs, cwd) {
5946
7097
  }
5947
7098
  };
5948
7099
  }
5949
- const temporaryPath = join8(
5950
- dirname4(sourcePath),
7100
+ const temporaryPath = join9(
7101
+ dirname5(sourcePath),
5951
7102
  `.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
5952
7103
  );
5953
7104
  await writeFile4(
@@ -6012,6 +7163,8 @@ function normalizeProjectedPaths(value, replacements) {
6012
7163
  async function spawnLocalCli(args2) {
6013
7164
  const output = args2.stdout ?? process.stdout;
6014
7165
  const errors = args2.stderr ?? process.stderr;
7166
+ const errorTerminal = errors;
7167
+ const childEnv = errorTerminal.isTTY && !args2.jsonMode && args2.env.TT_PROGRESS_TTY === void 0 ? { ...args2.env, TT_PROGRESS_TTY: "1", TT_PROGRESS_COLUMNS: String(errorTerminal.columns ?? 100) } : args2.env;
6015
7168
  const pipeInput = Boolean(args2.stdin && args2.stdin !== process.stdin);
6016
7169
  const pipeStreamingOutput = args2.streamingStdout && Boolean(args2.stdout && args2.stdout !== process.stdout);
6017
7170
  const child = spawn(
@@ -6019,7 +7172,7 @@ async function spawnLocalCli(args2) {
6019
7172
  [args2.entrypoint, ...args2.commandArgs],
6020
7173
  {
6021
7174
  cwd: args2.cwd,
6022
- env: args2.env,
7175
+ env: childEnv,
6023
7176
  stdio: [
6024
7177
  pipeInput ? "pipe" : "inherit",
6025
7178
  args2.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
@@ -6109,7 +7262,7 @@ function setupFailure(originalArgs, error) {
6109
7262
  };
6110
7263
  }
6111
7264
  async function runLocalCommand(args2, options = {}) {
6112
- const cwd = resolve4(options.cwd ?? process.cwd());
7265
+ const cwd = resolve5(options.cwd ?? process.cwd());
6113
7266
  let projected;
6114
7267
  try {
6115
7268
  const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
@@ -6155,7 +7308,7 @@ async function executeLocalCommand(args2, options = {}) {
6155
7308
  }
6156
7309
 
6157
7310
  // src/commands/local.ts
6158
- import chalk6 from "chalk";
7311
+ import chalk9 from "chalk";
6159
7312
 
6160
7313
  // src/passthrough.ts
6161
7314
  function extractPassthroughOptions(args2, root) {
@@ -6183,7 +7336,8 @@ var LOCAL_COMMANDS = [
6183
7336
  "validate",
6184
7337
  "serve",
6185
7338
  "runs",
6186
- "models"
7339
+ "models",
7340
+ "datasets"
6187
7341
  ];
6188
7342
  var LOCAL_COMMAND_DESCRIPTIONS = {
6189
7343
  info: "Show the installed local runtime version and runner status",
@@ -6193,7 +7347,8 @@ var LOCAL_COMMAND_DESCRIPTIONS = {
6193
7347
  validate: "Validate a local behavior spec without executing it",
6194
7348
  serve: "Serve a verified adapter, the active model, or the protected base",
6195
7349
  runs: "Inspect locally stored runs",
6196
- models: "Inspect, verify, prefetch, or serve local models"
7350
+ models: "Inspect, verify, prefetch, or serve local models",
7351
+ datasets: "Download or verify the Hugging Face dataset a spec trains on"
6197
7352
  };
6198
7353
  function applyExitCode(result2) {
6199
7354
  if (result2?.exitCode !== null && result2?.exitCode !== void 0) {
@@ -6210,7 +7365,7 @@ function registerLocalCommands(program, runtime) {
6210
7365
  const runPassthrough = async (forwarded) => {
6211
7366
  const root = program.opts();
6212
7367
  const passthrough = extractPassthroughOptions(forwarded, root);
6213
- if (!passthrough.color) chalk6.level = 0;
7368
+ if (!passthrough.color) chalk9.level = 0;
6214
7369
  const result2 = await runtime.invokeLocal(
6215
7370
  passthrough.args.length > 0 ? passthrough.args : ["--help"],
6216
7371
  {
@@ -6308,12 +7463,12 @@ function registerAgentCommands(parent, options) {
6308
7463
  }
6309
7464
 
6310
7465
  // src/commands/pipeline.ts
6311
- 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";
7466
+ import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
7467
+ import { dirname as dirname9, join as join13, resolve as resolve8 } from "path";
6313
7468
 
6314
7469
  // src/local-runtime/project-migration.ts
6315
7470
  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";
7471
+ import { dirname as dirname6, join as join10 } from "path";
6317
7472
  async function migrateProjectWorkflow(specPath) {
6318
7473
  const info = await lstat5(specPath);
6319
7474
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
@@ -6324,7 +7479,7 @@ async function migrateProjectWorkflow(specPath) {
6324
7479
  const files = [];
6325
7480
  const sources = /* @__PURE__ */ new Map([[specPath, original]]);
6326
7481
  for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
6327
- const path = join9(dirname5(specPath), name);
7482
+ const path = join10(dirname6(specPath), name);
6328
7483
  const entry = await lstat5(path).catch((error) => {
6329
7484
  if (error.code === "ENOENT") return void 0;
6330
7485
  throw error;
@@ -6392,7 +7547,7 @@ async function migrateProjectWorkflow(specPath) {
6392
7547
  import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
6393
7548
  import { createReadStream } from "fs";
6394
7549
  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";
7550
+ import { basename as basename4, dirname as dirname7, join as join11, resolve as resolve6 } from "path";
6396
7551
  var INSTRUCTION_CORPUS_VERSION = 1;
6397
7552
  function isRef(value) {
6398
7553
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
@@ -6421,7 +7576,7 @@ async function hashPath(path) {
6421
7576
  if (info.isDirectory()) {
6422
7577
  for (const name of (await readdir4(path)).sort()) {
6423
7578
  hash.update(name);
6424
- hash.update(await hashPath(join10(path, name)));
7579
+ hash.update(await hashPath(join11(path, name)));
6425
7580
  }
6426
7581
  return hash.digest("hex");
6427
7582
  }
@@ -6444,7 +7599,7 @@ async function makePrivateTree(path) {
6444
7599
  if (info.isDirectory()) {
6445
7600
  await chmod2(path, 448);
6446
7601
  for (const name of await readdir4(path)) {
6447
- await makePrivateTree(join10(path, name));
7602
+ await makePrivateTree(join11(path, name));
6448
7603
  }
6449
7604
  return;
6450
7605
  }
@@ -6473,7 +7628,7 @@ async function readRequiredJsonObject(path, description) {
6473
7628
  return parsed;
6474
7629
  }
6475
7630
  async function writePrivateTextAtomic(path, content) {
6476
- const temporary = join10(dirname6(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
7631
+ const temporary = join11(dirname7(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
6477
7632
  const file = await open3(temporary, "wx", 384);
6478
7633
  let closed = false;
6479
7634
  try {
@@ -6588,8 +7743,8 @@ ${result2.stderr}` : ""}`
6588
7743
  async function runFoundationPipeline(args2) {
6589
7744
  assertFoundationPlanSupported(args2.spec, args2.plan);
6590
7745
  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()))
7746
+ const outputDir = resolve6(
7747
+ 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
7748
  );
6594
7749
  if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
6595
7750
  throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
@@ -6614,7 +7769,7 @@ async function runFoundationPipeline(args2) {
6614
7769
  await mkdir4(outputDir, { recursive: true, mode: 448 });
6615
7770
  }
6616
7771
  await makePrivateTree(outputDir);
6617
- const workflowPath = join10(outputDir, "resolved-workflow.json");
7772
+ const workflowPath = join11(outputDir, "resolved-workflow.json");
6618
7773
  const workflow = { spec: args2.spec, plan: args2.plan };
6619
7774
  if (outputExists) {
6620
7775
  const previous = await readFile6(workflowPath, "utf8").catch((error) => {
@@ -6647,16 +7802,16 @@ async function runFoundationPipeline(args2) {
6647
7802
  throw new Error(`Missing ${kind} artifact.`);
6648
7803
  };
6649
7804
  for (const step of args2.plan.steps) {
6650
- const stepDir = join10(outputDir, step.id);
6651
- const output = join10(stepDir, "output");
7805
+ const stepDir = join11(outputDir, step.id);
7806
+ const output = join11(stepDir, "output");
6652
7807
  await mkdir4(stepDir, { recursive: true, mode: 448 });
6653
7808
  await chmod2(stepDir, 448);
6654
7809
  await mkdir4(output, { recursive: true, mode: 448 });
6655
7810
  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");
7811
+ const configPath2 = join11(stepDir, "config.json");
7812
+ const logPath = join11(stepDir, "step.log");
7813
+ const metricsPath = join11(output, "metrics.json");
7814
+ const completionPath = join11(stepDir, "completion.json");
6660
7815
  const fields = stepConfig(step);
6661
7816
  let entrypoint;
6662
7817
  let config;
@@ -6677,7 +7832,7 @@ async function runFoundationPipeline(args2) {
6677
7832
  config = {
6678
7833
  tokenizer_dir: tokenizerDir,
6679
7834
  output_dir: output,
6680
- work_dir: join10(stepDir, "recovery"),
7835
+ work_dir: join11(stepDir, "recovery"),
6681
7836
  depth: fields.depth ?? hp.depth,
6682
7837
  steps: fields.steps ?? hp.pretrain_steps,
6683
7838
  batch_size: fields.batchSize ?? hp.batch_size,
@@ -6782,15 +7937,15 @@ async function runFoundationPipeline(args2) {
6782
7937
  const artifacts = {};
6783
7938
  const record4 = {};
6784
7939
  if (step.uses === "tokenize") {
6785
- const tokenizerPath = join10(output, "tokenizer.json");
7940
+ const tokenizerPath = join11(output, "tokenizer.json");
6786
7941
  await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
6787
7942
  await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
6788
7943
  record4.tokenizer = output;
6789
7944
  tokenizerDir = output;
6790
7945
  artifacts.tokenizer = await artifactFor(output);
6791
7946
  } 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");
7947
+ const modelPath = join11(output, "model.safetensors");
7948
+ const modelConfigPath = join11(output, "config.json");
6794
7949
  await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
6795
7950
  const modelConfig = await readRequiredJsonObject(
6796
7951
  modelConfigPath,
@@ -6805,7 +7960,7 @@ async function runFoundationPipeline(args2) {
6805
7960
  modelDir = output;
6806
7961
  artifacts.model = await artifactFor(output);
6807
7962
  } else if (step.uses === "evaluate") {
6808
- const reportPath2 = join10(output, "report.json");
7963
+ const reportPath2 = join11(output, "report.json");
6809
7964
  const stepReport = await readRequiredJsonObject(
6810
7965
  reportPath2,
6811
7966
  `Foundation step "${step.id}" report.json`
@@ -6845,7 +8000,7 @@ async function runFoundationPipeline(args2) {
6845
8000
  spec: args2.specPath,
6846
8001
  steps
6847
8002
  };
6848
- const reportPath = join10(outputDir, "report.json");
8003
+ const reportPath = join11(outputDir, "report.json");
6849
8004
  await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
6850
8005
  `);
6851
8006
  return {
@@ -6856,6 +8011,334 @@ async function runFoundationPipeline(args2) {
6856
8011
  };
6857
8012
  }
6858
8013
 
8014
+ // src/local-runtime/decision-runner.ts
8015
+ import { randomUUID as randomUUID9 } from "crypto";
8016
+ import { lstat as lstat7, mkdir as mkdir5, readFile as readFile7, rename as rename5, rm as rm4, writeFile as writeFile6 } from "fs/promises";
8017
+ import { dirname as dirname8, join as join12, resolve as resolve7 } from "path";
8018
+ var PROBABILITY_FLOOR = 1e-6;
8019
+ function assertDecisionPlanSupported(plan) {
8020
+ for (const step of plan.steps) {
8021
+ if (step.uses === "evaluate" && step.with.evaluator !== "behavior") {
8022
+ throw new Error(`Decision runner evaluates with the behavior evaluator; step ${step.id} uses ${step.with.evaluator}.`);
8023
+ }
8024
+ if (!["train", "evaluate", "compare"].includes(step.uses)) {
8025
+ throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
8026
+ }
8027
+ }
8028
+ }
8029
+ function decisionQuestion(spec) {
8030
+ return { ...spec.decision, instructions: buildSystemMessage(spec) };
8031
+ }
8032
+ function canonicalLabel(labels, output) {
8033
+ const normalized = output.trim().toLowerCase();
8034
+ const label = labels.find((candidate) => candidate.trim().toLowerCase() === normalized);
8035
+ if (!label) throw new Error(`Example output ${JSON.stringify(output)} is not a decision label (${labels.join(", ")}).`);
8036
+ return label;
8037
+ }
8038
+ async function readPredictions(path, ids, labels) {
8039
+ const rows2 = (await readFile7(path, "utf8")).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
8040
+ if (rows2.length !== ids.length || new Set(rows2.map((row) => row.id)).size !== rows2.length) {
8041
+ throw new Error(`Decision predictions must contain exactly one row per evaluation ID: ${path}`);
8042
+ }
8043
+ for (const row of rows2) validatePrediction(row, labels);
8044
+ const byId = new Map(rows2.map((row) => [row.id, row]));
8045
+ return ids.map((id) => {
8046
+ const row = byId.get(id);
8047
+ if (!row) throw new Error(`Decision predictions are missing row ${id}: ${path}`);
8048
+ if (!labels.includes(row.prediction)) throw new Error(`Decision prediction for ${id} is not a label: ${row.prediction}`);
8049
+ return row;
8050
+ });
8051
+ }
8052
+ function validatePrediction(row, labels) {
8053
+ const values = row?.probabilities;
8054
+ 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)) {
8055
+ throw new Error("Decision probabilities must contain a finite probability for every label.");
8056
+ }
8057
+ const sum = labels.reduce((total, label) => total + values[label], 0);
8058
+ const maximum = Math.max(...labels.map((label) => values[label]));
8059
+ const sumTolerance = labels.length * 5e-5 + 1e-8;
8060
+ const tolerance = 2e-5;
8061
+ 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) {
8062
+ throw new Error("Decision prediction must have normalized probabilities, an argmax label, matching confidence and nonnegative latency.");
8063
+ }
8064
+ }
8065
+ function scoreDecisionPredictions(args2) {
8066
+ if (args2.predictions.length !== args2.examples.length) {
8067
+ throw new Error("Decision prediction count must match the evaluation examples.");
8068
+ }
8069
+ for (const prediction of args2.predictions) validatePrediction(prediction, args2.labels);
8070
+ const perLabel = Object.fromEntries(
8071
+ args2.labels.map((label) => [label, { support: 0, correct: 0, recall: 0 }])
8072
+ );
8073
+ let logLoss = 0;
8074
+ let brier = 0;
8075
+ let confidence = 0;
8076
+ const results = args2.examples.map((example, index) => {
8077
+ const prediction = args2.predictions[index];
8078
+ const passed = prediction.prediction === example.output;
8079
+ const expectedProbability = prediction.probabilities[example.output] ?? 0;
8080
+ logLoss -= Math.log(Math.max(expectedProbability, PROBABILITY_FLOOR));
8081
+ brier += args2.labels.reduce((sum, label) => {
8082
+ const target = label === example.output ? 1 : 0;
8083
+ return sum + ((prediction.probabilities[label] ?? 0) - target) ** 2;
8084
+ }, 0);
8085
+ confidence += prediction.confidence;
8086
+ const entry = perLabel[example.output];
8087
+ entry.support += 1;
8088
+ if (passed) entry.correct += 1;
8089
+ return {
8090
+ prompt: example.input,
8091
+ expected: example.output,
8092
+ actual: prediction.prediction,
8093
+ passed,
8094
+ score: passed ? 1 : 0,
8095
+ reasoning: `p(expected)=${expectedProbability.toFixed(4)}; confidence=${prediction.confidence.toFixed(4)}`,
8096
+ latency_ms: Math.max(0, Math.round(prediction.latency_ms)),
8097
+ scored_by: "exact_match"
8098
+ };
8099
+ });
8100
+ for (const entry of Object.values(perLabel)) entry.recall = entry.support ? entry.correct / entry.support : 0;
8101
+ const total = results.length;
8102
+ const mean = (value) => total ? value / total : 0;
8103
+ const accuracy = mean(results.filter((result2) => result2.passed).length);
8104
+ const report = evalReportSchema.parse({
8105
+ kind: args2.kind,
8106
+ model_id: args2.modelId,
8107
+ total,
8108
+ eval_examples_total: total,
8109
+ eval_examples_used: total,
8110
+ eval_truncated: false,
8111
+ eval_split: "spec_holdout",
8112
+ eval_sample_seed: args2.sampleSeed,
8113
+ avg_score: accuracy,
8114
+ pass_rate: accuracy,
8115
+ exact_match_rate: accuracy,
8116
+ avg_latency_ms: Math.round(mean(results.reduce((sum, result2) => sum + result2.latency_ms, 0))),
8117
+ results,
8118
+ artifact_uri: fileUri(args2.outputPath),
8119
+ scoring_method: "exact_match",
8120
+ scoring_mode: "exact_match"
8121
+ });
8122
+ return {
8123
+ report,
8124
+ metrics: {
8125
+ accuracy,
8126
+ log_loss: mean(logLoss),
8127
+ brier: mean(brier),
8128
+ mean_confidence: mean(confidence),
8129
+ per_label: perLabel
8130
+ }
8131
+ };
8132
+ }
8133
+ async function defaultDecisionSpawn(args2) {
8134
+ const launched = buildDecisionPythonCommand(args2.entrypoint, ["--config", args2.configPath]);
8135
+ const result2 = await runLoggedProcess({
8136
+ command: launched.command,
8137
+ commandArgs: launched.commandArgs,
8138
+ env: withDecisionPythonEnvironment(process.env),
8139
+ logPath: args2.logPath,
8140
+ stage: args2.stepId,
8141
+ appendLog: true,
8142
+ onLine: (line, stream) => {
8143
+ if (stream !== "stdout" || !args2.onProgress) return;
8144
+ try {
8145
+ const event = JSON.parse(line);
8146
+ if (event.event === "step") {
8147
+ args2.onProgress(`${args2.stepId}: step ${event.step}/${event.total_steps} (epoch ${event.epoch}) loss ${event.loss}`);
8148
+ }
8149
+ } catch {
8150
+ }
8151
+ }
8152
+ });
8153
+ if (result2.exitCode !== 0) {
8154
+ throw new Error(
8155
+ `Decision step "${args2.stepId}" failed (exit ${result2.exitCode}). Log: ${args2.logPath}${result2.stderr ? `
8156
+ ${result2.stderr}` : ""}`
8157
+ );
8158
+ }
8159
+ }
8160
+ async function requireRegularNonemptyFile2(path, description) {
8161
+ const info = await lstat7(path).catch(() => null);
8162
+ if (!info || info.isSymbolicLink() || !info.isFile() || info.size === 0) {
8163
+ throw new Error(`${description} must be a non-empty regular file: ${path}`);
8164
+ }
8165
+ }
8166
+ async function readJsonObject2(path, description) {
8167
+ const parsed = JSON.parse(await readFile7(path, "utf8").catch((error) => {
8168
+ throw new Error(`${description} is missing or unreadable: ${path}`, { cause: error });
8169
+ }));
8170
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8171
+ throw new Error(`${description} must be a JSON object: ${path}`);
8172
+ }
8173
+ return parsed;
8174
+ }
8175
+ async function writePrivateAtomic(path, content) {
8176
+ await mkdir5(dirname8(path), { recursive: true, mode: 448 });
8177
+ const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
8178
+ try {
8179
+ await writeFile6(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
8180
+ await rename5(temporary, path);
8181
+ } finally {
8182
+ await rm4(temporary, { force: true });
8183
+ }
8184
+ }
8185
+ async function writePrivateJson2(path, value) {
8186
+ await writePrivateAtomic(path, `${JSON.stringify(value, null, 2)}
8187
+ `);
8188
+ }
8189
+ function producer(reference) {
8190
+ return reference.slice(0, reference.lastIndexOf("."));
8191
+ }
8192
+ function jsonl(rows2) {
8193
+ return rows2.map((row) => JSON.stringify(row)).join("\n") + "\n";
8194
+ }
8195
+ async function runDecisionPipeline(args2) {
8196
+ assertDecisionPlanSupported(args2.plan);
8197
+ const model = resolveDecisionModel(args2.spec.base_model);
8198
+ const specDir = dirname8(resolve7(args2.specPath));
8199
+ 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())));
8200
+ if (await lstat7(outputDir).catch(() => null)) {
8201
+ throw new Error(`Decision output directory already exists: ${outputDir}`);
8202
+ }
8203
+ await mkdir5(outputDir, { recursive: true, mode: 448 });
8204
+ const labels = decisionLabels(args2.spec.decision);
8205
+ const question = decisionQuestion(args2.spec);
8206
+ const examples = args2.spec.examples.map((example) => ({
8207
+ input: example.input,
8208
+ output: canonicalLabel(labels, example.output)
8209
+ }));
8210
+ const seed = deriveSampleSeed(args2.spec.id ?? args2.spec.name);
8211
+ const split = splitSpecExamples(examples, seed);
8212
+ if (!split.train.length || !split.holdout.length) {
8213
+ throw new Error("Decision runs need at least one training and one held-out example.");
8214
+ }
8215
+ const hp = args2.spec.hyperparameters ?? {};
8216
+ const device = hp.device ?? "auto";
8217
+ const evalIds = split.holdout.map((_, index) => `eval-${index}`);
8218
+ const dataDir = join12(outputDir, "data");
8219
+ await mkdir5(dataDir, { recursive: true, mode: 448 });
8220
+ const trainPath = join12(dataDir, "train.jsonl");
8221
+ const evalInputsPath = join12(dataDir, "eval-inputs.jsonl");
8222
+ await writePrivateAtomic(trainPath, jsonl(split.train));
8223
+ await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
8224
+ await writePrivateJson2(join12(outputDir, "resolved-workflow.json"), {
8225
+ spec: args2.spec,
8226
+ plan: args2.plan,
8227
+ model: { id: model.id, revision: model.revision },
8228
+ question,
8229
+ split: { seed, train: split.train.length, eval: split.holdout.length }
8230
+ });
8231
+ const spawnStep = args2.spawnStep ?? defaultDecisionSpawn;
8232
+ const producedModels = /* @__PURE__ */ new Map();
8233
+ const reports = /* @__PURE__ */ new Map();
8234
+ const steps = [];
8235
+ let tunedModelDir;
8236
+ for (const step of args2.plan.steps) {
8237
+ const stepDir = join12(outputDir, step.id);
8238
+ await mkdir5(stepDir, { recursive: true, mode: 448 });
8239
+ const configPath2 = join12(stepDir, "config.json");
8240
+ const logPath = join12(stepDir, "step.log");
8241
+ const metricsPath = join12(stepDir, "metrics.json");
8242
+ if (step.uses === "train") {
8243
+ const modelDir = join12(stepDir, "model");
8244
+ args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
8245
+ await writePrivateJson2(configPath2, {
8246
+ model: model.id,
8247
+ revision: model.revision,
8248
+ question,
8249
+ train_path: trainPath,
8250
+ output_dir: modelDir,
8251
+ metrics_path: metricsPath,
8252
+ epochs: hp.n_epochs ?? model.defaultEpochs,
8253
+ learning_rate: hp.learning_rate ?? model.defaultLearningRate,
8254
+ batch_size: hp.batch_size ?? model.defaultBatchSize,
8255
+ freeze_encoder: hp.freeze_encoder ?? false,
8256
+ shuffle_options: hp.shuffle_options ?? true,
8257
+ seed: hp.seed ?? 0,
8258
+ device
8259
+ });
8260
+ await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
8261
+ const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
8262
+ if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
8263
+ await requireRegularNonemptyFile2(join12(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
8264
+ await readJsonObject2(join12(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
8265
+ producedModels.set(step.id, modelDir);
8266
+ tunedModelDir = modelDir;
8267
+ steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
8268
+ continue;
8269
+ }
8270
+ if (step.uses === "evaluate") {
8271
+ const source = step.with.model;
8272
+ const tuned = source !== "base";
8273
+ const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
8274
+ if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
8275
+ const predictionsPath = join12(stepDir, "predictions.jsonl");
8276
+ args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
8277
+ await writePrivateJson2(configPath2, {
8278
+ model: modelDir ?? model.id,
8279
+ ...tuned ? {} : { revision: model.revision },
8280
+ question,
8281
+ inputs_path: evalInputsPath,
8282
+ output_path: predictionsPath,
8283
+ metrics_path: metricsPath,
8284
+ device
8285
+ });
8286
+ await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
8287
+ const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
8288
+ if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
8289
+ const reportPath2 = join12(stepDir, "report.json");
8290
+ const scored = scoreDecisionPredictions({
8291
+ kind: tuned ? "candidate" : "baseline",
8292
+ modelId: modelDir ? fileUri(modelDir) : model.id,
8293
+ labels,
8294
+ examples: split.holdout,
8295
+ predictions: await readPredictions(predictionsPath, evalIds, labels),
8296
+ outputPath: reportPath2,
8297
+ sampleSeed: seed
8298
+ });
8299
+ await writePrivateJson2(reportPath2, { ...scored.report, decision_metrics: scored.metrics });
8300
+ reports.set(step.id, scored);
8301
+ steps.push({ id: step.id, uses: step.uses, model: modelDir ?? model.id, report: scored.report, decision_metrics: scored.metrics });
8302
+ continue;
8303
+ }
8304
+ if (step.uses !== "compare") throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
8305
+ const before = reports.get(producer(step.with.before.from));
8306
+ const after = reports.get(producer(step.with.after.from));
8307
+ if (!before || !after) throw new Error(`Step ${step.id} needs both evaluation reports.`);
8308
+ const comparison = {
8309
+ ...compareEvalReports(before.report, after.report),
8310
+ log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
8311
+ brier_delta: after.metrics.brier - before.metrics.brier
8312
+ };
8313
+ await writePrivateJson2(join12(stepDir, "comparison.json"), comparison);
8314
+ steps.push({ id: step.id, uses: step.uses, comparison });
8315
+ }
8316
+ const reportPath = join12(outputDir, "report.json");
8317
+ const report = {
8318
+ status: "succeeded",
8319
+ engine: "decision",
8320
+ ...args2.plan.name ? { name: args2.plan.name } : {},
8321
+ spec: resolve7(args2.specPath),
8322
+ base_model: model.id,
8323
+ base_model_revision: model.revision,
8324
+ question: { type: question.type, labels },
8325
+ split: { seed, train: split.train.length, eval: split.holdout.length, eval_split: "spec_holdout" },
8326
+ ...tunedModelDir ? { model_dir: tunedModelDir } : {},
8327
+ steps,
8328
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
8329
+ };
8330
+ await writePrivateJson2(reportPath, report);
8331
+ return {
8332
+ status: "succeeded",
8333
+ engine: "decision",
8334
+ ...args2.plan.name ? { name: args2.plan.name } : {},
8335
+ run_dir: outputDir,
8336
+ report_path: reportPath,
8337
+ ...tunedModelDir ? { model_dir: tunedModelDir } : {},
8338
+ steps
8339
+ };
8340
+ }
8341
+
6859
8342
  // src/commands/pipeline.ts
6860
8343
  var DEFAULT_SPEC_FILE = "tunedtensor.json";
6861
8344
  function parseList(value) {
@@ -6865,10 +8348,10 @@ function parseList(value) {
6865
8348
  return ids;
6866
8349
  }
6867
8350
  function loadPipelineFile(path) {
6868
- const resolved = resolve6(path);
8351
+ const resolved = resolve8(path);
6869
8352
  if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
6870
8353
  try {
6871
- return JSON.parse(readFileSync5(resolved, "utf8"));
8354
+ return JSON.parse(readFileSync6(resolved, "utf8"));
6872
8355
  } catch (error) {
6873
8356
  throw new Error(`Pipeline file must be JSON: ${error.message}`);
6874
8357
  }
@@ -6877,23 +8360,27 @@ function pipelineFromSpec(input) {
6877
8360
  return pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation);
6878
8361
  }
6879
8362
  function resolvePipelineDocument(options, specRequired) {
6880
- const specPath = resolve6(options.spec);
8363
+ const specPath = resolve8(options.spec);
6881
8364
  const requested = options.file ? loadPipelineFile(options.file) : void 0;
6882
8365
  if (!existsSync4(specPath)) {
6883
8366
  if (specRequired) throw new Error(`Behavior spec not found: ${options.spec}.`);
6884
8367
  if (requested !== void 0) return { document: parsePipeline(requested) };
6885
8368
  throw new Error(`Behavior spec not found: ${options.spec}. Create tunedtensor.json or pass an explicit --file for a recipe-only preview.`);
6886
8369
  }
6887
- const source = readFileSync5(specPath, "utf8");
8370
+ const source = readFileSync6(specPath, "utf8");
6888
8371
  const input = parseLocalRunInput(JSON.parse(source), specPath);
6889
8372
  const document = pipelineForRunInput(input, requested);
6890
8373
  const spec = {
6891
8374
  path: specPath,
6892
8375
  sha256: specHash(source),
6893
- engine: input.kind === "foundation-spec" ? "foundation" : "adapter"
8376
+ engine: specEngine(input)
6894
8377
  };
6895
8378
  return { document, input, spec };
6896
8379
  }
8380
+ function specEngine(input) {
8381
+ if (input.kind === "foundation-spec") return "foundation";
8382
+ return input.kind === "decision-spec" ? "decision" : "adapter";
8383
+ }
6897
8384
  function outputPlan(plan, hostWarnings = []) {
6898
8385
  if (isJsonMode()) {
6899
8386
  return printJson(hostWarnings.length ? { ...plan, host_warnings: hostWarnings } : plan);
@@ -6910,16 +8397,35 @@ function outputPlan(plan, hostWarnings = []) {
6910
8397
  async function hostWarningsForPipeline(document, input) {
6911
8398
  const snapshot = await readHardwareSnapshot();
6912
8399
  if (!snapshot) return [];
8400
+ if (input?.kind === "decision-spec") return warningsFromSnapshot(snapshot.capabilities, { engine: "decision" });
6913
8401
  const engine = isFoundationPipeline(parsePipeline(document)) ? "foundation" : "adapter";
6914
8402
  const baseModel = input && input.kind !== "foundation-spec" ? input.request.spec_snapshot.base_model : void 0;
6915
8403
  return warningsFromSnapshot(snapshot.capabilities, { engine, baseModel });
6916
8404
  }
8405
+ function percent(value) {
8406
+ return `${(value * 100).toFixed(1)}%`;
8407
+ }
8408
+ function printDecisionSummary(result2) {
8409
+ for (const step of result2.steps) {
8410
+ if (step.decision_metrics) {
8411
+ const metrics = step.decision_metrics;
8412
+ 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)`);
8413
+ }
8414
+ if (step.comparison) {
8415
+ const comparison = step.comparison;
8416
+ const sign = (value) => value >= 0 ? "+" : "";
8417
+ 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}`);
8418
+ }
8419
+ }
8420
+ if (result2.model_dir) console.log(`Tuned decision model: ${result2.model_dir}`);
8421
+ printSuccess(`Decision pipeline completed. Report: ${result2.report_path}`);
8422
+ }
6917
8423
  function registerPipelineCommands(parent) {
6918
8424
  const pipeline2 = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
6919
8425
  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
8426
  if (!options.file) {
6921
- const path2 = resolve6(options.spec ?? DEFAULT_SPEC_FILE);
6922
- const raw = JSON.parse(readFileSync5(path2, "utf8"));
8427
+ const path2 = resolve8(options.spec ?? DEFAULT_SPEC_FILE);
8428
+ const raw = JSON.parse(readFileSync6(path2, "utf8"));
6923
8429
  const input = parseLocalRunInput(raw, path2);
6924
8430
  if (input.kind === "request") throw new Error("Pipeline init requires a behavior spec.");
6925
8431
  if (options.engine && options.engine !== (input.spec.engine ?? "adapter")) throw new Error("--engine must match the spec.");
@@ -6937,12 +8443,12 @@ function registerPipelineCommands(parent) {
6937
8443
  if (engine !== "foundation" && options.spec) {
6938
8444
  throw new Error("--spec is only valid with --engine foundation.");
6939
8445
  }
6940
- const path = resolve6(options.file);
8446
+ const path = resolve8(options.file);
6941
8447
  if (existsSync4(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
6942
8448
  let recipe;
6943
8449
  if (engine === "foundation") {
6944
8450
  if (options.spec) {
6945
- const input = await loadLocalRunInput(resolve6(options.spec));
8451
+ const input = await loadLocalRunInput(resolve8(options.spec));
6946
8452
  if (input.kind !== "foundation-spec") {
6947
8453
  throw new Error("--spec must be a foundation tunedtensor.json.");
6948
8454
  }
@@ -6959,7 +8465,7 @@ function registerPipelineCommands(parent) {
6959
8465
  printSuccess(`Created ${options.file}`);
6960
8466
  });
6961
8467
  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));
8468
+ const result2 = await migrateProjectWorkflow(resolve8(options.spec));
6963
8469
  if (isJsonMode()) return printJson(result2);
6964
8470
  printSuccess(result2.migrated ? `Migrated ${result2.path}; originals retained as .bak files.` : "No legacy configuration found.");
6965
8471
  });
@@ -6983,7 +8489,7 @@ function registerPipelineCommands(parent) {
6983
8489
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
6984
8490
  outputPlan({ ...plan, ...spec ? { spec } : {} }, await hostWarningsForPipeline(document, input));
6985
8491
  });
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) => {
8492
+ 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").option("--verbose", "Also stream training and evaluation process logs").option("--quiet", "Do not print live stage progress").action(async (options, command) => {
6987
8493
  if (options.output && options.resume) {
6988
8494
  throw new Error("--output and --resume are mutually exclusive.");
6989
8495
  }
@@ -7016,8 +8522,8 @@ function registerPipelineCommands(parent) {
7016
8522
  const result3 = await runFoundationPipeline({
7017
8523
  spec: input.spec,
7018
8524
  plan,
7019
- specPath: resolve6(options.spec),
7020
- ...options.output || options.resume ? { outputDir: resolve6(options.resume ?? options.output) } : {},
8525
+ specPath: resolve8(options.spec),
8526
+ ...options.output || options.resume ? { outputDir: resolve8(options.resume ?? options.output) } : {},
7021
8527
  resume: Boolean(options.resume),
7022
8528
  gpu: config.gpu
7023
8529
  });
@@ -7025,15 +8531,40 @@ function registerPipelineCommands(parent) {
7025
8531
  printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
7026
8532
  return;
7027
8533
  }
8534
+ if (input.kind === "decision-spec") {
8535
+ if (options.resume) throw new Error("--resume is only valid for foundation pipelines.");
8536
+ const result3 = await runDecisionPipeline({
8537
+ spec: input.spec,
8538
+ plan,
8539
+ specPath: resolve8(options.spec),
8540
+ ...options.output ? { outputDir: resolve8(options.output) } : {},
8541
+ onProgress: isJsonMode() ? void 0 : (message) => console.error(message)
8542
+ });
8543
+ if (isJsonMode()) return printJson({ ...result3, spec });
8544
+ printDecisionSummary(result3);
8545
+ return;
8546
+ }
7028
8547
  if (options.output || options.resume) {
7029
- throw new Error("--output and --resume are only valid for foundation pipelines.");
8548
+ throw new Error("--output is only valid for foundation and decision pipelines; --resume only for foundation.");
7030
8549
  }
7031
8550
  const localPipeline = {
7032
8551
  version: 1,
7033
8552
  ...plan.name ? { name: plan.name } : {},
7034
8553
  steps: plan.steps.map(({ transfers: _transfers, ...step }) => step)
7035
8554
  };
7036
- const result2 = await runLocalPipeline({ request: input.request, config, pipeline: localPipeline, ...input.kind === "spec" ? { projectSpec: input.spec } : {} });
8555
+ const reporter = options.quiet ? void 0 : createRunConsoleReporter({ verbose: options.verbose });
8556
+ const { request: request2 } = await ensureDatasetCached({ request: input.request, config, reporter });
8557
+ const result2 = await runLocalPipeline({
8558
+ request: request2,
8559
+ config,
8560
+ pipeline: localPipeline,
8561
+ ...input.kind === "spec" ? { projectSpec: input.spec } : {},
8562
+ ...spec ? { specFile: { path: spec.path, sha256: spec.sha256 } } : {},
8563
+ ...reporter ? { reporter } : {}
8564
+ });
8565
+ if (!isJsonMode()) {
8566
+ console.error(`Run ${result2.request.run_id}: \`tt runs audit ${result2.request.run_id}\` verifies its record and shows timing and provenance.`);
8567
+ }
7037
8568
  if (isJsonMode()) return printJson({ ...result2, spec });
7038
8569
  printSuccess(`Pipeline completed with status ${result2.status}.`);
7039
8570
  });
@@ -7043,7 +8574,7 @@ function registerPipelineCommands(parent) {
7043
8574
  import { createInterface as createInterface3 } from "readline/promises";
7044
8575
  import { stdin as stdin2, stdout as stdout2 } from "process";
7045
8576
  import { Writable as Writable2 } from "stream";
7046
- import chalk7 from "chalk";
8577
+ import chalk10 from "chalk";
7047
8578
  async function promptForApiKey(input = stdin2, output = stdout2) {
7048
8579
  if (input.isTTY !== true || output.isTTY !== true) {
7049
8580
  throw new Error(
@@ -7085,9 +8616,9 @@ function registerAuthCommands(parent) {
7085
8616
  const settingsUrl = `${getBaseUrl(parent.optsWithGlobals()).replace(/\/$/, "")}/dashboard/settings`;
7086
8617
  console.log();
7087
8618
  console.log(
7088
- ` To get your TT access token, go to ${chalk7.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
8619
+ ` To get your TT access token, go to ${chalk10.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
7089
8620
  );
7090
- console.log(` ${chalk7.hex("#A78BFA").underline(settingsUrl)}`);
8621
+ console.log(` ${chalk10.hex("#A78BFA").underline(settingsUrl)}`);
7091
8622
  console.log();
7092
8623
  apiKey = await promptForApiKey();
7093
8624
  }
@@ -7143,7 +8674,7 @@ function registerAuthCommands(parent) {
7143
8674
  }
7144
8675
 
7145
8676
  // src/commands/balance.ts
7146
- import chalk8 from "chalk";
8677
+ import chalk11 from "chalk";
7147
8678
  var KIND_LABELS = {
7148
8679
  signup_bonus: "Signup bonus",
7149
8680
  topup: "Top-up",
@@ -7158,8 +8689,8 @@ function formatCents(cents) {
7158
8689
  return `${sign}$${(abs / 100).toFixed(2)}`;
7159
8690
  }
7160
8691
  function formatSigned(cents) {
7161
- if (cents > 0) return chalk8.green(`+${formatCents(cents)}`);
7162
- if (cents < 0) return chalk8.red(formatCents(cents));
8692
+ if (cents > 0) return chalk11.green(`+${formatCents(cents)}`);
8693
+ if (cents < 0) return chalk11.red(formatCents(cents));
7163
8694
  return formatCents(cents);
7164
8695
  }
7165
8696
  function registerBalanceCommands(parent) {
@@ -7174,7 +8705,7 @@ function registerBalanceCommands(parent) {
7174
8705
  return printJson({ balance, transactions });
7175
8706
  }
7176
8707
  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));
8708
+ const balanceLine = lowBalance ? chalk11.red(formatCents(balance.balance_cents)) + chalk11.dim(" (low)") : chalk11.bold.green(formatCents(balance.balance_cents));
7178
8709
  printDetail([
7179
8710
  ["Credits", balanceLine],
7180
8711
  [
@@ -7189,7 +8720,7 @@ function registerBalanceCommands(parent) {
7189
8720
  ]);
7190
8721
  if (transactions && transactions.length > 0) {
7191
8722
  console.log(`
7192
- ${chalk8.bold("Recent transactions")}`);
8723
+ ${chalk11.bold("Recent transactions")}`);
7193
8724
  printTable(
7194
8725
  ["Date", "Type", "Amount", "Balance", "Reference"],
7195
8726
  transactions.map((row) => [
@@ -7197,19 +8728,19 @@ ${chalk8.bold("Recent transactions")}`);
7197
8728
  KIND_LABELS[row.kind] ?? row.kind,
7198
8729
  formatSigned(row.amount_cents),
7199
8730
  formatCents(row.balance_after_cents),
7200
- row.reference_id ? shortId(row.reference_id) : chalk8.dim("\u2014")
8731
+ row.reference_id ? shortId(row.reference_id) : chalk11.dim("\u2014")
7201
8732
  ])
7202
8733
  );
7203
8734
  } else {
7204
8735
  console.log(
7205
8736
  `
7206
- ${chalk8.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
8737
+ ${chalk11.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
7207
8738
  );
7208
8739
  }
7209
8740
  if (lowBalance) {
7210
8741
  console.log(
7211
8742
  `
7212
- ${formatStatus("preparing")} ${chalk8.yellow(
8743
+ ${formatStatus("preparing")} ${chalk11.yellow(
7213
8744
  "Run `tt topup` to add more credits before starting a run."
7214
8745
  )}`
7215
8746
  );
@@ -7218,7 +8749,7 @@ ${formatStatus("preparing")} ${chalk8.yellow(
7218
8749
  }
7219
8750
 
7220
8751
  // src/commands/topup.ts
7221
- import chalk9 from "chalk";
8752
+ import chalk12 from "chalk";
7222
8753
  import open4 from "open";
7223
8754
  var PRESETS_USD = [10, 25, 50, 100];
7224
8755
  var MIN_USD = 5;
@@ -7257,9 +8788,9 @@ function registerTopupCommands(parent) {
7257
8788
  "amount is required in --json mode (use --amount <usd>)"
7258
8789
  );
7259
8790
  }
7260
- console.log(chalk9.bold("Add credits via Stripe Checkout"));
8791
+ console.log(chalk12.bold("Add credits via Stripe Checkout"));
7261
8792
  console.log(
7262
- chalk9.dim(
8793
+ chalk12.dim(
7263
8794
  `Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
7264
8795
  )
7265
8796
  );
@@ -7286,8 +8817,8 @@ function registerTopupCommands(parent) {
7286
8817
  }
7287
8818
  const url = data3.checkout_url;
7288
8819
  console.log(
7289
- `${chalk9.bold("Checkout URL:")} ${chalk9.hex("#A78BFA")(url)}
7290
- ` + chalk9.dim("Complete the payment to credit your account.")
8820
+ `${chalk12.bold("Checkout URL:")} ${chalk12.hex("#A78BFA")(url)}
8821
+ ` + chalk12.dim("Complete the payment to credit your account.")
7291
8822
  );
7292
8823
  if (options.open !== false) {
7293
8824
  try {
@@ -7295,7 +8826,7 @@ function registerTopupCommands(parent) {
7295
8826
  printSuccess("Opened Stripe Checkout in your browser.");
7296
8827
  } catch {
7297
8828
  console.log(
7298
- chalk9.yellow(
8829
+ chalk12.yellow(
7299
8830
  "Could not open browser automatically \u2014 copy the URL above."
7300
8831
  )
7301
8832
  );
@@ -7306,30 +8837,30 @@ function registerTopupCommands(parent) {
7306
8837
 
7307
8838
  // src/commands/publish.ts
7308
8839
  import { createInterface as createInterface4 } from "readline/promises";
7309
- 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";
8840
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
8841
+ import { homedir as homedir3 } from "os";
8842
+ import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
7312
8843
  import { stdin as defaultStdin, stdout as defaultStdout } from "process";
7313
8844
  var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
7314
8845
  function expandPath2(value, baseDirectory, homeDirectory) {
7315
8846
  if (value === "~") return homeDirectory;
7316
- if (value.startsWith("~/")) return resolve7(homeDirectory, value.slice(2));
7317
- return isAbsolute5(value) ? resolve7(value) : resolve7(baseDirectory, value);
8847
+ if (value.startsWith("~/")) return resolve9(homeDirectory, value.slice(2));
8848
+ return isAbsolute5(value) ? resolve9(value) : resolve9(baseDirectory, value);
7318
8849
  }
7319
8850
  function resolvePublishStoreRoot(options) {
7320
8851
  const cwd = options?.cwd ?? process.cwd();
7321
8852
  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");
8853
+ const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir3());
8854
+ const localConfigPath = join14(cwd, "local-runner.json");
8855
+ const projectPath = join14(cwd, "tunedtensor.json");
7325
8856
  if (existsSync5(projectPath)) {
7326
- const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync6(projectPath, "utf8")));
8857
+ const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync7(projectPath, "utf8")));
7327
8858
  if (existsSync5(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
7328
8859
  if (!existsSync5(localConfigPath)) return expandPath2(spec.runtime?.storeRoot ?? (env.TT_LOCAL_HOME?.trim() || DEFAULT_PROJECT_STORE_ROOT), cwd, homeDirectory);
7329
8860
  }
7330
8861
  if (existsSync5(localConfigPath)) {
7331
8862
  try {
7332
- const raw = JSON.parse(readFileSync6(localConfigPath, "utf-8"));
8863
+ const raw = JSON.parse(readFileSync7(localConfigPath, "utf-8"));
7333
8864
  if (typeof raw.storeRoot === "string" && raw.storeRoot.trim()) {
7334
8865
  return expandPath2(raw.storeRoot, cwd, homeDirectory);
7335
8866
  }
@@ -7393,7 +8924,7 @@ function assertPublishPayloadSize(payload) {
7393
8924
  }
7394
8925
  }
7395
8926
  function registerPublishCommand(parent) {
7396
- parent.command("publish").description("Publish local run evidence to the Tuned Tensor dashboard").argument("[runId]", "Local run id or prefix (defaults to latest completed/failed run)").option("-k, --api-key <key>", "API key (tt_...)").option("-u, --base-url <url>", "API base URL").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be published without uploading").action(async (runId, cmdOpts) => {
8927
+ parent.command("publish").description("Publish local run evidence to the Tuned Tensor dashboard").argument("[runId]", "Local run id or prefix (defaults to latest completed/failed run)").option("-k, --api-key <key>", "API key (tt_...)").option("-u, --base-url <url>", "API base URL").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be published without uploading").option("--allow-unverified", "Publish evidence that cannot be fully verified (older TT versions, unreadable events)").action(async (runId, cmdOpts) => {
7397
8928
  const inherited = parent.optsWithGlobals();
7398
8929
  const opts = {
7399
8930
  apiKey: cmdOpts.apiKey ?? inherited.apiKey,
@@ -7412,12 +8943,27 @@ function registerPublishCommand(parent) {
7412
8943
  const report = runReportSchema.parse(rawReport);
7413
8944
  const specRecord = await store.getSpec(run.behavior_spec_id);
7414
8945
  const exampleCount = report.candidate.results.length || report.baseline.results.length;
8946
+ const audit = await auditRun(store, run.id);
8947
+ if (audit.verdict === "tampered") {
8948
+ throw new Error(
8949
+ `Run ${run.id} evidence does not verify: ${audit.findings.join(" ")} Inspect it with \`tt runs audit ${run.id}\`; tampered runs are not published.`
8950
+ );
8951
+ }
7415
8952
  const payload = {
7416
8953
  spec: {
7417
8954
  id: specRecord.id,
7418
8955
  ...specRecord.spec
7419
8956
  },
7420
- report
8957
+ report: {
8958
+ ...report,
8959
+ audit: {
8960
+ verdict: audit.verdict,
8961
+ findings: audit.findings,
8962
+ event_chain: { status: audit.chain.status, events: audit.chain.events, head_hash: audit.chain.head_hash },
8963
+ report_sha256: audit.report.recorded_sha256,
8964
+ timeline: audit.timeline
8965
+ }
8966
+ }
7421
8967
  };
7422
8968
  assertPublishPayloadSize(payload);
7423
8969
  if (isJsonMode() && cmdOpts.dryRun) {
@@ -7445,6 +8991,14 @@ function registerPublishCommand(parent) {
7445
8991
  printWarning(
7446
8992
  "Prompts and model outputs from the run report will be uploaded to your Tuned Tensor account."
7447
8993
  );
8994
+ if (audit.verdict !== "verified") {
8995
+ printWarning(`Run evidence is unverified: ${audit.findings.join(" ")}`);
8996
+ }
8997
+ }
8998
+ if (audit.verdict !== "verified" && !cmdOpts.allowUnverified && !cmdOpts.dryRun) {
8999
+ throw new Error(
9000
+ `Run ${run.id} evidence cannot be fully verified: ${audit.findings.join(" ")} Review it with \`tt runs audit\`, then pass --allow-unverified to publish it anyway; the dashboard will show it as unverified.`
9001
+ );
7448
9002
  }
7449
9003
  if (cmdOpts.dryRun) {
7450
9004
  if (isJsonMode()) {
@@ -7489,7 +9043,7 @@ function registerPublishCommand(parent) {
7489
9043
  }
7490
9044
 
7491
9045
  // src/commands/specs.ts
7492
- import { readFileSync as readFileSync7 } from "fs";
9046
+ import { readFileSync as readFileSync8 } from "fs";
7493
9047
 
7494
9048
  // src/resolve.ts
7495
9049
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -7593,7 +9147,7 @@ var SpecBodyError = class extends Error {
7593
9147
  function loadSpecBody(filePath, mode) {
7594
9148
  let parsed;
7595
9149
  try {
7596
- parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
9150
+ parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
7597
9151
  } catch (e) {
7598
9152
  throw new SpecBodyError(
7599
9153
  `Could not parse ${filePath}: ${e.message}`
@@ -7722,7 +9276,7 @@ function registerSpecsCommands(parent) {
7722
9276
  }
7723
9277
 
7724
9278
  // src/commands/datasets.ts
7725
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
9279
+ import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
7726
9280
  var DOCUMENT_OCR_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
7727
9281
  function formatBytes(bytes) {
7728
9282
  if (bytes < 1024) return `${bytes} B`;
@@ -7852,7 +9406,7 @@ function parseDatasetFormat(value) {
7852
9406
  throw new Error("--format must be one of: jsonl, document_ocr_jsonl");
7853
9407
  }
7854
9408
  function validateDatasetFile(file, requestedFormat) {
7855
- const lines = readFileSync8(file, "utf8").split(/\r?\n/);
9409
+ const lines = readFileSync9(file, "utf8").split(/\r?\n/);
7856
9410
  const errors = [];
7857
9411
  let rowCount = 0;
7858
9412
  let detectedFormat = null;
@@ -7984,7 +9538,7 @@ function registerDatasetsCommands(parent) {
7984
9538
  }
7985
9539
  const format = validateDatasetFile(file, parseDatasetFormat(cmdOpts.format));
7986
9540
  const name = cmdOpts.name || file.split("/").pop().replace(/\.jsonl?$/, "");
7987
- const fileBytes = readFileSync8(file);
9541
+ const fileBytes = readFileSync9(file);
7988
9542
  const uploadUrl = await post(
7989
9543
  "/datasets/upload-url",
7990
9544
  {
@@ -8029,12 +9583,12 @@ function registerDatasetsCommands(parent) {
8029
9583
  }
8030
9584
 
8031
9585
  // src/commands/label.ts
8032
- import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
9586
+ import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
8033
9587
  function formatCents2(cents) {
8034
9588
  return `$${(cents / 100).toFixed(2)}`;
8035
9589
  }
8036
9590
  function sleep(ms) {
8037
- return new Promise((resolve11) => setTimeout(resolve11, ms));
9591
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
8038
9592
  }
8039
9593
  function parseCsvHeader(line) {
8040
9594
  const fields = [];
@@ -8073,7 +9627,7 @@ function validateUnlabeledFile(file, inputColumn) {
8073
9627
  "CSV uploads need --input-column <name> to say which column holds the input text."
8074
9628
  );
8075
9629
  }
8076
- const firstLine = readFileSync9(file, "utf8").split(/\r?\n/, 1)[0] ?? "";
9630
+ const firstLine = readFileSync10(file, "utf8").split(/\r?\n/, 1)[0] ?? "";
8077
9631
  const columns = parseCsvHeader(firstLine);
8078
9632
  if (!columns.includes(inputColumn)) {
8079
9633
  throw new Error(
@@ -8085,7 +9639,7 @@ function validateUnlabeledFile(file, inputColumn) {
8085
9639
  if (!lower.endsWith(".jsonl") && !lower.endsWith(".json")) {
8086
9640
  throw new Error("Labeling source file must be .jsonl or .csv");
8087
9641
  }
8088
- const lines = readFileSync9(file, "utf8").split(/\r?\n/);
9642
+ const lines = readFileSync10(file, "utf8").split(/\r?\n/);
8089
9643
  const errors = [];
8090
9644
  let rowCount = 0;
8091
9645
  for (const [index, rawLine] of lines.entries()) {
@@ -8208,7 +9762,7 @@ function registerLabelCommands(parent) {
8208
9762
  const { format } = validateUnlabeledFile(file, cmdOpts.inputColumn);
8209
9763
  const specId = await resolveSpecId(cmdOpts.spec, opts);
8210
9764
  const name = cmdOpts.name || file.split("/").pop().replace(/\.(jsonl|json|csv)$/, "");
8211
- const fileBytes = readFileSync9(file);
9765
+ const fileBytes = readFileSync10(file);
8212
9766
  const contentType = format === "csv" ? "text/csv" : "application/jsonl";
8213
9767
  const { data: uploadUrl } = await post(
8214
9768
  "/labeling-jobs/upload-url",
@@ -8381,7 +9935,7 @@ function registerLabelCommands(parent) {
8381
9935
  printError("Provide --output <text> or --file <path>.");
8382
9936
  process.exit(1);
8383
9937
  }
8384
- const output = cmdOpts.output ?? readFileSync9(cmdOpts.file, "utf8");
9938
+ const output = cmdOpts.output ?? readFileSync10(cmdOpts.file, "utf8");
8385
9939
  const jobId = await resolveLabelingJobId(id, opts);
8386
9940
  const indexes = parseIndexList(cmdOpts.row);
8387
9941
  const rowIds = await resolveRowIds(jobId, indexes, opts);
@@ -8871,22 +10425,22 @@ import {
8871
10425
  statSync as statSync4,
8872
10426
  createWriteStream,
8873
10427
  unlinkSync,
8874
- readFileSync as readFileSync11,
10428
+ readFileSync as readFileSync12,
8875
10429
  writeFileSync as writeFileSync5,
8876
10430
  readdirSync,
8877
10431
  rmSync
8878
10432
  } 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";
10433
+ import { homedir as homedir4 } from "os";
10434
+ import { dirname as dirname11, join as join15, resolve as resolve11, basename as basename5, normalize, isAbsolute as isAbsolute6 } from "path";
8881
10435
  import { Readable, Transform } from "stream";
8882
10436
  import { pipeline } from "stream/promises";
8883
10437
 
8884
10438
  // src/commands/init.ts
8885
- import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
8886
- import { resolve as resolve8 } from "path";
10439
+ import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
10440
+ import { resolve as resolve10 } from "path";
8887
10441
  var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
8888
10442
  function loadSpec(filePath) {
8889
- const resolved = resolve8(filePath || DEFAULT_SPEC_FILE2);
10443
+ const resolved = resolve10(filePath || DEFAULT_SPEC_FILE2);
8890
10444
  if (!existsSync8(resolved)) {
8891
10445
  printError(
8892
10446
  `Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
@@ -8894,29 +10448,29 @@ Run \`tt init\` to create one.`
8894
10448
  );
8895
10449
  process.exit(1);
8896
10450
  }
8897
- const raw = JSON.parse(readFileSync10(resolved, "utf-8"));
10451
+ const raw = JSON.parse(readFileSync11(resolved, "utf-8"));
8898
10452
  return raw;
8899
10453
  }
8900
10454
 
8901
10455
  // src/serve-manager.ts
8902
10456
  import { spawn as spawn2 } from "child_process";
8903
- import { randomUUID as randomUUID9 } from "crypto";
10457
+ import { randomUUID as randomUUID10 } from "crypto";
8904
10458
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
8905
10459
  import { createServer } from "http";
8906
10460
  import { createServer as createNetServer } from "net";
8907
- import { dirname as dirname8 } from "path";
10461
+ import { dirname as dirname10 } from "path";
8908
10462
  var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
8909
10463
  function parsePositiveInteger(value, fallback) {
8910
10464
  return Number.isInteger(value) && value >= 0 ? value : fallback;
8911
10465
  }
8912
10466
  function sleep2(ms) {
8913
- return new Promise((resolve11) => setTimeout(resolve11, ms));
10467
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
8914
10468
  }
8915
10469
  function readRequestBody(req) {
8916
- return new Promise((resolve11, reject) => {
10470
+ return new Promise((resolve13, reject) => {
8917
10471
  const chunks = [];
8918
10472
  req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
8919
- req.on("end", () => resolve11(Buffer.concat(chunks)));
10473
+ req.on("end", () => resolve13(Buffer.concat(chunks)));
8920
10474
  req.on("error", reject);
8921
10475
  });
8922
10476
  }
@@ -8984,7 +10538,7 @@ function errorSummary(response, fallback) {
8984
10538
  return typeof message === "string" ? message : fallback;
8985
10539
  }
8986
10540
  async function allocatePort(host) {
8987
- return new Promise((resolve11, reject) => {
10541
+ return new Promise((resolve13, reject) => {
8988
10542
  const server = createNetServer();
8989
10543
  server.on("error", reject);
8990
10544
  server.listen(0, host, () => {
@@ -8994,7 +10548,7 @@ async function allocatePort(host) {
8994
10548
  return;
8995
10549
  }
8996
10550
  const port = address.port;
8997
- server.close(() => resolve11(port));
10551
+ server.close(() => resolve13(port));
8998
10552
  });
8999
10553
  });
9000
10554
  }
@@ -9034,11 +10588,11 @@ var ManagedModelServer = class {
9034
10588
  sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
9035
10589
  });
9036
10590
  });
9037
- await new Promise((resolve11, reject) => {
10591
+ await new Promise((resolve13, reject) => {
9038
10592
  this.server?.once("error", reject);
9039
10593
  this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
9040
10594
  this.server?.off("error", reject);
9041
- resolve11();
10595
+ resolve13();
9042
10596
  });
9043
10597
  });
9044
10598
  }
@@ -9048,15 +10602,15 @@ var ManagedModelServer = class {
9048
10602
  if (!this.server) return;
9049
10603
  const server = this.server;
9050
10604
  this.server = null;
9051
- await new Promise((resolve11, reject) => {
9052
- server.close((err) => err ? reject(err) : resolve11());
10605
+ await new Promise((resolve13, reject) => {
10606
+ server.close((err) => err ? reject(err) : resolve13());
9053
10607
  });
9054
10608
  }
9055
10609
  async run() {
9056
10610
  await this.start();
9057
- await new Promise((resolve11, reject) => {
10611
+ await new Promise((resolve13, reject) => {
9058
10612
  this.server?.once("error", reject);
9059
- this.server?.once("close", resolve11);
10613
+ this.server?.once("close", resolve13);
9060
10614
  });
9061
10615
  }
9062
10616
  async handle(req, res) {
@@ -9124,7 +10678,7 @@ var ManagedModelServer = class {
9124
10678
  });
9125
10679
  }
9126
10680
  async processGeneration(route, body, res, receivedAt) {
9127
- const requestId = `ttreq-${randomUUID9()}`;
10681
+ const requestId = `ttreq-${randomUUID10()}`;
9128
10682
  const startedAt = Date.now();
9129
10683
  const queuedMs = startedAt - receivedAt;
9130
10684
  this.activeRequests += 1;
@@ -9230,7 +10784,7 @@ var ManagedModelServer = class {
9230
10784
  try {
9231
10785
  child.kill("SIGTERM");
9232
10786
  await Promise.race([
9233
- new Promise((resolve11) => child.once("exit", () => resolve11())),
10787
+ new Promise((resolve13) => child.once("exit", () => resolve13())),
9234
10788
  sleep2(5e3).then(() => {
9235
10789
  if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
9236
10790
  })
@@ -9308,7 +10862,7 @@ var ManagedModelServer = class {
9308
10862
  const line = `${JSON.stringify(record4)}
9309
10863
  `;
9310
10864
  if (this.options.logFile) {
9311
- mkdirSync3(dirname8(this.options.logFile), { recursive: true });
10865
+ mkdirSync3(dirname10(this.options.logFile), { recursive: true });
9312
10866
  appendFileSync(this.options.logFile, line, "utf8");
9313
10867
  return;
9314
10868
  }
@@ -9856,7 +11410,7 @@ if __name__ == "__main__":
9856
11410
  function resolveOutputPath(output, filename) {
9857
11411
  if (!output) return filename;
9858
11412
  if (existsSync9(output) && statSync4(output).isDirectory()) {
9859
- return join13(output, filename);
11413
+ return join15(output, filename);
9860
11414
  }
9861
11415
  return output;
9862
11416
  }
@@ -9932,7 +11486,7 @@ async function downloadUrlToFile(url, outputPath) {
9932
11486
  if (!res.body) {
9933
11487
  throw new Error("Download failed: response body was empty");
9934
11488
  }
9935
- mkdirSync4(dirname9(outputPath), { recursive: true });
11489
+ mkdirSync4(dirname11(outputPath), { recursive: true });
9936
11490
  const file = createWriteStream(outputPath);
9937
11491
  const contentLength = parseContentLength(res.headers.get("content-length"));
9938
11492
  const progress = createDownloadProgress(
@@ -9963,15 +11517,15 @@ async function downloadUrlToFile(url, outputPath) {
9963
11517
  return contentLength;
9964
11518
  }
9965
11519
  function defaultCacheDir() {
9966
- const root = process.env.XDG_CACHE_HOME || join13(homedir3(), ".cache");
9967
- return join13(root, "tuned-tensor", "models");
11520
+ const root = process.env.XDG_CACHE_HOME || join15(homedir4(), ".cache");
11521
+ return join15(root, "tuned-tensor", "models");
9968
11522
  }
9969
11523
  function runtimeDir(cacheDir) {
9970
- return join13(cacheDir, "_runtime");
11524
+ return join15(cacheDir, "_runtime");
9971
11525
  }
9972
11526
  function runtimePythonPath(cacheDir) {
9973
11527
  const dir = runtimeDir(cacheDir);
9974
- return process.platform === "win32" ? join13(dir, "Scripts", "python.exe") : join13(dir, "bin", "python");
11528
+ return process.platform === "win32" ? join15(dir, "Scripts", "python.exe") : join15(dir, "bin", "python");
9975
11529
  }
9976
11530
  function resolveServePython(cacheDir, explicitPython) {
9977
11531
  if (explicitPython) return explicitPython;
@@ -10044,7 +11598,7 @@ print(json.dumps({"missing": missing}))
10044
11598
  }
10045
11599
  }
10046
11600
  function setupRuntimeCommands(python, venvDir, deps) {
10047
- const venvPython = process.platform === "win32" ? join13(venvDir, "Scripts", "python.exe") : join13(venvDir, "bin", "python");
11601
+ const venvPython = process.platform === "win32" ? join15(venvDir, "Scripts", "python.exe") : join15(venvDir, "bin", "python");
10048
11602
  return [
10049
11603
  [python, "-m", "venv", venvDir],
10050
11604
  [venvPython, "-m", "pip", "install", "--upgrade", "pip"],
@@ -10082,9 +11636,9 @@ function extractArchive(archivePath, targetDir, force) {
10082
11636
  return targetDir;
10083
11637
  }
10084
11638
  function localArchiveCacheDir(archivePath, cacheDir) {
10085
- const resolved = resolve9(archivePath);
11639
+ const resolved = resolve11(archivePath);
10086
11640
  const digest = createHash6("sha256").update(resolved).digest("hex").slice(0, 12);
10087
- return join13(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
11641
+ return join15(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
10088
11642
  }
10089
11643
  function buildSystemPrompt(spec) {
10090
11644
  const parts = [];
@@ -10100,39 +11654,39 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
10100
11654
  return parts.join("\n\n");
10101
11655
  }
10102
11656
  function findSpecPath(explicitPath, modelPath) {
10103
- const candidates = explicitPath ? [explicitPath] : [join13(process.cwd(), DEFAULT_SPEC_FILE2), join13(modelPath, DEFAULT_SPEC_FILE2)];
11657
+ const candidates = explicitPath ? [explicitPath] : [join15(process.cwd(), DEFAULT_SPEC_FILE2), join15(modelPath, DEFAULT_SPEC_FILE2)];
10104
11658
  for (const candidate of candidates) {
10105
- const resolved = resolve9(candidate);
11659
+ const resolved = resolve11(candidate);
10106
11660
  if (existsSync9(resolved) && statSync4(resolved).isFile()) return resolved;
10107
11661
  }
10108
11662
  return null;
10109
11663
  }
10110
11664
  function loadSystemPromptFromSpec(specPath) {
10111
- const spec = JSON.parse(readFileSync11(specPath, "utf8"));
11665
+ const spec = JSON.parse(readFileSync12(specPath, "utf8"));
10112
11666
  return buildSystemPrompt(spec);
10113
11667
  }
10114
11668
  function loadJsonSchemaForServe(schemaPath) {
10115
- const resolved = resolve9(schemaPath);
11669
+ const resolved = resolve11(schemaPath);
10116
11670
  if (!existsSync9(resolved) || !statSync4(resolved).isFile()) {
10117
11671
  throw new Error(`JSON schema file not found: ${schemaPath}`);
10118
11672
  }
10119
11673
  try {
10120
- const schema = JSON.parse(readFileSync11(resolved, "utf8"));
11674
+ const schema = JSON.parse(readFileSync12(resolved, "utf8"));
10121
11675
  return JSON.stringify(schema);
10122
11676
  } catch (err) {
10123
11677
  throw new Error(`JSON schema file is not valid JSON: ${schemaPath}`);
10124
11678
  }
10125
11679
  }
10126
11680
  function writeReferenceServerScript(cacheDir) {
10127
- const scriptDir = join13(cacheDir, "_server");
11681
+ const scriptDir = join15(cacheDir, "_server");
10128
11682
  mkdirSync4(scriptDir, { recursive: true });
10129
- const scriptPath = join13(scriptDir, "openai_reference_server.py");
11683
+ const scriptPath = join15(scriptDir, "openai_reference_server.py");
10130
11684
  writeFileSync5(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
10131
11685
  return scriptPath;
10132
11686
  }
10133
11687
  async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10134
11688
  if (existsSync9(target)) {
10135
- const resolved = resolve9(target);
11689
+ const resolved = resolve11(target);
10136
11690
  const stats = statSync4(resolved);
10137
11691
  if (stats.isDirectory()) {
10138
11692
  return {
@@ -10142,7 +11696,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10142
11696
  };
10143
11697
  }
10144
11698
  if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
10145
- const extractDir2 = join13(localArchiveCacheDir(resolved, cacheDir), "model");
11699
+ const extractDir2 = join15(localArchiveCacheDir(resolved, cacheDir), "model");
10146
11700
  return {
10147
11701
  modelPath: extractArchive(resolved, extractDir2, forceDownload),
10148
11702
  modelName: basename5(resolved).replace(/\.t(ar\.)?gz$/, ""),
@@ -10154,9 +11708,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
10154
11708
  const fullId = await resolveModelId(target, opts);
10155
11709
  const { data: model } = await get(`/models/${fullId}`, void 0, opts);
10156
11710
  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");
11711
+ const modelCacheDir = join15(cacheDir, fullId);
11712
+ const archivePath = join15(modelCacheDir, data3.filename);
11713
+ const extractDir = join15(modelCacheDir, "model");
10160
11714
  if (!existsSync9(archivePath) || forceDownload) {
10161
11715
  await downloadUrlToFile(data3.url, archivePath);
10162
11716
  }
@@ -10262,7 +11816,7 @@ function ollamaSlug(name) {
10262
11816
  function firstExisting(candidates) {
10263
11817
  for (const candidate of candidates) {
10264
11818
  if (candidate && existsSync9(candidate) && statSync4(candidate).isFile()) {
10265
- return resolve9(candidate);
11819
+ return resolve11(candidate);
10266
11820
  }
10267
11821
  }
10268
11822
  return null;
@@ -10272,14 +11826,14 @@ function llamaCppDir(explicit) {
10272
11826
  }
10273
11827
  function resolveConvertScript(opts, required) {
10274
11828
  if (opts.convertScript) {
10275
- const resolved = resolve9(opts.convertScript);
11829
+ const resolved = resolve11(opts.convertScript);
10276
11830
  if (required && !existsSync9(resolved)) {
10277
11831
  throw new Error(`Conversion script not found: ${opts.convertScript}`);
10278
11832
  }
10279
11833
  return resolved;
10280
11834
  }
10281
11835
  const dir = llamaCppDir(opts.llamaCpp);
10282
- const candidates = dir ? [join13(dir, "convert_hf_to_gguf.py"), join13(dir, "convert-hf-to-gguf.py")] : [];
11836
+ const candidates = dir ? [join15(dir, "convert_hf_to_gguf.py"), join15(dir, "convert-hf-to-gguf.py")] : [];
10283
11837
  const found = firstExisting(candidates);
10284
11838
  if (found) return found;
10285
11839
  if (required) {
@@ -10287,11 +11841,11 @@ function resolveConvertScript(opts, required) {
10287
11841
  "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
11842
  );
10289
11843
  }
10290
- return dir ? join13(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
11844
+ return dir ? join15(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
10291
11845
  }
10292
11846
  function resolveQuantizeBin(opts, required) {
10293
11847
  if (opts.quantizeBin) {
10294
- const resolved = resolve9(opts.quantizeBin);
11848
+ const resolved = resolve11(opts.quantizeBin);
10295
11849
  if (required && !existsSync9(resolved)) {
10296
11850
  throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
10297
11851
  }
@@ -10300,10 +11854,10 @@ function resolveQuantizeBin(opts, required) {
10300
11854
  const dir = llamaCppDir(opts.llamaCpp);
10301
11855
  if (dir) {
10302
11856
  const candidates = [
10303
- join13(dir, "build", "bin", "llama-quantize"),
10304
- join13(dir, "build", "bin", "quantize"),
10305
- join13(dir, "llama-quantize"),
10306
- join13(dir, "quantize")
11857
+ join15(dir, "build", "bin", "llama-quantize"),
11858
+ join15(dir, "build", "bin", "quantize"),
11859
+ join15(dir, "llama-quantize"),
11860
+ join15(dir, "quantize")
10307
11861
  ];
10308
11862
  const found = firstExisting(candidates);
10309
11863
  if (found) return found;
@@ -10451,7 +12005,7 @@ function registerModelsCommands(parent) {
10451
12005
  }
10452
12006
  const quantPlan = planQuant(String(cmdOpts.quant));
10453
12007
  const printOnly = Boolean(cmdOpts.printCommand);
10454
- const cacheDir = resolve9(cmdOpts.cacheDir || defaultCacheDir());
12008
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10455
12009
  mkdirSync4(cacheDir, { recursive: true });
10456
12010
  const serveTarget = await resolveServeTarget(
10457
12011
  target,
@@ -10460,9 +12014,9 @@ function registerModelsCommands(parent) {
10460
12014
  Boolean(cmdOpts.forceDownload)
10461
12015
  );
10462
12016
  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;
12017
+ const outputDir = resolve11(cmdOpts.output || join15(process.cwd(), `${slug}-gguf`));
12018
+ const finalPath = join15(outputDir, `${slug}.${quantPlan.quant}.gguf`);
12019
+ const intermediatePath = quantPlan.requiresQuantize ? join15(outputDir, `${slug}.f16.gguf`) : void 0;
10466
12020
  const convertScript = resolveConvertScript(cmdOpts, !printOnly);
10467
12021
  const convertOutfile = intermediatePath ?? finalPath;
10468
12022
  const steps = [
@@ -10500,7 +12054,7 @@ function registerModelsCommands(parent) {
10500
12054
  }
10501
12055
  if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
10502
12056
  }
10503
- modelfilePath = join13(outputDir, "Modelfile");
12057
+ modelfilePath = join15(outputDir, "Modelfile");
10504
12058
  modelfileContent = buildModelfile(basename5(finalPath), systemPrompt2);
10505
12059
  const createModel = cmdOpts.ollamaCreate !== false;
10506
12060
  if (createModel) {
@@ -10587,7 +12141,7 @@ function registerModelsCommands(parent) {
10587
12141
  }
10588
12142
  });
10589
12143
  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());
12144
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10591
12145
  const venvDir = runtimeDir(cacheDir);
10592
12146
  const venvPython = runtimePythonPath(cacheDir);
10593
12147
  const python = pickRuntimePython(cmdOpts.python);
@@ -10633,7 +12187,7 @@ function registerModelsCommands(parent) {
10633
12187
  });
10634
12188
  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
12189
  const opts = parent.optsWithGlobals();
10636
- const cacheDir = resolve9(cmdOpts.cacheDir || defaultCacheDir());
12190
+ const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
10637
12191
  mkdirSync4(cacheDir, { recursive: true });
10638
12192
  const python = resolveServePython(cacheDir, cmdOpts.python);
10639
12193
  if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
@@ -10682,7 +12236,7 @@ function registerModelsCommands(parent) {
10682
12236
  idle_timeout_seconds: idleTimeoutSeconds,
10683
12237
  restart_after_requests: restartAfterRequests,
10684
12238
  gate_field: String(cmdOpts.gateField),
10685
- log_file: cmdOpts.logFile ? resolve9(cmdOpts.logFile) : void 0
12239
+ log_file: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
10686
12240
  } : void 0;
10687
12241
  if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
10688
12242
  if (!isJsonMode()) {
@@ -10719,7 +12273,7 @@ function registerModelsCommands(parent) {
10719
12273
  idleTimeoutSeconds,
10720
12274
  restartAfterRequests,
10721
12275
  gateField: String(cmdOpts.gateField),
10722
- logFile: cmdOpts.logFile ? resolve9(cmdOpts.logFile) : void 0
12276
+ logFile: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
10723
12277
  });
10724
12278
  return;
10725
12279
  }
@@ -10745,17 +12299,17 @@ function registerModelsCommands(parent) {
10745
12299
  }
10746
12300
 
10747
12301
  // src/commands/push.ts
10748
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
10749
- import { resolve as resolve10 } from "path";
12302
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
12303
+ import { resolve as resolve12 } from "path";
10750
12304
  function persistRemoteId(filePath, id) {
10751
- const raw = JSON.parse(readFileSync12(filePath, "utf-8"));
12305
+ const raw = JSON.parse(readFileSync13(filePath, "utf-8"));
10752
12306
  raw.id = id;
10753
12307
  writeFileSync6(filePath, JSON.stringify(raw, null, 2) + "\n");
10754
12308
  }
10755
12309
  function registerPushCommand(parent) {
10756
12310
  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
12311
  const opts = parent.optsWithGlobals();
10758
- const filePath = resolve10(cmdOpts.file);
12312
+ const filePath = resolve12(cmdOpts.file);
10759
12313
  const spec = loadSpec(cmdOpts.file);
10760
12314
  const evalCaseErrors = validateEvalCases(spec);
10761
12315
  if (evalCaseErrors.length > 0) {
@@ -10901,10 +12455,10 @@ async function checkForCliUpdate(currentVersion, options = {}) {
10901
12455
  return null;
10902
12456
  }
10903
12457
  })();
10904
- const deadline = new Promise((resolve11) => {
12458
+ const deadline = new Promise((resolve13) => {
10905
12459
  timeout = setTimeout(() => {
10906
12460
  controller.abort();
10907
- resolve11(null);
12461
+ resolve13(null);
10908
12462
  }, timeoutMs);
10909
12463
  });
10910
12464
  try {
@@ -11029,7 +12583,7 @@ async function runSelfCommand(args2, options = {}) {
11029
12583
  if (!entrypoint) {
11030
12584
  throw new Error("Cannot locate the tt CLI entrypoint.");
11031
12585
  }
11032
- return await new Promise((resolve11, reject) => {
12586
+ return await new Promise((resolve13, reject) => {
11033
12587
  const child = spawn4(process.execPath, [entrypoint, ...args2], {
11034
12588
  cwd: options.cwd,
11035
12589
  env: options.env ?? process.env,
@@ -11069,7 +12623,7 @@ async function runSelfCommand(args2, options = {}) {
11069
12623
  child.once("close", (exitCode, signal) => {
11070
12624
  cleanup();
11071
12625
  const signalNumber = signal ? osConstants2.signals[signal] : void 0;
11072
- resolve11({
12626
+ resolve13({
11073
12627
  exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
11074
12628
  signal
11075
12629
  });
@@ -11132,7 +12686,7 @@ function createProgram(version, runtime = {}) {
11132
12686
  if (root.json) setJsonMode(true);
11133
12687
  if (root.color === false) {
11134
12688
  process.env.FORCE_COLOR = "0";
11135
- chalk10.level = 0;
12689
+ chalk13.level = 0;
11136
12690
  }
11137
12691
  });
11138
12692
  registerLocalCommands(program, {
@@ -11170,12 +12724,12 @@ function createProgram(version, runtime = {}) {
11170
12724
  `);
11171
12725
  return;
11172
12726
  }
11173
- output.write(`${chalk10.bold.hex("#8B5CF6")("Tuned Tensor status")}
12727
+ output.write(`${chalk13.bold.hex("#8B5CF6")("Tuned Tensor status")}
11174
12728
  `);
11175
12729
  output.write(`${formatShellStatus(context).join("\n")}
11176
12730
 
11177
12731
  `);
11178
- output.write(`${chalk10.bold("Context")}
12732
+ output.write(`${chalk13.bold("Context")}
11179
12733
  `);
11180
12734
  output.write(`${formatShellContext(context).join("\n")}
11181
12735
  `);
@@ -11276,7 +12830,7 @@ async function runCli(version, runtime = {}) {
11276
12830
  }
11277
12831
 
11278
12832
  // src/index.ts
11279
- runCli("0.20.0").catch((err) => {
12833
+ runCli("0.22.0").catch((err) => {
11280
12834
  reportError(err);
11281
12835
  process.exit(1);
11282
12836
  });