@tuned-tensor/cli 0.6.0 → 0.8.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
@@ -4,163 +4,7 @@
4
4
  import { spawn as spawn4 } from "child_process";
5
5
  import { constants as osConstants2 } from "os";
6
6
  import { Command } from "commander";
7
- import chalk7 from "chalk";
8
-
9
- // src/local-runner.ts
10
- import { constants as osConstants } from "os";
11
- import { randomUUID } from "crypto";
12
- import { createRequire } from "module";
13
- import { spawn } from "child_process";
14
- import {
15
- readFile,
16
- rm,
17
- stat,
18
- writeFile
19
- } from "fs/promises";
20
- import {
21
- basename as basename2,
22
- dirname,
23
- isAbsolute,
24
- join as join2,
25
- resolve
26
- } from "path";
27
-
28
- // src/base-models.ts
29
- var SUPPORTED_BASE_MODELS = [
30
- "google/gemma-4-E2B-it",
31
- "google/gemma-4-E4B-it",
32
- "Qwen/Qwen3.5-2B",
33
- "Qwen/Qwen3-VL-2B-Instruct",
34
- "Qwen/Qwen3.5-4B",
35
- "meta-llama/Llama-3.2-3B-Instruct",
36
- "microsoft/Phi-4-mini-instruct",
37
- "ibm-granite/granite-3.3-2b-instruct",
38
- "bigcode/starcoder2-3b"
39
- ];
40
- var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
41
- for (const model of SUPPORTED_BASE_MODELS) {
42
- BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
43
- }
44
- for (const [alias, model] of [
45
- ["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
46
- ["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
47
- ["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
48
- ["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
49
- ["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
50
- ["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
51
- ["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
52
- ["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
53
- ["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
54
- ["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
55
- ["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
56
- ["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
57
- ["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
58
- ["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
59
- ["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
60
- ["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
61
- ["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
62
- ["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
63
- ["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
64
- ["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
65
- ["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
66
- ["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
67
- ["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
68
- ["starcoder2-3b", "bigcode/starcoder2-3b"]
69
- ]) {
70
- BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
71
- }
72
- var UnsupportedBaseModelError = class extends Error {
73
- constructor(model) {
74
- const supported = SUPPORTED_BASE_MODELS.join(", ");
75
- super(
76
- `Unsupported base_model "${String(model)}". Supported base models: ${supported}`
77
- );
78
- this.name = "UnsupportedBaseModelError";
79
- }
80
- };
81
- function canonicalizeBaseModel(model) {
82
- if (typeof model !== "string" || !model.trim()) {
83
- throw new UnsupportedBaseModelError(model);
84
- }
85
- const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
86
- if (!canonical) {
87
- throw new UnsupportedBaseModelError(model);
88
- }
89
- return canonical;
90
- }
91
- function canonicalizeSpecBaseModel(body) {
92
- if ("base_model" in body && body.base_model !== void 0) {
93
- return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
94
- }
95
- return body;
96
- }
97
-
98
- // src/project-spec.ts
99
- var CLOUD_SPEC_KEYS = [
100
- "name",
101
- "description",
102
- "base_model",
103
- "system_prompt",
104
- "guidelines",
105
- "constraints",
106
- "examples",
107
- "eval_cases"
108
- ];
109
- var LOCAL_ONLY_SPEC_KEYS = [
110
- "hyperparameters",
111
- "dataset_prebuilt"
112
- ];
113
- var LOCAL_SPEC_KEYS = [
114
- "id",
115
- "name",
116
- "description",
117
- "system_prompt",
118
- "guidelines",
119
- "constraints",
120
- "base_model",
121
- "examples",
122
- ...LOCAL_ONLY_SPEC_KEYS
123
- ];
124
- var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
125
- var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
126
- var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
127
- var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
128
- ...CLOUD_SPEC_KEYS,
129
- ...LOCAL_SPEC_KEYS
130
- ]);
131
- function projectSpec(raw, supportedKeys) {
132
- const body = {};
133
- const droppedKeys = [];
134
- for (const [key, value] of Object.entries(raw)) {
135
- if (supportedKeys.has(key)) {
136
- body[key] = value;
137
- } else {
138
- droppedKeys.push(key);
139
- }
140
- }
141
- return {
142
- body: canonicalizeSpecBaseModel(body),
143
- droppedKeys
144
- };
145
- }
146
- function projectCloudSpec(raw) {
147
- return projectSpec(raw, CLOUD_SPEC_KEY_SET);
148
- }
149
- function projectLocalSpec(raw) {
150
- return projectSpec(raw, LOCAL_SPEC_KEY_SET);
151
- }
152
- function hasLocalOnlySpecFields(raw) {
153
- return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
154
- }
155
- function unknownProjectSpecKeys(raw) {
156
- return Object.keys(raw).filter(
157
- (key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
158
- );
159
- }
160
-
161
- // src/output.ts
162
- import chalk from "chalk";
163
- import Table from "cli-table3";
7
+ import chalk8 from "chalk";
164
8
 
165
9
  // src/client.ts
166
10
  import { readFileSync as readFileSync2, statSync } from "fs";
@@ -279,6 +123,37 @@ async function request(method, path, options = {}) {
279
123
  if (res.status === 204) return { data: null };
280
124
  return await res.json();
281
125
  }
126
+ async function postStream(path, body, opts) {
127
+ const key = resolveKey(opts);
128
+ const url = buildUrl(path, void 0, opts);
129
+ const res = await fetch(url, {
130
+ method: "POST",
131
+ headers: {
132
+ Authorization: `Bearer ${key}`,
133
+ Accept: "text/event-stream",
134
+ "Content-Type": "application/json"
135
+ },
136
+ body: JSON.stringify(body),
137
+ signal: opts?.signal
138
+ });
139
+ if (!res.ok) {
140
+ let errBody;
141
+ try {
142
+ errBody = await res.json();
143
+ } catch {
144
+ throw new ApiError(res.status, "UNKNOWN", res.statusText);
145
+ }
146
+ throw new ApiError(
147
+ res.status,
148
+ errBody.error?.code || "UNKNOWN",
149
+ errBody.error?.message || res.statusText
150
+ );
151
+ }
152
+ if (!res.body) {
153
+ throw new ApiError(502, "EMPTY_STREAM", "The agent returned an empty response.");
154
+ }
155
+ return res;
156
+ }
282
157
  function get(path, query, opts) {
283
158
  return request("GET", path, { query, opts });
284
159
  }
@@ -295,1855 +170,2682 @@ function del(path, opts) {
295
170
  return request("DELETE", path, { opts });
296
171
  }
297
172
 
298
- // src/output.ts
299
- var jsonMode = false;
300
- function setJsonMode(enabled) {
301
- jsonMode = enabled;
302
- }
303
- function isJsonMode() {
304
- return jsonMode;
305
- }
306
- function printJson(data) {
307
- console.log(JSON.stringify(data, null, 2));
308
- }
309
- function printTable(headers, rows2, meta) {
310
- const table = new Table({
311
- head: headers.map((h) => chalk.bold.cyan(h)),
312
- style: { head: [], border: [] }
313
- });
314
- for (const row of rows2) {
315
- table.push(row);
316
- }
317
- console.log(table.toString());
318
- if (meta) {
319
- const totalPages = Math.ceil(meta.total / meta.per_page);
320
- console.log(
321
- chalk.dim(
322
- `
323
- Page ${meta.page}/${totalPages} (${meta.total} total)`
324
- )
325
- );
326
- }
327
- }
328
- function printDetail(fields) {
329
- const maxLabel = Math.max(...fields.map(([l]) => l.length));
330
- for (const [label, value] of fields) {
331
- const paddedLabel = label.padEnd(maxLabel);
332
- console.log(`${chalk.bold.cyan(paddedLabel)} ${value ?? chalk.dim("\u2014")}`);
173
+ // src/agent-client.ts
174
+ function record(value) {
175
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
176
+ }
177
+ function stringValue(value) {
178
+ return typeof value === "string" && value.length > 0 ? value : null;
179
+ }
180
+ function dataEnvelope(value) {
181
+ const outer = record(value);
182
+ return outer && "data" in outer ? outer.data : value;
183
+ }
184
+ function normalizeEvent(block) {
185
+ let eventName = null;
186
+ const dataLines = [];
187
+ for (const rawLine of block.split(/\r?\n/)) {
188
+ if (!rawLine || rawLine.startsWith(":")) continue;
189
+ const separator = rawLine.indexOf(":");
190
+ const field = separator === -1 ? rawLine : rawLine.slice(0, separator);
191
+ const rawValue = separator === -1 ? "" : rawLine.slice(separator + 1);
192
+ const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
193
+ if (field === "event") eventName = value;
194
+ if (field === "data") dataLines.push(value);
195
+ }
196
+ const rawData = dataLines.join("\n");
197
+ if (!eventName && !rawData) return null;
198
+ if (rawData === "[DONE]") return { type: "final", payload: {} };
199
+ let decoded = rawData;
200
+ if (rawData) {
201
+ try {
202
+ decoded = JSON.parse(rawData);
203
+ } catch {
204
+ decoded = rawData;
205
+ }
333
206
  }
207
+ const decodedRecord = record(decoded);
208
+ const inferredType = stringValue(decodedRecord?.type) ?? stringValue(decodedRecord?.event);
209
+ const nestedData = record(decodedRecord?.data);
210
+ const payload = decodedRecord && nestedData && inferredType ? nestedData : decodedRecord ?? (rawData ? { text: rawData } : {});
211
+ const type = (eventName ?? inferredType ?? "message").toLowerCase().replaceAll("-", "_");
212
+ return { type, payload };
334
213
  }
335
- function printSuccess(message) {
336
- console.log(chalk.green("\u2713") + " " + message);
337
- }
338
- function printWarning(message) {
339
- console.log(chalk.yellow("!") + " " + message);
214
+ function createAgentEventDecoder(onEvent) {
215
+ let buffer = "";
216
+ const drain = (flush) => {
217
+ while (true) {
218
+ const boundary = buffer.match(/\r?\n\r?\n/);
219
+ if (!boundary || boundary.index === void 0) break;
220
+ const block = buffer.slice(0, boundary.index);
221
+ buffer = buffer.slice(boundary.index + boundary[0].length);
222
+ const event = normalizeEvent(block);
223
+ if (event) onEvent(event);
224
+ }
225
+ if (flush && buffer) {
226
+ const event = normalizeEvent(buffer);
227
+ buffer = "";
228
+ if (event) onEvent(event);
229
+ }
230
+ };
231
+ return {
232
+ push(chunk) {
233
+ buffer += chunk;
234
+ drain(false);
235
+ },
236
+ finish() {
237
+ drain(true);
238
+ }
239
+ };
340
240
  }
341
- function printError(message) {
342
- console.error(chalk.red("\u2717") + " " + message);
241
+ async function consumeAgentEventStream(response, onEvent) {
242
+ if (!response.body) throw new Error("The agent returned an empty response.");
243
+ const reader = response.body.getReader();
244
+ const decoder = new TextDecoder();
245
+ const events = createAgentEventDecoder(onEvent);
246
+ while (true) {
247
+ const { value, done } = await reader.read();
248
+ events.push(decoder.decode(value, { stream: !done }));
249
+ if (done) break;
250
+ }
251
+ events.finish();
252
+ }
253
+ function normalizeAction(value) {
254
+ const input = record(value);
255
+ const id = stringValue(input?.id);
256
+ const title = stringValue(input?.title);
257
+ if (!input || !id || !title) return null;
258
+ return {
259
+ id,
260
+ title,
261
+ summary: stringValue(input.summary) ?? "",
262
+ risk: stringValue(input.risk) ?? "unknown",
263
+ status: stringValue(input.status) ?? void 0,
264
+ operation: stringValue(input.operation) ?? void 0,
265
+ thread_id: stringValue(input.thread_id) ?? void 0,
266
+ turn_id: stringValue(input.turn_id) ?? void 0,
267
+ arguments: input.arguments,
268
+ preview: input.preview,
269
+ method: stringValue(input.method) ?? void 0,
270
+ path: stringValue(input.path) ?? void 0
271
+ };
343
272
  }
344
- function reportError(err) {
345
- const isApi = err instanceof ApiError;
346
- const message = err?.message || String(err);
347
- if (jsonMode) {
348
- printJson({
349
- error: {
350
- status: isApi ? err.status : null,
351
- code: isApi ? err.code : "CLI_ERROR",
352
- message
273
+ async function streamResult(response, fallbackThreadId, onEvent) {
274
+ let threadId = fallbackThreadId;
275
+ let turnId = null;
276
+ let status2 = null;
277
+ let assistantText = "";
278
+ let sawFinal = false;
279
+ const actions = [];
280
+ await consumeAgentEventStream(response, (event) => {
281
+ const payload = event.payload;
282
+ if (event.type === "turn_started") {
283
+ threadId = stringValue(payload.thread_id) ?? stringValue(payload.threadId) ?? threadId;
284
+ turnId = stringValue(payload.turn_id) ?? stringValue(payload.turnId) ?? turnId;
285
+ } else if (event.type === "text_delta") {
286
+ assistantText += stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content) ?? "";
287
+ } else if (event.type === "approval_required") {
288
+ const action = normalizeAction(payload.action ?? payload.approval ?? payload);
289
+ if (action && !actions.some((candidate) => candidate.id === action.id)) {
290
+ actions.push(action);
353
291
  }
354
- });
355
- } else if (isApi) {
356
- printError(`[${err.status}] ${message}`);
357
- } else {
358
- printError(message);
292
+ } else if (event.type === "final") {
293
+ sawFinal = true;
294
+ status2 = stringValue(payload.status) ?? status2;
295
+ threadId = stringValue(payload.thread_id) ?? stringValue(payload.threadId) ?? threadId;
296
+ turnId = stringValue(payload.turn_id) ?? stringValue(payload.turnId) ?? turnId;
297
+ }
298
+ onEvent(event);
299
+ });
300
+ if (!sawFinal) {
301
+ throw new Error("The agent response ended before its final status was confirmed.");
359
302
  }
303
+ return { threadId, turnId, status: status2, response: assistantText, actions };
360
304
  }
361
- function formatDate(iso) {
362
- if (!iso) return "\u2014";
363
- return new Date(iso).toLocaleString();
364
- }
365
- function formatStatus(status2) {
366
- const map = {
367
- completed: chalk.green,
368
- running: chalk.blue,
369
- training: chalk.blue,
370
- evaluating: chalk.blue,
371
- preparing: chalk.yellow,
372
- pending: chalk.yellow,
373
- uploading: chalk.yellow,
374
- failed: chalk.red,
375
- cancelled: chalk.dim,
376
- validated: chalk.green,
377
- invalid: chalk.red
305
+ function createAgentClient(opts) {
306
+ return {
307
+ async createThread() {
308
+ const response = await post("/agent/threads", {}, opts);
309
+ return dataEnvelope(response);
310
+ },
311
+ async listThreads() {
312
+ const response = await get("/agent/threads", void 0, opts);
313
+ return dataEnvelope(response);
314
+ },
315
+ async getThread(id) {
316
+ const response = await get(`/agent/threads/${encodeURIComponent(id)}`, void 0, opts);
317
+ const detail = dataEnvelope(response);
318
+ return {
319
+ thread: detail.thread,
320
+ actions: (detail.actions ?? []).map((action) => normalizeAction(action)).filter((action) => action !== null)
321
+ };
322
+ },
323
+ async runTurn(threadId, prompt, onEvent, signal) {
324
+ const streamOpts = { ...opts, signal };
325
+ const response = await postStream(
326
+ `/agent/threads/${encodeURIComponent(threadId)}/turns`,
327
+ { prompt },
328
+ streamOpts
329
+ );
330
+ return await streamResult(response, threadId, onEvent);
331
+ },
332
+ async approveAction(actionId, onEvent, signal) {
333
+ const streamOpts = { ...opts, signal };
334
+ const response = await postStream(
335
+ `/agent/actions/${encodeURIComponent(actionId)}/approve`,
336
+ {},
337
+ streamOpts
338
+ );
339
+ return await streamResult(response, "", onEvent);
340
+ },
341
+ async rejectAction(actionId) {
342
+ await post(`/agent/actions/${encodeURIComponent(actionId)}/reject`, {}, opts);
343
+ }
378
344
  };
379
- const colorFn = map[status2] || chalk.white;
380
- return colorFn(status2);
381
- }
382
- function truncate(str, max) {
383
- if (str.length <= max) return str;
384
- return str.slice(0, max - 1) + "\u2026";
385
- }
386
- function shortId(id) {
387
- return id.slice(0, 8);
388
345
  }
389
346
 
390
- // src/local-output.ts
391
- function isRecord(value) {
392
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
347
+ // src/agent.ts
348
+ import chalk2 from "chalk";
349
+
350
+ // src/shell.ts
351
+ import chalk from "chalk";
352
+ import { stat as stat2 } from "fs/promises";
353
+ import { homedir as homedir3 } from "os";
354
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
355
+ import { createInterface } from "readline";
356
+
357
+ // src/command-catalog.ts
358
+ var CLOUD = ["cloud"];
359
+ var LOCAL = ["local"];
360
+ var BOTH = ["cloud", "local"];
361
+ var COMMAND_CATALOG = [
362
+ { path: "init", description: "Create a behaviour-spec project.", group: "Workflow", modes: BOTH },
363
+ { path: "eval", description: "Validate a managed-service behaviour spec.", group: "Workflow", modes: CLOUD },
364
+ { path: "push", description: "Create or update the cloud behaviour spec.", group: "Workflow", modes: CLOUD },
365
+ { path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
366
+ { path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
367
+ { path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
368
+ { path: "runs list", description: "List runs.", group: "Inspect", modes: BOTH },
369
+ { path: "runs get", description: "Show a run.", group: "Inspect", modes: BOTH },
370
+ { path: "runs report", description: "Show a run report.", group: "Inspect", modes: BOTH },
371
+ { path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
372
+ { path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
373
+ { path: "runs estimate", description: "Estimate managed run cost and duration.", group: "Workflow", modes: CLOUD },
374
+ { path: "runs start", description: "Start a managed fine-tuning run.", group: "Workflow", modes: CLOUD },
375
+ { path: "runs watch", description: "Watch a managed run.", group: "Inspect", modes: CLOUD },
376
+ { path: "runs diagnose", description: "Show managed run diagnostics.", group: "Inspect", modes: CLOUD },
377
+ { path: "runs cancel", description: "Cancel a managed run.", group: "Workflow", modes: CLOUD },
378
+ { path: "models list", description: "List models.", group: "Inspect", modes: BOTH },
379
+ { path: "models get", description: "Show a model.", group: "Inspect", modes: BOTH },
380
+ { path: "models base", description: "List managed-service base models.", group: "Inspect", modes: CLOUD },
381
+ { path: "models download", description: "Download a managed model artifact.", group: "Serving", modes: CLOUD },
382
+ { path: "models export", description: "Export a managed model to GGUF or Ollama.", group: "Serving", modes: CLOUD },
383
+ { path: "models setup-runtime", description: "Install the managed model-serving runtime.", group: "Serving", modes: CLOUD },
384
+ { path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: BOTH },
385
+ { path: "models delete", description: "Delete a managed model.", group: "Workflow", modes: CLOUD },
386
+ { path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
387
+ { path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
388
+ { path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
389
+ { path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
390
+ { path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
391
+ { path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
392
+ { path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
393
+ { path: "specs list", description: "List cloud behaviour specs.", group: "Inspect", modes: CLOUD },
394
+ { path: "specs get", description: "Show a cloud behaviour spec.", group: "Inspect", modes: CLOUD },
395
+ { path: "specs create", description: "Create a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
396
+ { path: "specs update", description: "Update a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
397
+ { path: "specs delete", description: "Delete a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
398
+ { path: "datasets list", description: "List cloud datasets.", group: "Data", modes: CLOUD },
399
+ { path: "datasets get", description: "Show a cloud dataset.", group: "Data", modes: CLOUD },
400
+ { path: "datasets upload", description: "Upload a cloud dataset.", group: "Data", modes: CLOUD },
401
+ { path: "datasets delete", description: "Delete a cloud dataset.", group: "Data", modes: CLOUD },
402
+ { path: "label upload", description: "Start a cloud teacher-labeling job.", group: "Data", modes: CLOUD },
403
+ { path: "label watch", description: "Watch a cloud labeling job.", group: "Data", modes: CLOUD },
404
+ { path: "label list", description: "List cloud labeling jobs.", group: "Data", modes: CLOUD },
405
+ { path: "label status", description: "Show cloud labeling-job status.", group: "Data", modes: CLOUD },
406
+ { path: "label rows", description: "Review labeled rows.", group: "Data", modes: CLOUD },
407
+ { path: "label accept", description: "Accept labeled rows.", group: "Data", modes: CLOUD },
408
+ { path: "label reject", description: "Reject labeled rows.", group: "Data", modes: CLOUD },
409
+ { path: "label edit", description: "Edit a labeled row.", group: "Data", modes: CLOUD },
410
+ { path: "label promote", description: "Promote reviewed rows to a dataset.", group: "Data", modes: CLOUD },
411
+ { path: "label cancel", description: "Cancel a labeling job.", group: "Data", modes: CLOUD },
412
+ { path: "auth login", description: "Store a cloud API key.", group: "Account", modes: CLOUD },
413
+ { path: "auth logout", description: "Remove stored cloud credentials.", group: "Account", modes: CLOUD },
414
+ { path: "auth status", description: "Show cloud authentication status.", group: "Account", modes: CLOUD },
415
+ { path: "balance", description: "Show cloud credit balance.", group: "Account", modes: CLOUD },
416
+ { path: "topup", description: "Open a cloud credit checkout.", group: "Account", modes: CLOUD },
417
+ { path: "info", description: "Show TT Local package information.", group: "Inspect", modes: LOCAL }
418
+ ];
419
+ var SLASH_COMMANDS = [
420
+ { path: "/help", description: "Show commands; add a word to filter." },
421
+ { path: "/status", description: "Show lightweight workflow status." },
422
+ { path: "/context", description: "Show the current project and backend context." },
423
+ { path: "/mode cloud", description: "Use the managed cloud workflow." },
424
+ { path: "/mode local", description: "Use the local GPU workflow." },
425
+ { path: "/model", description: "Show the active model; /model <id> activates a local model." },
426
+ { path: "/cd", description: "Change the shell's working directory." },
427
+ { path: "/clear", description: "Clear the terminal." },
428
+ { path: "/exit", description: "Exit the TT shell." }
429
+ ];
430
+ function catalogForMode(mode) {
431
+ return COMMAND_CATALOG.filter((command) => command.modes.includes(mode));
393
432
  }
394
- function text(value) {
395
- if (typeof value === "string") return value;
396
- if (typeof value === "number" || typeof value === "boolean") {
397
- return String(value);
433
+ function commandPathsForMode(mode) {
434
+ return [...new Set(catalogForMode(mode).map((command) => command.path))].sort((left, right) => left.localeCompare(right));
435
+ }
436
+ function groupedCatalog(mode, query) {
437
+ const normalizedQuery = query?.trim().toLowerCase();
438
+ const matches = catalogForMode(mode).filter((command) => {
439
+ if (!normalizedQuery) return true;
440
+ return command.path.toLowerCase().includes(normalizedQuery) || command.description.toLowerCase().includes(normalizedQuery) || command.group.toLowerCase().includes(normalizedQuery);
441
+ });
442
+ const groups = /* @__PURE__ */ new Map();
443
+ for (const command of matches) {
444
+ const group = groups.get(command.group) ?? [];
445
+ group.push(command);
446
+ groups.set(command.group, group);
398
447
  }
399
- return void 0;
448
+ return groups;
400
449
  }
401
- function number(value) {
402
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
450
+ function completionTarget(line, activeMode) {
451
+ const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
452
+ const content = line.slice(leadingWhitespace.length);
453
+ const override = content.match(/^(cloud|local)(?:\s+|$)/);
454
+ if (!override) {
455
+ return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
456
+ }
457
+ const mode = override[1];
458
+ const prefix = `${leadingWhitespace}${mode} `;
459
+ return {
460
+ prefix,
461
+ mode,
462
+ fragment: content.slice(override[0].length)
463
+ };
403
464
  }
404
- function record(value) {
405
- return isRecord(value) ? value : void 0;
465
+ function createCommandCompleter(getMode) {
466
+ return (line) => {
467
+ const trimmed = line.trimStart();
468
+ if (trimmed.startsWith("/")) {
469
+ const candidates = [
470
+ ...SLASH_COMMANDS.map((command) => command.path),
471
+ "/new",
472
+ "/threads",
473
+ "/resume ",
474
+ "/approve ",
475
+ "/reject "
476
+ ];
477
+ const matches2 = candidates.filter((candidate) => candidate.startsWith(trimmed));
478
+ return [matches2.length > 0 ? matches2 : candidates, line];
479
+ }
480
+ if (trimmed.startsWith("?")) {
481
+ return [["?"], line];
482
+ }
483
+ const { prefix, mode, fragment } = completionTarget(line, getMode());
484
+ const paths = commandPathsForMode(mode);
485
+ const targetCandidates = paths.map((path) => `${prefix}${path}`);
486
+ if (!prefix.trim()) targetCandidates.unshift("cloud ", "local ");
487
+ const normalizedLine = line.trimStart().toLowerCase();
488
+ const normalizedFragment = fragment.toLowerCase();
489
+ const matches = targetCandidates.filter((candidate) => {
490
+ if (prefix.trim()) return candidate.toLowerCase().startsWith(`${prefix.toLowerCase()}${normalizedFragment}`);
491
+ return candidate.toLowerCase().startsWith(normalizedLine);
492
+ });
493
+ return [matches.length > 0 ? matches : targetCandidates, line];
494
+ };
406
495
  }
407
- function rows(value) {
408
- return Array.isArray(value) ? value.filter(isRecord) : [];
496
+
497
+ // src/shell-context.ts
498
+ import { readFile, readdir, stat } from "fs/promises";
499
+ import { homedir as homedir2 } from "os";
500
+ import { basename as basename2, dirname, isAbsolute, join as join2, resolve } from "path";
501
+ async function readJsonObject(path) {
502
+ try {
503
+ const parsed = JSON.parse(await readFile(path, "utf8"));
504
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
505
+ return { found: true, invalid: true };
506
+ }
507
+ return {
508
+ found: true,
509
+ value: parsed,
510
+ invalid: false
511
+ };
512
+ } catch (error) {
513
+ if (error.code === "ENOENT") {
514
+ return { found: false, invalid: false };
515
+ }
516
+ return { found: true, invalid: true };
517
+ }
409
518
  }
410
- function displayId(value) {
411
- const id = text(value);
412
- return id ? shortId(id) : "\u2014";
519
+ function stringField(value, key) {
520
+ const field = value?.[key];
521
+ return typeof field === "string" && field.trim() ? field : void 0;
413
522
  }
414
- function displayNumber(value, digits = 4) {
415
- const parsed = number(value);
416
- if (parsed === void 0) return void 0;
417
- return parsed.toFixed(digits).replace(/\.?0+$/, "");
523
+ function expandPath(value, baseDirectory, homeDirectory) {
524
+ if (value === "~") return homeDirectory;
525
+ if (value.startsWith("~/")) return resolve(homeDirectory, value.slice(2));
526
+ return isAbsolute(value) ? resolve(value) : resolve(baseDirectory, value);
418
527
  }
419
- function displayBytes(value) {
420
- const bytes = number(value);
421
- if (bytes === void 0 || bytes < 0) return void 0;
422
- const units = ["B", "KiB", "MiB", "GiB", "TiB"];
423
- let amount = bytes;
424
- let unit = 0;
425
- while (amount >= 1024 && unit < units.length - 1) {
426
- amount /= 1024;
427
- unit += 1;
428
- }
429
- const precision = amount >= 10 || unit === 0 ? 0 : 1;
430
- return `${amount.toFixed(precision)} ${units[unit]}`;
528
+ function environmentHome(env) {
529
+ return env.HOME ? resolve(env.HOME) : homedir2();
431
530
  }
432
- function displayCommand(value) {
433
- if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
434
- return void 0;
435
- }
436
- return value.map(
437
- (part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
438
- ).join(" ");
531
+ function cloudConfigPath(env, homeDirectory) {
532
+ const configHome = env.XDG_CONFIG_HOME ? resolve(env.XDG_CONFIG_HOME) : join2(homeDirectory, ".config");
533
+ return join2(configHome, "tuned-tensor", "config.json");
439
534
  }
440
- function status(value) {
441
- const parsed = text(value) ?? "unknown";
442
- return formatStatus(parsed);
535
+ function redactApiKey(key) {
536
+ if (!key) return void 0;
537
+ if (key.length <= 4) return "\u2026";
538
+ const prefixLength = key.length > 8 ? 8 : Math.max(1, Math.floor(key.length / 2));
539
+ return `${key.slice(0, prefixLength)}\u2026`;
443
540
  }
444
- function gateFrom(value) {
445
- return record(record(value)?.general_regression);
541
+ function targetFromEnvironment(env) {
542
+ const value = env.TT_TARGET?.trim().toLowerCase();
543
+ return value === "cloud" || value === "local" ? value : void 0;
446
544
  }
447
- function renderGate(value) {
448
- const gate = gateFrom(value);
449
- if (!gate) {
450
- printWarning(
451
- "General regression was not run; this model cannot be activated yet."
452
- );
453
- return;
454
- }
455
- if (gate.passed === true) {
456
- printSuccess("General regression gate passed");
457
- return;
545
+ async function adjacentFile(path) {
546
+ try {
547
+ return (await stat(path)).isFile();
548
+ } catch {
549
+ return false;
458
550
  }
459
- printWarning(
460
- "The local run completed, but the general regression gate failed."
461
- );
462
- const failures = Array.isArray(gate.failures) ? gate.failures.filter(
463
- (failure) => typeof failure === "string"
464
- ) : [];
465
- for (const failure of failures) printWarning(failure);
466
551
  }
467
- function renderDoctor(value) {
468
- const checks = rows(value.checks);
469
- printTable(
470
- ["Check", "Status", "Details"],
471
- checks.map((check) => [
472
- text(check.name) ?? "unknown",
473
- check.ok === true ? formatStatus("completed") : formatStatus("failed"),
474
- truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
475
- ])
476
- );
477
- if (value.ok === true) {
478
- printSuccess("Local environment is ready");
479
- } else {
480
- printError("Local environment needs attention");
481
- }
482
- if (value.config_path) {
483
- printDetail([["Config", text(value.config_path)]]);
484
- }
552
+ async function readActiveModelId(storeRoot) {
553
+ const result = await readJsonObject(join2(storeRoot, "active-model.json"));
554
+ if (!result.found || result.invalid) return void 0;
555
+ const modelId = result.value?.model_id;
556
+ if (modelId === null) return "base";
557
+ return typeof modelId === "string" && modelId ? modelId : void 0;
485
558
  }
486
- function renderInit(value) {
487
- printSuccess("Created local Tuned Tensor project");
488
- printDetail([
489
- ["Path", text(value.path)],
490
- ["Name", text(value.name)],
491
- ["ID", text(value.id)],
492
- ["Base model", text(value.base_model)],
493
- ["Config", text(value.config_path)]
494
- ]);
559
+ async function readLatestRun(storeRoot) {
560
+ const runsRoot = join2(storeRoot, "runs");
561
+ let names;
562
+ try {
563
+ names = (await readdir(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
564
+ } catch {
565
+ return void 0;
566
+ }
567
+ const states = await Promise.all(names.map(async (name) => {
568
+ const result = await readJsonObject(join2(runsRoot, name, "state.json"));
569
+ if (!result.found || result.invalid || !result.value) return void 0;
570
+ const id = stringField(result.value, "id") ?? name;
571
+ const state = {
572
+ id,
573
+ status: stringField(result.value, "status"),
574
+ specName: stringField(result.value, "spec_name"),
575
+ updatedAt: stringField(result.value, "updated_at")
576
+ };
577
+ return state;
578
+ }));
579
+ return states.filter((state) => Boolean(state)).sort((left, right) => {
580
+ const leftUpdated = left.updatedAt ?? "";
581
+ const rightUpdated = right.updatedAt ?? "";
582
+ if (leftUpdated !== rightUpdated) {
583
+ return rightUpdated.localeCompare(leftUpdated);
584
+ }
585
+ return right.id.localeCompare(left.id);
586
+ })[0];
495
587
  }
496
- function renderValidate(value) {
497
- printSuccess("Local behavior spec is valid");
498
- printDetail([
499
- ["Input", text(value.input_path)],
500
- ["Config", text(value.config_path)],
501
- ["Spec", text(value.behavior_spec_id)],
502
- ["Base model", text(value.base_model)],
503
- ["Dataset", text(value.dataset_format)],
504
- ["Artifacts", text(value.artifact_root)],
505
- ["Store", text(value.store_root)],
506
- ["Dry run", text(value.dry_run)]
588
+ async function discoverShellContext(options = {}) {
589
+ const env = options.env ?? process.env;
590
+ const cwd = resolve(options.cwd ?? process.cwd());
591
+ const homeDirectory = environmentHome(env);
592
+ const warnings = [];
593
+ const specPath = join2(cwd, "tunedtensor.json");
594
+ const specJson = await readJsonObject(specPath);
595
+ let spec;
596
+ if (specJson.found) {
597
+ if (specJson.invalid || !specJson.value) {
598
+ spec = { path: specPath, parseError: true };
599
+ warnings.push("tunedtensor.json could not be parsed.");
600
+ } else {
601
+ const examples = specJson.value.examples;
602
+ spec = {
603
+ path: specPath,
604
+ id: stringField(specJson.value, "id"),
605
+ name: stringField(specJson.value, "name"),
606
+ baseModel: stringField(specJson.value, "base_model"),
607
+ exampleCount: Array.isArray(examples) ? examples.length : void 0,
608
+ parseError: false
609
+ };
610
+ }
611
+ }
612
+ const localConfigPath = join2(cwd, "local-runner.json");
613
+ const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
614
+ const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
615
+ if (localConfigJson.invalid) {
616
+ warnings.push("local-runner.json could not be parsed.");
617
+ }
618
+ const localConfigDirectory = hasAdjacentLocalConfig ? dirname(localConfigPath) : cwd;
619
+ const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
620
+ const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
621
+ const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve(cwd, ".tt-local", "artifacts");
622
+ const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join2(homeDirectory, ".tuned-tensor-local");
623
+ const cloudPath = cloudConfigPath(env, homeDirectory);
624
+ const cloudConfigJson = await readJsonObject(cloudPath);
625
+ if (cloudConfigJson.invalid) {
626
+ warnings.push("The Tuned Tensor cloud config could not be parsed.");
627
+ }
628
+ const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
629
+ const storedApiKey = stringField(cloudConfigJson.value, "api_key");
630
+ const apiKey = environmentApiKey || storedApiKey;
631
+ const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
632
+ const environmentTarget = targetFromEnvironment(env);
633
+ if (env.TT_TARGET && !environmentTarget) {
634
+ warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
635
+ }
636
+ const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
637
+ const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
638
+ const [activeModelId, latestRun] = await Promise.all([
639
+ readActiveModelId(storeRoot),
640
+ readLatestRun(storeRoot)
507
641
  ]);
642
+ return {
643
+ cwd,
644
+ projectName: basename2(cwd) || cwd,
645
+ inferredTarget,
646
+ targetSource,
647
+ spec,
648
+ cloud: {
649
+ authenticated: Boolean(apiKey),
650
+ keyPrefix: redactApiKey(apiKey),
651
+ baseUrl,
652
+ configPath: cloudPath,
653
+ configFound: cloudConfigJson.found
654
+ },
655
+ local: {
656
+ configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
657
+ artifactRoot,
658
+ storeRoot,
659
+ activeModelId,
660
+ latestRun
661
+ },
662
+ warnings
663
+ };
508
664
  }
509
- function renderRun(value) {
510
- const runStatus = text(value.status) ?? "unknown";
511
- if (runStatus === "completed") {
512
- printSuccess("Local run completed");
513
- } else {
514
- printWarning(`Local run finished with status ${runStatus}`);
665
+ function valueOrDash(value) {
666
+ return value === void 0 || value === "" ? "\u2014" : String(value);
667
+ }
668
+ function targetSourceLabel(source) {
669
+ switch (source) {
670
+ case "environment":
671
+ return "TT_TARGET";
672
+ case "adjacent-config":
673
+ return "adjacent local-runner.json";
674
+ default:
675
+ return "default";
515
676
  }
516
- const comparison = record(value.comparison);
517
- printDetail([
518
- ["Run", text(value.run_id)],
519
- ["Status", status(runStatus)],
520
- ["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
521
- ["Report", text(value.report_path)],
522
- ["Artifacts", text(value.artifact_dir)],
523
- ["Score delta", displayNumber(comparison?.avg_score_delta)],
524
- ["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
525
- ]);
526
- renderGate(value);
527
677
  }
528
- function renderRunsList(value) {
529
- const runRows = rows(value);
530
- if (runRows.length === 0) {
531
- printWarning("No local runs found.");
532
- return;
678
+ function formatShellContext(context, selectedTarget, targetSource) {
679
+ const specLabel = context.spec ? context.spec.parseError ? `${context.spec.path} (invalid JSON)` : context.spec.path : "not found";
680
+ const sourceLabel = targetSource === "session" ? "session" : targetSource === "command-option" ? "--target" : targetSourceLabel(targetSource);
681
+ const lines = [
682
+ `Target ${selectedTarget} (${sourceLabel})`,
683
+ `Project ${context.projectName}`,
684
+ `Directory ${context.cwd}`,
685
+ `Spec ${specLabel}`,
686
+ `Spec ID ${valueOrDash(context.spec?.id)}`,
687
+ `Base model ${valueOrDash(context.spec?.baseModel)}`,
688
+ `Examples ${valueOrDash(context.spec?.exampleCount)}`,
689
+ `Cloud endpoint ${context.cloud.baseUrl}`,
690
+ `Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
691
+ `Local config ${context.local.configPath ?? "not found"}`,
692
+ `Artifact root ${context.local.artifactRoot}`,
693
+ `Store root ${context.local.storeRoot}`
694
+ ];
695
+ for (const warning of context.warnings) lines.push(`Warning ${warning}`);
696
+ return lines;
697
+ }
698
+ function formatShellStatus(context, selectedTarget) {
699
+ const lines = [
700
+ `Workflow ${selectedTarget}`,
701
+ `Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
702
+ ];
703
+ if (selectedTarget === "cloud") {
704
+ lines.push(
705
+ `Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
706
+ `Endpoint ${context.cloud.baseUrl}`,
707
+ "Remote status not queried"
708
+ );
709
+ } else {
710
+ lines.push(
711
+ `Local config ${context.local.configPath ?? "not found"}`,
712
+ `Active model ${context.local.activeModelId ?? "base"}`,
713
+ `Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
714
+ "Host checks not run (use doctor)"
715
+ );
533
716
  }
534
- printTable(
535
- ["Run", "Spec", "Status", "Stage", "Model", "Updated"],
536
- runRows.map((run) => [
537
- displayId(run.id),
538
- truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
539
- status(run.status),
540
- text(run.current_stage) ?? "\u2014",
541
- displayId(run.model_id),
542
- formatDate(text(run.updated_at))
543
- ])
544
- );
717
+ return lines;
545
718
  }
546
- function renderRunEvents(value) {
547
- const eventRows = rows(value);
548
- if (eventRows.length === 0) {
549
- printWarning("No local run events found.");
550
- return;
719
+
720
+ // src/shell.ts
721
+ var AGENT_SLASH_COMMANDS = /* @__PURE__ */ new Set([
722
+ "new",
723
+ "threads",
724
+ "resume",
725
+ "approve",
726
+ "reject"
727
+ ]);
728
+ var ShellParseError = class extends Error {
729
+ constructor(message) {
730
+ super(message);
731
+ this.name = "ShellParseError";
551
732
  }
552
- printTable(
553
- ["Time", "Stage", "Status", "Message"],
554
- eventRows.map((event) => [
555
- formatDate(text(event.occurred_at)),
556
- text(event.stage) ?? "\u2014",
557
- status(event.status),
558
- truncate(text(event.message) ?? "\u2014", 80)
559
- ])
733
+ };
734
+ function operatorError(operator) {
735
+ return new ShellParseError(
736
+ `Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
560
737
  );
561
738
  }
562
- function renderRunRecord(value) {
563
- printDetail([
564
- ["Run", text(value.id) ?? text(value.run_id)],
565
- ["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
566
- ["Status", status(value.status)],
567
- ["Stage", text(value.current_stage)],
568
- ["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
569
- ["Base model", text(value.base_model)],
570
- ["Report", text(value.report_path)],
571
- ["Artifacts", text(value.artifact_dir)],
572
- ["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
573
- ]);
574
- }
575
- function renderRunReport(value) {
576
- const baseline = record(value.baseline);
577
- const candidate = record(value.candidate);
578
- const comparison = record(value.comparison);
579
- const metadata = record(value.run_metadata);
580
- printDetail([
581
- ["Run", text(value.run_id)],
582
- ["Status", status(value.status)],
583
- ["Base model", text(value.base_model)],
584
- ["Tuned model", text(value.fine_tuned_model_id)],
585
- ["Training examples", text(metadata?.training_example_count)],
586
- ["Evaluation examples", text(metadata?.eval_examples_used)],
587
- ["Baseline score", displayNumber(baseline?.avg_score)],
588
- ["Candidate score", displayNumber(candidate?.avg_score)],
589
- ["Score delta", displayNumber(comparison?.avg_score_delta)],
590
- ["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
591
- ["Regressions", text(comparison?.regressions)],
592
- ["Improvements", text(comparison?.improvements)]
593
- ]);
594
- renderGate(value);
595
- }
596
- function renderRunComparison(value) {
597
- const runA = record(value.run_a);
598
- const runB = record(value.run_b);
599
- const shared = record(value.shared);
600
- const sharedA = record(shared?.run_a);
601
- const sharedB = record(shared?.run_b);
602
- printTable(
603
- ["Run", "Candidate score", "Token F1", "Examples"],
604
- [
605
- [
606
- displayId(runA?.run_id),
607
- displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
608
- displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
609
- text(shared?.examples) ?? "0"
610
- ],
611
- [
612
- displayId(runB?.run_id),
613
- displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
614
- displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
615
- text(shared?.examples) ?? "0"
616
- ]
617
- ]
618
- );
619
- const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
620
- for (const note of notes) printWarning(note);
621
- }
622
- function renderModelsList(value) {
623
- const modelRows = rows(value);
624
- if (modelRows.length === 0) {
625
- printWarning("No local models found.");
626
- return;
627
- }
628
- printTable(
629
- ["Model", "Base model", "Run", "Provider", "Created"],
630
- modelRows.map((model) => [
631
- displayId(model.id),
632
- truncate(text(model.base_model) ?? "\u2014", 32),
633
- displayId(model.run_id),
634
- text(model.provider) ?? "\u2014",
635
- formatDate(text(model.created_at))
636
- ])
637
- );
638
- }
639
- function renderModelRecord(value) {
640
- const model = record(value.model) ?? value;
641
- const artifact = record(value.artifact);
642
- const integrity = record(value.integrity);
643
- printDetail([
644
- ["Model", text(model.id) ?? text(value.active)],
645
- ["Base model", text(model.base_model)],
646
- ["Run", text(model.run_id)],
647
- ["Provider", text(model.provider)],
648
- ["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
649
- ["Manifest", text(value.manifest_path)],
650
- ["Verified files", text(integrity?.checked)],
651
- ["Created", formatDate(text(model.created_at))]
652
- ]);
653
- }
654
- function renderModelPrefetch(value) {
655
- printSuccess(
656
- value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
657
- );
658
- printDetail([
659
- ["Base model", text(value.base_model)],
660
- ["Revision", text(value.snapshot_revision)],
661
- ["Snapshot", text(value.snapshot_path)],
662
- ["Files", text(value.file_count)],
663
- ["Size", displayBytes(value.size_bytes)],
664
- ["Cache", text(value.model_cache) ?? text(value.hf_home)]
665
- ]);
666
- }
667
- function renderActiveModel(value) {
668
- const active = text(value.active) ?? "base";
669
- const pointer = record(value.pointer);
670
- if (pointer?.action === "activate") {
671
- printSuccess(`Activated local model ${active}`);
672
- } else if (pointer?.action === "rollback") {
673
- printSuccess(`Rolled back to ${active}`);
674
- }
675
- printDetail([
676
- ["Active", active],
677
- ["Run", text(pointer?.run_id)],
678
- ["Previous", text(pointer?.previous_model_id) ?? "base"],
679
- ["Changed", formatDate(text(pointer?.activated_at))],
680
- ["Manifest", text(value.manifest_path)]
681
- ]);
682
- }
683
- function renderServePlan(value) {
684
- printSuccess("Local serving plan is valid");
685
- printDetail([
686
- ["Model", text(value.model_id) ?? text(value.base_model)],
687
- ["URL", text(value.url)],
688
- ["Artifact", text(value.artifact_path)],
689
- ["Manifest", text(value.manifest_path)],
690
- ["Command", displayCommand(value.command)]
691
- ]);
692
- }
693
- function renderKnownJson(args, value) {
694
- const command = args[0];
695
- const subcommand = args[1];
696
- if (command === "doctor" && isRecord(value)) {
697
- renderDoctor(value);
698
- return true;
699
- }
700
- if (command === "init" && isRecord(value)) {
701
- renderInit(value);
702
- return true;
739
+ function tokenizeShellInput(input) {
740
+ if (input.includes("\0")) {
741
+ throw new ShellParseError("Command input must not contain NUL bytes.");
703
742
  }
704
- if (command === "validate" && isRecord(value)) {
705
- renderValidate(value);
706
- return true;
743
+ if (/[\r\n]/.test(input)) {
744
+ throw new ShellParseError("Enter one command at a time.");
707
745
  }
708
- if (command === "run" && isRecord(value)) {
709
- renderRun(value);
710
- return true;
746
+ if (input.trimStart().startsWith("!")) {
747
+ throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
711
748
  }
712
- if (command === "runs") {
713
- if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
714
- renderRunsList(value);
715
- return true;
716
- }
717
- if (subcommand === "events" && Array.isArray(value)) {
718
- renderRunEvents(value);
719
- return true;
720
- }
721
- if (subcommand === "get" && isRecord(value)) {
722
- renderRunRecord(value);
723
- return true;
749
+ const tokens = [];
750
+ let token = "";
751
+ let tokenStarted = false;
752
+ let quote = "none";
753
+ const finishToken = () => {
754
+ if (!tokenStarted) return;
755
+ tokens.push(token);
756
+ token = "";
757
+ tokenStarted = false;
758
+ };
759
+ for (let index = 0; index < input.length; index += 1) {
760
+ const character = input[index];
761
+ if (quote === "single") {
762
+ if (character === "'") {
763
+ quote = "none";
764
+ } else {
765
+ token += character;
766
+ }
767
+ continue;
724
768
  }
725
- if (subcommand === "report" && isRecord(value)) {
726
- renderRunReport(value);
727
- return true;
769
+ if (quote === "double") {
770
+ if (character === '"') {
771
+ quote = "none";
772
+ continue;
773
+ }
774
+ if (character === "\\") {
775
+ const escaped = input[index + 1];
776
+ if (escaped === void 0) {
777
+ throw new ShellParseError("Command ends with an unfinished escape.");
778
+ }
779
+ token += escaped;
780
+ tokenStarted = true;
781
+ index += 1;
782
+ continue;
783
+ }
784
+ if (character === "`") throw operatorError("`\u2026`");
785
+ if (character === "$" && input[index + 1] === "(") {
786
+ throw operatorError("$(\u2026)");
787
+ }
788
+ token += character;
789
+ tokenStarted = true;
790
+ continue;
728
791
  }
729
- if (subcommand === "compare" && isRecord(value)) {
730
- renderRunComparison(value);
731
- return true;
792
+ if (/\s/.test(character)) {
793
+ finishToken();
794
+ continue;
732
795
  }
733
- }
734
- if (command === "models") {
735
- if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
736
- renderModelsList(value);
737
- return true;
796
+ if (character === "'") {
797
+ quote = "single";
798
+ tokenStarted = true;
799
+ continue;
738
800
  }
739
- if ((subcommand === "get" || subcommand === "verify") && isRecord(value)) {
740
- if (subcommand === "verify") printSuccess("Local model artifact is valid");
741
- renderModelRecord(value);
742
- return true;
801
+ if (character === '"') {
802
+ quote = "double";
803
+ tokenStarted = true;
804
+ continue;
743
805
  }
744
- if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord(value)) {
745
- renderModelPrefetch(value);
746
- return true;
806
+ if (character === "\\") {
807
+ const escaped = input[index + 1];
808
+ if (escaped === void 0) {
809
+ throw new ShellParseError("Command ends with an unfinished escape.");
810
+ }
811
+ token += escaped;
812
+ tokenStarted = true;
813
+ index += 1;
814
+ continue;
747
815
  }
748
- if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord(value)) {
749
- renderActiveModel(value);
750
- return true;
816
+ if (character === "`") throw operatorError("`\u2026`");
817
+ if (character === "$" && input[index + 1] === "(") {
818
+ throw operatorError("$(\u2026)");
751
819
  }
752
- if (subcommand === "serve" && isRecord(value)) {
753
- renderServePlan(value);
754
- return true;
820
+ if (character === "|" || character === ">" || character === "<" || character === ";" || character === "&") {
821
+ const doubled = input[index + 1] === character && (character === "|" || character === "&");
822
+ throw operatorError(doubled ? character.repeat(2) : character);
755
823
  }
824
+ token += character;
825
+ tokenStarted = true;
756
826
  }
757
- if (command === "serve" && isRecord(value)) {
758
- renderServePlan(value);
759
- return true;
827
+ if (quote !== "none") {
828
+ throw new ShellParseError(
829
+ `Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
830
+ );
760
831
  }
761
- return false;
832
+ finishToken();
833
+ return tokens;
762
834
  }
763
- function lastNonEmptyLine(value) {
764
- return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
835
+ function routeShellCommand(input, activeMode) {
836
+ let args = tokenizeShellInput(input);
837
+ if (args.length === 0) return null;
838
+ if (args[0] === "tt") {
839
+ args = args.slice(1);
840
+ if (args.length === 0) {
841
+ throw new ShellParseError("The TT shell is already open; enter a command such as `runs list`.");
842
+ }
843
+ }
844
+ if (args[0]?.startsWith("/")) {
845
+ throw new ShellParseError("Slash commands are session commands and cannot be routed to a workflow.");
846
+ }
847
+ const override = args[0];
848
+ if (override === "cloud" || override === "local") {
849
+ if (args.length === 1) {
850
+ throw new ShellParseError(`Add a ${override} command, or use \`/mode ${override}\` to switch workflows.`);
851
+ }
852
+ return { target: override, args: args.slice(1) };
853
+ }
854
+ return { target: activeMode, args };
765
855
  }
766
- function adaptLocalCliText(value) {
767
- return value.replace(
768
- /(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
769
- "tt local"
770
- ).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
856
+ function isCatalogCommand(mode, args) {
857
+ if (args.length === 0) return false;
858
+ if (args[0] === "status") {
859
+ return args.length === 1 || args[1]?.startsWith("-") === true;
860
+ }
861
+ return COMMAND_CATALOG.some((command) => {
862
+ if (!command.modes.includes(mode)) return false;
863
+ const path = command.path.split(" ");
864
+ return path.every((token, index) => args[index] === token);
865
+ });
771
866
  }
772
- function localCliErrorEnvelope(payload) {
773
- const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
774
- return {
775
- error: {
776
- status: null,
777
- code: "LOCAL_CLI_ERROR",
778
- message: adaptLocalCliText(rawMessage),
779
- exit_code: payload.exitCode,
780
- signal: payload.signal
781
- }
782
- };
867
+ function hasCatalogIntent(input, activeMode) {
868
+ let args = input.trim().split(/\s+/);
869
+ if (args[0] === "tt") args = args.slice(1);
870
+ const override = args[0];
871
+ const mode = override === "cloud" || override === "local" ? override : activeMode;
872
+ if (override === "cloud" || override === "local") args = args.slice(1);
873
+ return isCatalogCommand(mode, args);
783
874
  }
784
- function localCliTextEnvelope(payload) {
785
- const output = adaptLocalCliText(payload.stdout).trim();
786
- if (payload.args[0] === "info") {
787
- const lines = output.split(/\r?\n/);
788
- const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
789
- const fields = Object.fromEntries(
790
- lines.slice(1).flatMap((line) => {
791
- const match = line.match(/^([^:]+):\s*(.*)$/);
792
- return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
793
- })
794
- );
795
- if (heading) {
796
- return {
797
- data: {
798
- name: heading[1],
799
- description: heading[2],
800
- ...fields
801
- }
802
- };
875
+ var SLASH_NAMES = /* @__PURE__ */ new Set([
876
+ "help",
877
+ "status",
878
+ "context",
879
+ "mode",
880
+ "model",
881
+ "clear",
882
+ "cd",
883
+ "exit"
884
+ ]);
885
+ var WORKFLOW_ROOT_COMMANDS = new Set(
886
+ COMMAND_CATALOG.map((command) => command.path.split(" ", 1)[0])
887
+ );
888
+ function editDistance(left, right) {
889
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
890
+ for (let i = 1; i <= left.length; i += 1) {
891
+ let diagonal = previous[0];
892
+ previous[0] = i;
893
+ for (let j = 1; j <= right.length; j += 1) {
894
+ const above = previous[j];
895
+ previous[j] = Math.min(
896
+ previous[j] + 1,
897
+ previous[j - 1] + 1,
898
+ diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
899
+ );
900
+ diagonal = above;
803
901
  }
804
902
  }
805
- return { data: { output } };
903
+ return previous[right.length];
806
904
  }
807
- function renderLocalOutput(payload, options = {}) {
808
- if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
809
- return;
810
- }
811
- const output = options.stdout ?? process.stdout;
812
- if (options.jsonMode) {
813
- if (payload.hasJson) {
814
- output.write(payload.stdout);
815
- return;
816
- }
817
- if (payload.exitCode !== 0 || payload.errorMessage) {
818
- output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
819
- `);
820
- return;
821
- }
822
- output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
823
- `);
824
- return;
825
- }
826
- if (payload.droppedSpecKeys.length > 0) {
827
- printWarning(
828
- `Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
905
+ function suggestSlashCommands(rawName) {
906
+ const names = [...SLASH_NAMES].sort();
907
+ const prefixMatches = names.filter((name) => name.startsWith(rawName));
908
+ if (prefixMatches.length > 0) return prefixMatches;
909
+ return names.filter((name) => editDistance(name, rawName) <= 2);
910
+ }
911
+ function unknownSlashCommandError(rawName) {
912
+ if (WORKFLOW_ROOT_COMMANDS.has(rawName)) {
913
+ return new ShellParseError(
914
+ `Unknown session command: /${rawName}. Workflow commands need no slash \u2014 try "${rawName} --help".`
829
915
  );
830
916
  }
831
- if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
832
- if (payload.hasJson) {
833
- printJson(payload.json);
834
- return;
917
+ const suggestions = suggestSlashCommands(rawName);
918
+ const hint = suggestions.length > 0 ? ` Did you mean ${suggestions.map((name) => `/${name}`).join(" or ")}?` : "";
919
+ return new ShellParseError(
920
+ `Unknown session command: /${rawName}.${hint} Use /help to see available commands.`
921
+ );
922
+ }
923
+ function parseSlashCommand(input) {
924
+ const trimmed = input.trim();
925
+ if (!trimmed) return null;
926
+ if (trimmed === "?") return { name: "help", args: [] };
927
+ if (trimmed.startsWith("? ")) {
928
+ return {
929
+ name: "help",
930
+ args: tokenizeShellInput(trimmed.slice(1))
931
+ };
835
932
  }
836
- if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
837
- if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
838
- printError(
839
- payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
840
- );
933
+ if (!trimmed.startsWith("/")) return null;
934
+ if (trimmed === "/") return { name: "palette", args: [] };
935
+ const tokens = tokenizeShellInput(trimmed);
936
+ const rawName = tokens[0].slice(1).toLowerCase();
937
+ if (!SLASH_NAMES.has(rawName)) {
938
+ throw unknownSlashCommandError(rawName);
841
939
  }
940
+ return {
941
+ name: rawName,
942
+ args: tokens.slice(1)
943
+ };
842
944
  }
843
-
844
- // src/local-runner.ts
845
- var localRequire = createRequire(import.meta.url);
846
- var DEFAULT_SPEC_NAME = "tunedtensor.json";
847
- var VALUE_OPTIONS = /* @__PURE__ */ new Set([
848
- "--config",
849
- "--name",
850
- "--model",
851
- "--output",
852
- "--profile",
853
- "--host",
854
- "--port",
855
- "--device",
856
- "--max-tokens",
857
- "--temperature",
858
- "--top-p",
859
- "--max-concurrent-requests",
860
- "--spec",
861
- "--api-key-env"
862
- ]);
863
- function isOptionWithInlineValue(value) {
864
- const equals = value.indexOf("=");
865
- return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
866
- }
867
- function positionalIndices(args, start) {
868
- const indices = [];
869
- let optionsEnded = false;
870
- for (let index = start; index < args.length; index += 1) {
871
- const value = args[index];
872
- if (!optionsEnded && value === "--") {
873
- optionsEnded = true;
874
- continue;
875
- }
876
- if (!optionsEnded && value.startsWith("-")) {
877
- if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
878
- continue;
879
- }
880
- indices.push(index);
881
- }
882
- return indices;
945
+ function errorMessage(error) {
946
+ return error instanceof Error ? error.message : String(error);
883
947
  }
884
- function optionValueArgument(args, name) {
885
- for (let index = 0; index < args.length; index += 1) {
886
- const value = args[index];
887
- if (value === name) {
888
- const path = args[index + 1];
889
- if (!path) return null;
890
- return {
891
- path,
892
- replace(projectedPath) {
893
- args[index + 1] = projectedPath;
894
- }
895
- };
896
- }
897
- if (value.startsWith(`${name}=`)) {
898
- const path = value.slice(name.length + 1);
899
- if (!path) return null;
900
- return {
901
- path,
902
- replace(projectedPath) {
903
- args[index] = `${name}=${projectedPath}`;
904
- }
905
- };
906
- }
948
+ var successMark = () => chalk.green("\u2713");
949
+ var errorMark = () => chalk.red("\u2717");
950
+ var accent = chalk.hex("#8B5CF6");
951
+ var ANSI_BOLD = "\x1B[1m";
952
+ var ANSI_BOLD_OFF = "\x1B[22m";
953
+ var ANSI_CLEAR_TO_END = "\x1B[K";
954
+ var ANSI_RESET = "\x1B[0m";
955
+ function inputColorCodes() {
956
+ if (chalk.level === 0) return null;
957
+ if (chalk.level === 1) {
958
+ return {
959
+ background: "\x1B[100m",
960
+ accent: "\x1B[95m",
961
+ text: "\x1B[97m"
962
+ };
907
963
  }
908
- return null;
909
- }
910
- function positionalSpecArgument(args, start, defaultPath) {
911
- const positional = positionalIndices(args, start)[0];
912
- if (positional !== void 0) {
964
+ if (chalk.level === 2) {
913
965
  return {
914
- path: args[positional],
915
- replace(projectedPath) {
916
- args[positional] = projectedPath;
917
- }
966
+ background: "\x1B[48;5;238m",
967
+ accent: "\x1B[38;5;183m",
968
+ text: "\x1B[38;5;255m"
918
969
  };
919
970
  }
920
971
  return {
921
- path: defaultPath,
922
- replace(projectedPath) {
923
- args.splice(start, 0, projectedPath);
924
- }
972
+ background: "\x1B[48;2;50;52;67m",
973
+ accent: "\x1B[38;2;196;181;253m",
974
+ text: "\x1B[38;2;245;243;255m"
925
975
  };
926
976
  }
927
- function selectedSpecArgument(args, cwd) {
928
- const command = args[0];
929
- if (["doctor", "validate", "run"].includes(command ?? "")) {
930
- return positionalSpecArgument(args, 1, join2(cwd, DEFAULT_SPEC_NAME));
931
- }
932
- if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
933
- return positionalSpecArgument(args, 2, join2(cwd, DEFAULT_SPEC_NAME));
934
- }
935
- const serves = command === "serve" || command === "models" && args[1] === "serve";
936
- if (!serves) return null;
937
- const explicit = optionValueArgument(args, "--spec");
938
- if (explicit) return explicit;
939
- return null;
977
+ var DETAIL_LABEL_WIDTH = 15;
978
+ function styleDetailLines(lines) {
979
+ return lines.map((line) => {
980
+ if (line.length <= DETAIL_LABEL_WIDTH) return line;
981
+ const label = line.slice(0, DETAIL_LABEL_WIDTH);
982
+ if (!label.trim()) return line;
983
+ const color = label.startsWith("Warning") ? chalk.yellow : accent.bold;
984
+ return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
985
+ });
940
986
  }
941
- async function pathIsFile(path) {
942
- return (await stat(path).catch(() => null))?.isFile() ?? false;
987
+ function detailLine(label, value) {
988
+ return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
943
989
  }
944
- function resolveFromCwd(path, cwd) {
945
- if (isAbsolute(path)) return path;
946
- return resolve(cwd, path);
990
+ function assertNoArgs(command, args) {
991
+ if (args.length > 0) {
992
+ throw new ShellParseError(`/${command} does not accept arguments.`);
993
+ }
947
994
  }
948
- async function projectSpecArguments(originalArgs, cwd) {
949
- const args = [...originalArgs];
950
- if (args.includes("--help") || args.includes("-h")) {
951
- return {
952
- args,
953
- droppedSpecKeys: [],
954
- pathReplacements: [],
955
- cleanup: async () => {
995
+ function expandDirectory(value, cwd, env) {
996
+ const home = env.HOME ? resolve2(env.HOME) : homedir3();
997
+ if (value === "~") return home;
998
+ if (value.startsWith("~/")) return resolve2(home, value.slice(2));
999
+ return isAbsolute2(value) ? resolve2(value) : resolve2(cwd, value);
1000
+ }
1001
+ function helpText(mode, query, palette = false) {
1002
+ const groups = groupedCatalog(mode, query);
1003
+ const lines = [];
1004
+ lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
1005
+ if (groups.size === 0) {
1006
+ lines.push(chalk.dim(" No matching commands."));
1007
+ } else {
1008
+ for (const [group, commands] of groups) {
1009
+ lines.push("");
1010
+ lines.push(chalk.bold(group));
1011
+ for (const command of commands) {
1012
+ lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
956
1013
  }
957
- };
1014
+ }
958
1015
  }
959
- const selected = selectedSpecArgument(args, cwd);
960
- if (!selected) {
961
- return {
962
- args,
963
- droppedSpecKeys: [],
964
- pathReplacements: [],
965
- cleanup: async () => {
966
- }
967
- };
1016
+ if (!query) {
1017
+ lines.push("");
1018
+ lines.push(chalk.bold("Conversation"));
1019
+ lines.push(` ${accent("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
1020
+ lines.push(` ${accent("/new".padEnd(22))} Start a new agent conversation.`);
1021
+ lines.push(` ${accent("/threads".padEnd(22))} List recent conversations.`);
1022
+ lines.push(` ${accent("/resume <id>".padEnd(22))} Resume a conversation.`);
1023
+ lines.push(` ${accent("/approve [id]".padEnd(22))} Approve a proposed action.`);
1024
+ lines.push(` ${accent("/reject [id]".padEnd(22))} Reject a proposed action.`);
1025
+ lines.push("");
1026
+ lines.push(chalk.bold("Session"));
1027
+ for (const command of SLASH_COMMANDS) {
1028
+ lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
1029
+ }
1030
+ lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
1031
+ lines.push(chalk.dim(
1032
+ "\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
1033
+ ));
968
1034
  }
969
- const sourcePath = resolveFromCwd(selected.path, cwd);
970
- if (!await pathIsFile(sourcePath)) {
971
- return {
972
- args,
973
- droppedSpecKeys: [],
974
- pathReplacements: [],
975
- cleanup: async () => {
976
- }
977
- };
1035
+ return `${lines.join("\n")}
1036
+ `;
1037
+ }
1038
+ function activeModelLabel(snapshot) {
1039
+ return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
1040
+ }
1041
+ function renderShellBanner(snapshot) {
1042
+ const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
1043
+ const heading = snapshot.version ? `${accent.bold("tt")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("tt");
1044
+ const lines = [
1045
+ heading,
1046
+ chalk.dim(
1047
+ `${snapshot.mode} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`
1048
+ ),
1049
+ chalk.dim("ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
1050
+ "",
1051
+ chalk.dim("Ask TT anything. Known commands run directly.")
1052
+ ];
1053
+ return `${lines.join("\n")}
1054
+
1055
+ `;
1056
+ }
1057
+ function renderShellPrompt() {
1058
+ const colors = inputColorCodes();
1059
+ if (!colors) return "\u203A ";
1060
+ return [
1061
+ colors.background,
1062
+ colors.accent,
1063
+ ANSI_BOLD,
1064
+ "\u203A",
1065
+ ANSI_BOLD_OFF,
1066
+ colors.text,
1067
+ " ",
1068
+ ANSI_CLEAR_TO_END
1069
+ ].join("");
1070
+ }
1071
+ function resetShellPromptStyle() {
1072
+ return chalk.level === 0 ? "" : ANSI_RESET;
1073
+ }
1074
+ var TunedTensorShellSession = class _TunedTensorShellSession {
1075
+ constructor(runner, io, agent, env, contextProvider, version, initialContext) {
1076
+ this.runner = runner;
1077
+ this.io = io;
1078
+ this.agent = agent;
1079
+ this.env = env;
1080
+ this.contextProvider = contextProvider;
1081
+ this.version = version;
1082
+ this.mode = initialContext.inferredTarget;
1083
+ this.modeSource = initialContext.targetSource;
1084
+ this.cwd = initialContext.cwd;
1085
+ this.context = initialContext;
978
1086
  }
979
- const raw = JSON.parse(await readFile(sourcePath, "utf8"));
980
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
981
- throw new Error(`${sourcePath} must contain a JSON object.`);
1087
+ mode;
1088
+ modeSource;
1089
+ cwd;
1090
+ context;
1091
+ static async create(options) {
1092
+ const env = options.env ?? process.env;
1093
+ const cwd = resolve2(options.cwd ?? process.cwd());
1094
+ const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
1095
+ const initialContext = await contextProvider({ cwd, env });
1096
+ return new _TunedTensorShellSession(
1097
+ options.runner,
1098
+ options.io,
1099
+ options.agent,
1100
+ env,
1101
+ contextProvider,
1102
+ options.version,
1103
+ initialContext
1104
+ );
982
1105
  }
983
- const body = raw;
984
- const runRequestKeys = [
985
- "run_id",
986
- "behavior_spec_id",
987
- "run_number",
988
- "spec_snapshot"
989
- ].filter((key) => key in body);
990
- if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
991
- return {
992
- args,
993
- droppedSpecKeys: [],
994
- pathReplacements: [],
995
- cleanup: async () => {
996
- }
997
- };
1106
+ interruptAgent() {
1107
+ return this.agent?.interrupt() ?? false;
998
1108
  }
999
- const projected = projectLocalSpec(body);
1000
- if (projected.droppedKeys.length === 0) {
1109
+ snapshot() {
1001
1110
  return {
1002
- args,
1003
- droppedSpecKeys: [],
1004
- pathReplacements: [],
1005
- cleanup: async () => {
1006
- }
1111
+ mode: this.mode,
1112
+ modeSource: this.modeSource,
1113
+ cwd: this.cwd,
1114
+ context: this.context,
1115
+ version: this.version
1007
1116
  };
1008
1117
  }
1009
- const temporaryPath = join2(
1010
- dirname(sourcePath),
1011
- `.${basename2(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
1012
- );
1013
- await writeFile(
1014
- temporaryPath,
1015
- `${JSON.stringify(projected.body, null, 2)}
1016
- `,
1017
- { encoding: "utf8", flag: "wx", mode: 384 }
1018
- );
1019
- selected.replace(temporaryPath);
1020
- return {
1021
- args,
1022
- droppedSpecKeys: projected.droppedKeys,
1023
- pathReplacements: [{
1024
- sourcePath,
1025
- projectedPath: temporaryPath
1026
- }],
1027
- cleanup: () => rm(temporaryPath, { force: true })
1028
- };
1029
- }
1030
- function resolveLocalCliEntrypoint() {
1031
- try {
1032
- return localRequire.resolve("@tuned-tensor/local");
1033
- } catch (error) {
1034
- throw new Error(
1035
- "TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
1036
- { cause: error }
1037
- );
1118
+ prompt() {
1119
+ return renderShellPrompt();
1038
1120
  }
1039
- }
1040
- function localCommandStreamsStdout(args) {
1041
- const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
1042
- return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
1043
- }
1044
- function parseJsonOutput(stdout2) {
1045
- if (!stdout2.trim()) return { hasJson: false, json: void 0 };
1046
- try {
1047
- return { hasJson: true, json: JSON.parse(stdout2) };
1048
- } catch {
1049
- return { hasJson: false, json: void 0 };
1121
+ banner() {
1122
+ return renderShellBanner(this.snapshot());
1050
1123
  }
1051
- }
1052
- function signalExitCode(signal) {
1053
- if (!signal) return 1;
1054
- const signalNumber = osConstants.signals[signal];
1055
- return typeof signalNumber === "number" ? 128 + signalNumber : 1;
1056
- }
1057
- function writeStream(stream, chunk) {
1058
- stream.write(chunk);
1059
- }
1060
- function normalizeProjectedPaths(value, replacements) {
1061
- let normalized = value;
1062
- for (const replacement of replacements) {
1063
- const encodedProjectedPath = JSON.stringify(
1064
- replacement.projectedPath
1065
- ).slice(1, -1);
1066
- const encodedSourcePath = JSON.stringify(
1067
- replacement.sourcePath
1068
- ).slice(1, -1);
1069
- normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
1124
+ async refreshContext() {
1125
+ this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
1126
+ this.cwd = this.context.cwd;
1127
+ if (this.modeSource !== "session" && this.modeSource !== "environment") {
1128
+ this.mode = this.context.inferredTarget;
1129
+ this.modeSource = this.context.targetSource;
1130
+ }
1070
1131
  }
1071
- return normalized;
1072
- }
1073
- async function spawnLocalCli(args) {
1074
- const output = args.stdout ?? process.stdout;
1075
- const errors = args.stderr ?? process.stderr;
1076
- const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
1077
- const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
1078
- const child = spawn(
1079
- args.nodeExecutable,
1080
- [args.entrypoint, ...args.commandArgs],
1081
- {
1082
- cwd: args.cwd,
1083
- env: args.env,
1084
- stdio: [
1085
- pipeInput ? "pipe" : "inherit",
1086
- args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
1087
- "pipe"
1088
- ],
1089
- detached: process.platform !== "win32"
1132
+ writeLines(lines) {
1133
+ this.io.write(`${lines.join("\n")}
1134
+ `);
1135
+ }
1136
+ modelLines() {
1137
+ if (this.mode === "local") {
1138
+ return styleDetailLines([
1139
+ detailLine("Active model", this.context.local.activeModelId ?? "base"),
1140
+ detailLine("Change", "/model <id> to activate a verified local model")
1141
+ ]);
1090
1142
  }
1091
- );
1092
- if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
1093
- let stdout2 = "";
1094
- let stderr = "";
1095
- child.stdout?.setEncoding("utf8");
1096
- child.stderr?.setEncoding("utf8");
1097
- child.stdout?.on("data", (chunk) => {
1098
- const normalized = normalizeProjectedPaths(
1099
- chunk,
1100
- args.pathReplacements
1101
- );
1102
- if (args.streamingStdout) writeStream(output, normalized);
1103
- else stdout2 += normalized;
1104
- });
1105
- child.stderr?.on("data", (chunk) => {
1106
- const normalized = normalizeProjectedPaths(
1107
- chunk,
1108
- args.pathReplacements
1109
- );
1110
- stderr += normalized;
1111
- writeStream(
1112
- errors,
1113
- args.jsonMode ? normalized : adaptLocalCliText(normalized)
1114
- );
1115
- });
1116
- return new Promise((resolveChild) => {
1117
- let spawnError;
1118
- const forwardSignal = (signal) => {
1119
- const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
1120
- if (child.pid && process.platform !== "win32") {
1121
- try {
1122
- process.kill(-child.pid, childSignal);
1123
- return;
1124
- } catch {
1143
+ return styleDetailLines([
1144
+ detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
1145
+ detailLine("Change", "edit base_model in tunedtensor.json, then run push")
1146
+ ]);
1147
+ }
1148
+ async handleSlash(command) {
1149
+ switch (command.name) {
1150
+ case "palette":
1151
+ this.io.write(helpText(this.mode, void 0, true));
1152
+ return "continue";
1153
+ case "help":
1154
+ this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
1155
+ return "continue";
1156
+ case "status":
1157
+ assertNoArgs("status", command.args);
1158
+ await this.refreshContext();
1159
+ this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
1160
+ return "continue";
1161
+ case "context":
1162
+ assertNoArgs("context", command.args);
1163
+ await this.refreshContext();
1164
+ this.writeLines(styleDetailLines(
1165
+ formatShellContext(this.context, this.mode, this.modeSource)
1166
+ ));
1167
+ return "continue";
1168
+ case "mode": {
1169
+ if (command.args.length === 0) {
1170
+ this.io.write(`Current workflow: ${this.mode}
1171
+ `);
1172
+ return "continue";
1173
+ }
1174
+ if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
1175
+ throw new ShellParseError("Usage: /mode cloud|local");
1125
1176
  }
1177
+ this.mode = command.args[0];
1178
+ this.modeSource = "session";
1179
+ this.io.write(`${successMark()} Workflow switched to ${this.mode}.
1180
+ `);
1181
+ return "continue";
1126
1182
  }
1127
- child.kill(childSignal);
1128
- };
1129
- const onSigint = () => forwardSignal("SIGINT");
1130
- const onSigterm = () => forwardSignal("SIGTERM");
1131
- const onSighup = () => forwardSignal("SIGHUP");
1132
- const cleanup = () => {
1133
- process.off("SIGINT", onSigint);
1134
- process.off("SIGTERM", onSigterm);
1135
- if (process.platform !== "win32") process.off("SIGHUP", onSighup);
1136
- };
1137
- process.on("SIGINT", onSigint);
1138
- process.on("SIGTERM", onSigterm);
1139
- if (process.platform !== "win32") process.on("SIGHUP", onSighup);
1140
- child.once("error", (error) => {
1141
- spawnError = error;
1142
- });
1143
- child.once("close", (code, signal) => {
1144
- cleanup();
1145
- resolveChild({
1146
- exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
1147
- signal,
1148
- stdout: stdout2,
1149
- stderr,
1150
- ...spawnError ? { errorMessage: spawnError.message } : {}
1183
+ case "model": {
1184
+ if (command.args.length === 0) {
1185
+ await this.refreshContext();
1186
+ this.writeLines(this.modelLines());
1187
+ return "continue";
1188
+ }
1189
+ if (command.args.length !== 1) {
1190
+ throw new ShellParseError("Usage: /model [model-id]");
1191
+ }
1192
+ if (this.mode !== "local") {
1193
+ throw new ShellParseError(
1194
+ "Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
1195
+ );
1196
+ }
1197
+ await this.runner({
1198
+ target: "local",
1199
+ args: ["models", "activate", command.args[0]],
1200
+ cwd: this.cwd
1201
+ });
1202
+ await this.refreshContext();
1203
+ return "continue";
1204
+ }
1205
+ case "clear":
1206
+ assertNoArgs("clear", command.args);
1207
+ this.io.clear();
1208
+ return "continue";
1209
+ case "cd": {
1210
+ if (command.args.length === 0) {
1211
+ this.io.write(`${this.cwd}
1212
+ `);
1213
+ return "continue";
1214
+ }
1215
+ if (command.args.length !== 1) {
1216
+ throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
1217
+ }
1218
+ const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
1219
+ const metadata = await stat2(nextDirectory).catch(() => null);
1220
+ if (!metadata?.isDirectory()) {
1221
+ throw new ShellParseError(`Not a directory: ${nextDirectory}`);
1222
+ }
1223
+ this.cwd = nextDirectory;
1224
+ await this.refreshContext();
1225
+ this.io.write(`${successMark()} Directory: ${this.cwd}
1226
+ `);
1227
+ return "continue";
1228
+ }
1229
+ case "exit":
1230
+ assertNoArgs("exit", command.args);
1231
+ return "exit";
1232
+ }
1233
+ }
1234
+ async handleLine(input) {
1235
+ try {
1236
+ if (/^(exit|quit)$/i.test(input.trim())) return "exit";
1237
+ const trimmed = input.trim();
1238
+ if (trimmed.startsWith("/")) {
1239
+ const rawName = trimmed.slice(1).split(/\s+/, 1)[0].toLowerCase();
1240
+ if (AGENT_SLASH_COMMANDS.has(rawName)) {
1241
+ if (!this.agent) {
1242
+ throw new ShellParseError("The conversational agent is unavailable.");
1243
+ }
1244
+ await this.agent.handleLine(trimmed);
1245
+ await this.refreshContext();
1246
+ return "continue";
1247
+ }
1248
+ }
1249
+ const slash = parseSlashCommand(input);
1250
+ if (slash) return await this.handleSlash(slash);
1251
+ const explicitCommand = trimmed.startsWith(":") ? trimmed.slice(1).trimStart() : null;
1252
+ if (explicitCommand !== null && !explicitCommand) {
1253
+ throw new ShellParseError("Add a TT command after :.");
1254
+ }
1255
+ const explicitWorkflowPrefix = /^(?:tt\s+)?(?:cloud|local)(?:\s|$)/.test(trimmed) || /^tt(?:\s|$)/.test(trimmed);
1256
+ if (explicitCommand === null && !explicitWorkflowPrefix && this.agent && !hasCatalogIntent(input, this.mode)) {
1257
+ await this.agent.handleLine(input);
1258
+ await this.refreshContext();
1259
+ return "continue";
1260
+ }
1261
+ const commandInput = explicitCommand ?? input;
1262
+ const routed = routeShellCommand(commandInput, this.mode);
1263
+ if (!routed) return "continue";
1264
+ await this.runner({
1265
+ target: routed.target,
1266
+ args: [...routed.args],
1267
+ cwd: this.cwd
1151
1268
  });
1152
- });
1153
- });
1269
+ await this.refreshContext();
1270
+ return "continue";
1271
+ } catch (error) {
1272
+ this.io.writeError(`${errorMark()} ${errorMessage(error)}
1273
+ `);
1274
+ return "continue";
1275
+ }
1276
+ }
1277
+ };
1278
+ async function createShellSession(options) {
1279
+ return TunedTensorShellSession.create(options);
1154
1280
  }
1155
- function setupFailure(originalArgs, error) {
1156
- const message = error instanceof Error ? error.message : String(error);
1281
+ function streamIsTTY(stream) {
1282
+ return Boolean(stream.isTTY);
1283
+ }
1284
+ async function withForegroundSignalHandoff(control, run) {
1285
+ const signals = control.signals ?? process;
1286
+ const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
1287
+ const keepParentAlive = () => {
1288
+ };
1289
+ control.pauseReadline();
1290
+ if (restoreRawMode) control.input.setRawMode(false);
1291
+ signals.on("SIGINT", keepParentAlive);
1292
+ try {
1293
+ return await run();
1294
+ } finally {
1295
+ signals.off("SIGINT", keepParentAlive);
1296
+ if (restoreRawMode) control.input.setRawMode(true);
1297
+ control.resumeReadline();
1298
+ }
1299
+ }
1300
+ function streamIO(output, error) {
1157
1301
  return {
1158
- args: [...originalArgs],
1159
- projectedArgs: [...originalArgs],
1160
- entrypoint: null,
1161
- exitCode: 1,
1162
- signal: null,
1163
- stdout: "",
1164
- stderr: "",
1165
- hasJson: false,
1166
- json: void 0,
1167
- streamingStdout: false,
1168
- droppedSpecKeys: [],
1169
- errorMessage: message
1302
+ write(text2) {
1303
+ output.write(text2);
1304
+ },
1305
+ writeError(text2) {
1306
+ error.write(text2);
1307
+ },
1308
+ clear() {
1309
+ output.write("\x1B[2J\x1B[H");
1310
+ }
1170
1311
  };
1171
1312
  }
1172
- async function runLocalCommand(args, options = {}) {
1173
- const cwd = resolve(options.cwd ?? process.cwd());
1174
- let projected;
1313
+ async function startInteractiveShell(options) {
1314
+ const input = options.input ?? process.stdin;
1315
+ const output = options.output ?? process.stdout;
1316
+ const error = options.error ?? process.stderr;
1317
+ if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
1318
+ throw new Error("The interactive TT shell requires a TTY.");
1319
+ }
1320
+ let readline;
1321
+ const terminalInput = input;
1322
+ const foregroundRunner = (request2) => withForegroundSignalHandoff(
1323
+ {
1324
+ input: terminalInput,
1325
+ pauseReadline: () => readline?.pause(),
1326
+ resumeReadline: () => readline?.resume()
1327
+ },
1328
+ () => options.runner(request2)
1329
+ );
1330
+ const session = await createShellSession({
1331
+ runner: foregroundRunner,
1332
+ io: streamIO(output, error),
1333
+ agent: options.agent,
1334
+ cwd: options.cwd,
1335
+ env: options.env,
1336
+ version: options.version
1337
+ });
1338
+ const completer = createCommandCompleter(() => session.snapshot().mode);
1339
+ readline = createInterface({
1340
+ input,
1341
+ output,
1342
+ terminal: true,
1343
+ historySize: 100,
1344
+ removeHistoryDuplicates: true,
1345
+ completer
1346
+ });
1347
+ readline.on("SIGINT", () => {
1348
+ if (session.interruptAgent()) {
1349
+ output.write(`${resetShellPromptStyle()}
1350
+ `);
1351
+ return;
1352
+ }
1353
+ output.write(`${resetShellPromptStyle()}
1354
+ `);
1355
+ readline?.setPrompt(session.prompt());
1356
+ readline?.write(null, { ctrl: true, name: "u" });
1357
+ });
1358
+ output.write(session.banner());
1359
+ readline.setPrompt(session.prompt());
1360
+ readline.prompt();
1175
1361
  try {
1176
- const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
1177
- projected = await projectSpecArguments(args, cwd);
1178
- const streamingStdout = localCommandStreamsStdout(projected.args);
1179
- const result = await spawnLocalCli({
1180
- entrypoint,
1181
- commandArgs: projected.args,
1182
- cwd,
1183
- env: options.env ?? process.env,
1184
- nodeExecutable: options.nodeExecutable ?? process.execPath,
1185
- streamingStdout,
1186
- jsonMode: options.jsonMode ?? false,
1187
- stdin: options.stdin,
1188
- stdout: options.stdout,
1189
- stderr: options.stderr,
1190
- pathReplacements: projected.pathReplacements
1191
- });
1192
- const parsed = parseJsonOutput(result.stdout);
1193
- return {
1194
- args: [...args],
1195
- projectedArgs: projected.args,
1196
- entrypoint,
1197
- ...result,
1198
- ...parsed,
1199
- streamingStdout,
1200
- droppedSpecKeys: projected.droppedSpecKeys
1201
- };
1202
- } catch (error) {
1203
- return setupFailure(args, error);
1362
+ for await (const line of readline) {
1363
+ output.write(resetShellPromptStyle());
1364
+ const action = await session.handleLine(line);
1365
+ if (action === "exit") {
1366
+ readline.close();
1367
+ break;
1368
+ }
1369
+ output.write("\n");
1370
+ readline.setPrompt(session.prompt());
1371
+ readline.prompt();
1372
+ }
1204
1373
  } finally {
1205
- await projected?.cleanup().catch(() => void 0);
1374
+ output.write(resetShellPromptStyle());
1375
+ readline.close();
1206
1376
  }
1207
1377
  }
1208
- async function executeLocalCommand(args, options = {}) {
1209
- const result = await runLocalCommand(args, options);
1210
- renderLocalOutput(result, {
1211
- jsonMode: options.jsonMode,
1212
- stdout: options.stdout
1213
- });
1214
- if (result.exitCode !== 0) process.exitCode = result.exitCode;
1215
- return result;
1378
+ function ciEnabled(value) {
1379
+ if (!value) return false;
1380
+ return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
1381
+ }
1382
+ function shouldStartInteractiveShell(options) {
1383
+ const env = options.env ?? process.env;
1384
+ return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
1216
1385
  }
1217
1386
 
1218
- // src/shell.ts
1219
- import chalk2 from "chalk";
1220
- import { stat as stat3 } from "fs/promises";
1221
- import { homedir as homedir3 } from "os";
1222
- import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
1223
- import { createInterface } from "readline";
1224
-
1225
- // src/command-catalog.ts
1226
- var CLOUD = ["cloud"];
1227
- var LOCAL = ["local"];
1228
- var BOTH = ["cloud", "local"];
1229
- var COMMAND_CATALOG = [
1230
- { path: "init", description: "Create a behaviour-spec project.", group: "Workflow", modes: BOTH },
1231
- { path: "eval", description: "Validate a managed-service behaviour spec.", group: "Workflow", modes: CLOUD },
1232
- { path: "push", description: "Create or update the cloud behaviour spec.", group: "Workflow", modes: CLOUD },
1233
- { path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
1234
- { path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
1235
- { path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
1236
- { path: "runs list", description: "List runs.", group: "Inspect", modes: BOTH },
1237
- { path: "runs get", description: "Show a run.", group: "Inspect", modes: BOTH },
1238
- { path: "runs report", description: "Show a run report.", group: "Inspect", modes: BOTH },
1239
- { path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
1240
- { path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
1241
- { path: "runs estimate", description: "Estimate managed run cost and duration.", group: "Workflow", modes: CLOUD },
1242
- { path: "runs start", description: "Start a managed fine-tuning run.", group: "Workflow", modes: CLOUD },
1243
- { path: "runs watch", description: "Watch a managed run.", group: "Inspect", modes: CLOUD },
1244
- { path: "runs diagnose", description: "Show managed run diagnostics.", group: "Inspect", modes: CLOUD },
1245
- { path: "runs cancel", description: "Cancel a managed run.", group: "Workflow", modes: CLOUD },
1246
- { path: "models list", description: "List models.", group: "Inspect", modes: BOTH },
1247
- { path: "models get", description: "Show a model.", group: "Inspect", modes: BOTH },
1248
- { path: "models base", description: "List managed-service base models.", group: "Inspect", modes: CLOUD },
1249
- { path: "models download", description: "Download a managed model artifact.", group: "Serving", modes: CLOUD },
1250
- { path: "models export", description: "Export a managed model to GGUF or Ollama.", group: "Serving", modes: CLOUD },
1251
- { path: "models setup-runtime", description: "Install the managed model-serving runtime.", group: "Serving", modes: CLOUD },
1252
- { path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: BOTH },
1253
- { path: "models delete", description: "Delete a managed model.", group: "Workflow", modes: CLOUD },
1254
- { path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
1255
- { path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
1256
- { path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
1257
- { path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
1258
- { path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
1259
- { path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
1260
- { path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
1261
- { path: "specs list", description: "List cloud behaviour specs.", group: "Inspect", modes: CLOUD },
1262
- { path: "specs get", description: "Show a cloud behaviour spec.", group: "Inspect", modes: CLOUD },
1263
- { path: "specs create", description: "Create a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
1264
- { path: "specs update", description: "Update a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
1265
- { path: "specs delete", description: "Delete a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
1266
- { path: "datasets list", description: "List cloud datasets.", group: "Data", modes: CLOUD },
1267
- { path: "datasets get", description: "Show a cloud dataset.", group: "Data", modes: CLOUD },
1268
- { path: "datasets upload", description: "Upload a cloud dataset.", group: "Data", modes: CLOUD },
1269
- { path: "datasets delete", description: "Delete a cloud dataset.", group: "Data", modes: CLOUD },
1270
- { path: "label upload", description: "Start a cloud teacher-labeling job.", group: "Data", modes: CLOUD },
1271
- { path: "label watch", description: "Watch a cloud labeling job.", group: "Data", modes: CLOUD },
1272
- { path: "label list", description: "List cloud labeling jobs.", group: "Data", modes: CLOUD },
1273
- { path: "label status", description: "Show cloud labeling-job status.", group: "Data", modes: CLOUD },
1274
- { path: "label rows", description: "Review labeled rows.", group: "Data", modes: CLOUD },
1275
- { path: "label accept", description: "Accept labeled rows.", group: "Data", modes: CLOUD },
1276
- { path: "label reject", description: "Reject labeled rows.", group: "Data", modes: CLOUD },
1277
- { path: "label edit", description: "Edit a labeled row.", group: "Data", modes: CLOUD },
1278
- { path: "label promote", description: "Promote reviewed rows to a dataset.", group: "Data", modes: CLOUD },
1279
- { path: "label cancel", description: "Cancel a labeling job.", group: "Data", modes: CLOUD },
1280
- { path: "auth login", description: "Store a cloud API key.", group: "Account", modes: CLOUD },
1281
- { path: "auth logout", description: "Remove stored cloud credentials.", group: "Account", modes: CLOUD },
1282
- { path: "auth status", description: "Show cloud authentication status.", group: "Account", modes: CLOUD },
1283
- { path: "balance", description: "Show cloud credit balance.", group: "Account", modes: CLOUD },
1284
- { path: "topup", description: "Open a cloud credit checkout.", group: "Account", modes: CLOUD },
1285
- { path: "info", description: "Show TT Local package information.", group: "Inspect", modes: LOCAL }
1387
+ // src/agent.ts
1388
+ var accent2 = chalk2.hex("#8B5CF6");
1389
+ var successMark2 = () => chalk2.green("\u2713");
1390
+ var errorMark2 = () => chalk2.red("\u2717");
1391
+ function isRecord(value) {
1392
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1393
+ }
1394
+ function stringValue2(value) {
1395
+ return typeof value === "string" && value.length > 0 ? value : null;
1396
+ }
1397
+ function helpText2() {
1398
+ return [
1399
+ accent2.bold("Agent commands"),
1400
+ "",
1401
+ ` ${accent2("/new".padEnd(22))} Start a new conversation.`,
1402
+ ` ${accent2("/threads".padEnd(22))} List recent conversations.`,
1403
+ ` ${accent2("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
1404
+ ` ${accent2("/approve [id]".padEnd(22))} Approve a proposed action.`,
1405
+ ` ${accent2("/reject [id]".padEnd(22))} Reject a proposed action.`,
1406
+ ` ${accent2("/status".padEnd(22))} Show the active conversation.`,
1407
+ ` ${accent2("/clear".padEnd(22))} Clear the terminal.`,
1408
+ ` ${accent2("/exit".padEnd(22))} Exit the agent.`,
1409
+ "",
1410
+ chalk2.dim(
1411
+ "Everything else is sent to the Tuned Tensor agent; known TT commands still run directly in the shell."
1412
+ ),
1413
+ ""
1414
+ ].join("\n");
1415
+ }
1416
+ function formatThread(thread, activeId) {
1417
+ const active = thread.id === activeId ? accent2("\u25CF") : chalk2.dim("\u25CB");
1418
+ const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
1419
+ const date = new Date(time);
1420
+ const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
1421
+ month: "short",
1422
+ day: "numeric",
1423
+ hour: "2-digit",
1424
+ minute: "2-digit"
1425
+ });
1426
+ return `${active} ${chalk2.dim(thread.id.slice(0, 8))} ${thread.title} ${chalk2.dim(dateLabel)}`;
1427
+ }
1428
+ function actionFromPayload(payload) {
1429
+ const value = isRecord(payload.action) ? payload.action : isRecord(payload.approval) ? payload.approval : payload;
1430
+ const id = stringValue2(value.id);
1431
+ const title = stringValue2(value.title);
1432
+ if (!id || !title) return null;
1433
+ return {
1434
+ id,
1435
+ title,
1436
+ summary: stringValue2(value.summary) ?? "",
1437
+ risk: stringValue2(value.risk) ?? "unknown",
1438
+ status: stringValue2(value.status) ?? "proposed",
1439
+ operation: stringValue2(value.operation) ?? void 0,
1440
+ thread_id: stringValue2(value.thread_id) ?? void 0,
1441
+ turn_id: stringValue2(value.turn_id) ?? void 0,
1442
+ arguments: value.arguments,
1443
+ preview: value.preview,
1444
+ method: stringValue2(value.method) ?? void 0,
1445
+ path: stringValue2(value.path) ?? void 0
1446
+ };
1447
+ }
1448
+ function formatJson(value) {
1449
+ try {
1450
+ return JSON.stringify(value, null, 2);
1451
+ } catch {
1452
+ return String(value);
1453
+ }
1454
+ }
1455
+ var TunedTensorAgentSession = class {
1456
+ constructor(options) {
1457
+ this.options = options;
1458
+ this.thread = options.thread ?? null;
1459
+ }
1460
+ thread;
1461
+ pendingActions = /* @__PURE__ */ new Map();
1462
+ activeRequest = null;
1463
+ responseStarted = false;
1464
+ reasoningActive = false;
1465
+ lineOpen = false;
1466
+ get busy() {
1467
+ return this.activeRequest !== null;
1468
+ }
1469
+ snapshot() {
1470
+ return {
1471
+ thread: this.thread,
1472
+ pendingActions: [...this.pendingActions.values()]
1473
+ };
1474
+ }
1475
+ interrupt() {
1476
+ if (!this.activeRequest) return false;
1477
+ this.activeRequest.abort("user-stop");
1478
+ return true;
1479
+ }
1480
+ endOpenLine() {
1481
+ if (this.lineOpen) this.options.io.write("\n");
1482
+ this.lineOpen = false;
1483
+ this.reasoningActive = false;
1484
+ }
1485
+ renderAction(action) {
1486
+ this.endOpenLine();
1487
+ this.options.io.write(
1488
+ `
1489
+ ${chalk2.yellow.bold("Approval required")} ${chalk2.dim(action.id.slice(0, 8))}
1490
+ `
1491
+ );
1492
+ this.options.io.write(`${chalk2.bold(action.title)} ${chalk2.yellow(`[${action.risk} risk]`)}
1493
+ `);
1494
+ if (action.summary) this.options.io.write(`${action.summary}
1495
+ `);
1496
+ const request2 = [action.method, action.path].filter(Boolean).join(" ");
1497
+ const technical = {
1498
+ operation: action.operation,
1499
+ ...request2 ? { request: request2 } : {},
1500
+ arguments: action.arguments ?? null,
1501
+ preview: action.preview ?? null
1502
+ };
1503
+ this.options.io.write(`${chalk2.bold("Proposed action")}
1504
+ `);
1505
+ this.options.io.write(`${formatJson(technical)}
1506
+ `);
1507
+ this.options.io.write(
1508
+ chalk2.dim("Use /approve or /reject. The action will not run without approval.") + "\n"
1509
+ );
1510
+ }
1511
+ renderEvent(event) {
1512
+ const payload = event.payload;
1513
+ if (event.type === "text_delta") {
1514
+ const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
1515
+ if (!delta) return;
1516
+ const followsReasoning = this.reasoningActive;
1517
+ if (followsReasoning) this.endOpenLine();
1518
+ if (!this.responseStarted) {
1519
+ this.options.io.write(`
1520
+ ${accent2.bold("tt")} `);
1521
+ this.responseStarted = true;
1522
+ this.lineOpen = true;
1523
+ } else if (followsReasoning) {
1524
+ this.options.io.write("\n");
1525
+ }
1526
+ this.options.io.write(delta);
1527
+ this.lineOpen = !delta.endsWith("\n");
1528
+ return;
1529
+ }
1530
+ if (event.type === "reasoning_delta") {
1531
+ const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
1532
+ if (!delta) return;
1533
+ if (!this.reasoningActive) {
1534
+ this.endOpenLine();
1535
+ this.options.io.write("\n");
1536
+ this.reasoningActive = true;
1537
+ }
1538
+ this.options.io.write(chalk2.dim.italic(delta));
1539
+ this.lineOpen = !delta.endsWith("\n");
1540
+ return;
1541
+ }
1542
+ if (event.type === "tool_call") {
1543
+ this.endOpenLine();
1544
+ const name = stringValue2(payload.name) ?? stringValue2(isRecord(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
1545
+ this.options.io.write(chalk2.dim(` \u25CB ${name}
1546
+ `));
1547
+ return;
1548
+ }
1549
+ if (event.type === "tool_result") {
1550
+ this.endOpenLine();
1551
+ const failed = payload.status === "error" || Boolean(payload.error);
1552
+ this.options.io.write(
1553
+ chalk2.dim(` ${failed ? chalk2.red("\u2717") : chalk2.green("\u2713")} Tool ${failed ? "failed" : "complete"}
1554
+ `)
1555
+ );
1556
+ return;
1557
+ }
1558
+ if (event.type === "approval_required") {
1559
+ const action = actionFromPayload(payload);
1560
+ if (!action) return;
1561
+ this.pendingActions.set(action.id, action);
1562
+ this.renderAction(action);
1563
+ return;
1564
+ }
1565
+ if (event.type === "action_started") {
1566
+ this.endOpenLine();
1567
+ this.options.io.write(chalk2.dim(" \u25CB Running approved action\u2026\n"));
1568
+ return;
1569
+ }
1570
+ if (event.type === "action_result") {
1571
+ this.endOpenLine();
1572
+ const failed = payload.status === "failed" || Boolean(payload.error);
1573
+ this.options.io.write(
1574
+ `${failed ? errorMark2() : successMark2()} Approved action ${failed ? "failed" : "completed"}.
1575
+ `
1576
+ );
1577
+ return;
1578
+ }
1579
+ if (event.type === "error") {
1580
+ this.endOpenLine();
1581
+ const message = stringValue2(payload.message) ?? stringValue2(payload.error) ?? "The agent could not complete this request.";
1582
+ this.options.io.writeError(`${errorMark2()} ${message}
1583
+ `);
1584
+ }
1585
+ }
1586
+ async ensureThread() {
1587
+ if (!this.thread) this.thread = await this.options.client.createThread();
1588
+ return this.thread;
1589
+ }
1590
+ async runStream(operation) {
1591
+ if (this.activeRequest) {
1592
+ this.options.io.writeError(`${errorMark2()} A response is already running.
1593
+ `);
1594
+ return null;
1595
+ }
1596
+ const controller = new AbortController();
1597
+ this.activeRequest = controller;
1598
+ this.responseStarted = false;
1599
+ this.reasoningActive = false;
1600
+ this.lineOpen = false;
1601
+ try {
1602
+ const result = await operation(controller.signal);
1603
+ this.endOpenLine();
1604
+ for (const action of result.actions) {
1605
+ this.pendingActions.set(action.id, action);
1606
+ }
1607
+ return result;
1608
+ } catch (error) {
1609
+ this.endOpenLine();
1610
+ if (controller.signal.aborted) {
1611
+ this.options.io.write(chalk2.dim("Response stopped.\n"));
1612
+ return null;
1613
+ }
1614
+ throw error;
1615
+ } finally {
1616
+ this.activeRequest = null;
1617
+ this.responseStarted = false;
1618
+ this.reasoningActive = false;
1619
+ this.lineOpen = false;
1620
+ }
1621
+ }
1622
+ async send(prompt) {
1623
+ const normalized = prompt.trim();
1624
+ if (!normalized) return null;
1625
+ const thread = await this.ensureThread();
1626
+ return await this.runStream(
1627
+ async (signal) => await this.options.client.runTurn(
1628
+ thread.id,
1629
+ normalized,
1630
+ (event) => this.renderEvent(event),
1631
+ signal
1632
+ )
1633
+ );
1634
+ }
1635
+ resolvePendingAction(idOrPrefix) {
1636
+ const candidates = [...this.pendingActions.values()].filter(
1637
+ (action) => idOrPrefix ? action.id.startsWith(idOrPrefix) : true
1638
+ );
1639
+ if (candidates.length === 0) {
1640
+ throw new Error(
1641
+ idOrPrefix ? `No pending action matches ${idOrPrefix}.` : "There is no pending action."
1642
+ );
1643
+ }
1644
+ if (candidates.length > 1) {
1645
+ throw new Error("More than one action is pending; include its ID or prefix.");
1646
+ }
1647
+ return candidates[0];
1648
+ }
1649
+ async approve(idOrPrefix) {
1650
+ const action = this.resolvePendingAction(idOrPrefix);
1651
+ const result = await this.runStream(
1652
+ async (signal) => await this.options.client.approveAction(
1653
+ action.id,
1654
+ (event) => this.renderEvent(event),
1655
+ signal
1656
+ )
1657
+ );
1658
+ if (result) this.pendingActions.delete(action.id);
1659
+ }
1660
+ async reject(idOrPrefix) {
1661
+ const action = this.resolvePendingAction(idOrPrefix);
1662
+ await this.options.client.rejectAction(action.id);
1663
+ this.pendingActions.delete(action.id);
1664
+ this.options.io.write(`${successMark2()} Action rejected.
1665
+ `);
1666
+ }
1667
+ async resolveThread(idOrPrefix) {
1668
+ const id = idOrPrefix.trim();
1669
+ if (!id) throw new Error("Usage: /resume <conversation-id>");
1670
+ if (id.length >= 32) return await this.options.client.getThread(id);
1671
+ const matches = (await this.options.client.listThreads()).filter((thread) => thread.id.startsWith(id));
1672
+ if (matches.length === 0) throw new Error(`No conversation matches ${id}.`);
1673
+ if (matches.length > 1) {
1674
+ throw new Error(`Conversation prefix ${id} is ambiguous.`);
1675
+ }
1676
+ return await this.options.client.getThread(matches[0].id);
1677
+ }
1678
+ async handleCommand(input) {
1679
+ const tokens = tokenizeShellInput(input);
1680
+ const command = tokens[0].slice(1).toLowerCase();
1681
+ const args = tokens.slice(1);
1682
+ switch (command) {
1683
+ case "help":
1684
+ this.options.io.write(helpText2());
1685
+ return "continue";
1686
+ case "new":
1687
+ if (args.length > 0) throw new Error("/new does not accept arguments.");
1688
+ this.thread = null;
1689
+ this.pendingActions.clear();
1690
+ this.options.io.write(`${successMark2()} New conversation ready.
1691
+ `);
1692
+ return "continue";
1693
+ case "threads": {
1694
+ if (args.length > 0) throw new Error("/threads does not accept arguments.");
1695
+ const threads = await this.options.client.listThreads();
1696
+ if (threads.length === 0) {
1697
+ this.options.io.write(chalk2.dim("No conversations yet.\n"));
1698
+ return "continue";
1699
+ }
1700
+ this.options.io.write(
1701
+ `${threads.slice(0, 20).map((thread) => formatThread(thread, this.thread?.id)).join("\n")}
1702
+ `
1703
+ );
1704
+ return "continue";
1705
+ }
1706
+ case "resume":
1707
+ if (args.length !== 1) throw new Error("Usage: /resume <conversation-id>");
1708
+ {
1709
+ const detail = await this.resolveThread(args[0]);
1710
+ this.thread = detail.thread;
1711
+ this.pendingActions.clear();
1712
+ for (const action of detail.actions) {
1713
+ if (action.status === "proposed") {
1714
+ this.pendingActions.set(action.id, action);
1715
+ }
1716
+ }
1717
+ }
1718
+ this.options.io.write(
1719
+ `${successMark2()} Resumed ${this.thread.title} (${this.thread.id.slice(0, 8)}).
1720
+ `
1721
+ );
1722
+ return "continue";
1723
+ case "approve":
1724
+ if (args.length > 1) throw new Error("Usage: /approve [action-id]");
1725
+ await this.approve(args[0]);
1726
+ return "continue";
1727
+ case "reject":
1728
+ if (args.length > 1) throw new Error("Usage: /reject [action-id]");
1729
+ await this.reject(args[0]);
1730
+ return "continue";
1731
+ case "status":
1732
+ if (args.length > 0) throw new Error("/status does not accept arguments.");
1733
+ this.options.io.write(
1734
+ this.thread ? `${formatThread(this.thread, this.thread.id)}
1735
+ ` : chalk2.dim("New conversation; send a message to create it.\n")
1736
+ );
1737
+ this.options.io.write(
1738
+ chalk2.dim(`${this.pendingActions.size} pending approval(s).
1739
+ `)
1740
+ );
1741
+ return "continue";
1742
+ case "clear":
1743
+ if (args.length > 0) throw new Error("/clear does not accept arguments.");
1744
+ this.options.io.clear();
1745
+ return "continue";
1746
+ case "exit":
1747
+ if (args.length > 0) throw new Error("/exit does not accept arguments.");
1748
+ return "exit";
1749
+ default:
1750
+ throw new Error(`Unknown agent command: /${command}. Use /help.`);
1751
+ }
1752
+ }
1753
+ async handleLine(input) {
1754
+ try {
1755
+ const normalized = input.trim();
1756
+ if (!normalized) return "continue";
1757
+ if (/^(exit|quit)$/i.test(normalized)) return "exit";
1758
+ if (normalized.startsWith("/")) return await this.handleCommand(normalized);
1759
+ await this.send(normalized);
1760
+ return "continue";
1761
+ } catch (error) {
1762
+ const message = error instanceof Error ? error.message : String(error);
1763
+ this.options.io.writeError(`${errorMark2()} ${message}
1764
+ `);
1765
+ return "continue";
1766
+ }
1767
+ }
1768
+ };
1769
+
1770
+ // src/local-runner.ts
1771
+ import { constants as osConstants } from "os";
1772
+ import { randomUUID } from "crypto";
1773
+ import { createRequire } from "module";
1774
+ import { spawn } from "child_process";
1775
+ import {
1776
+ readFile as readFile2,
1777
+ rm,
1778
+ stat as stat3,
1779
+ writeFile
1780
+ } from "fs/promises";
1781
+ import {
1782
+ basename as basename3,
1783
+ dirname as dirname2,
1784
+ isAbsolute as isAbsolute3,
1785
+ join as join3,
1786
+ resolve as resolve3
1787
+ } from "path";
1788
+
1789
+ // src/base-models.ts
1790
+ var SUPPORTED_BASE_MODELS = [
1791
+ "google/gemma-4-E2B-it",
1792
+ "google/gemma-4-E4B-it",
1793
+ "Qwen/Qwen3.5-2B",
1794
+ "Qwen/Qwen3-VL-2B-Instruct",
1795
+ "Qwen/Qwen3.5-4B",
1796
+ "meta-llama/Llama-3.2-3B-Instruct",
1797
+ "microsoft/Phi-4-mini-instruct",
1798
+ "ibm-granite/granite-3.3-2b-instruct",
1799
+ "bigcode/starcoder2-3b"
1286
1800
  ];
1287
- var SLASH_COMMANDS = [
1288
- { path: "/help", description: "Show commands; add a word to filter." },
1289
- { path: "/status", description: "Show lightweight workflow status." },
1290
- { path: "/context", description: "Show the current project and backend context." },
1291
- { path: "/mode cloud", description: "Use the managed cloud workflow." },
1292
- { path: "/mode local", description: "Use the local GPU workflow." },
1293
- { path: "/model", description: "Show the active model; /model <id> activates a local model." },
1294
- { path: "/cd", description: "Change the shell's working directory." },
1295
- { path: "/clear", description: "Clear the terminal." },
1296
- { path: "/exit", description: "Exit the TT shell." }
1801
+ var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
1802
+ for (const model of SUPPORTED_BASE_MODELS) {
1803
+ BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
1804
+ }
1805
+ for (const [alias, model] of [
1806
+ ["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
1807
+ ["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
1808
+ ["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
1809
+ ["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
1810
+ ["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
1811
+ ["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
1812
+ ["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
1813
+ ["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
1814
+ ["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
1815
+ ["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
1816
+ ["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
1817
+ ["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
1818
+ ["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
1819
+ ["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
1820
+ ["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
1821
+ ["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
1822
+ ["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
1823
+ ["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
1824
+ ["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
1825
+ ["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
1826
+ ["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
1827
+ ["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
1828
+ ["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
1829
+ ["starcoder2-3b", "bigcode/starcoder2-3b"]
1830
+ ]) {
1831
+ BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
1832
+ }
1833
+ var UnsupportedBaseModelError = class extends Error {
1834
+ constructor(model) {
1835
+ const supported = SUPPORTED_BASE_MODELS.join(", ");
1836
+ super(
1837
+ `Unsupported base_model "${String(model)}". Supported base models: ${supported}`
1838
+ );
1839
+ this.name = "UnsupportedBaseModelError";
1840
+ }
1841
+ };
1842
+ function canonicalizeBaseModel(model) {
1843
+ if (typeof model !== "string" || !model.trim()) {
1844
+ throw new UnsupportedBaseModelError(model);
1845
+ }
1846
+ const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
1847
+ if (!canonical) {
1848
+ throw new UnsupportedBaseModelError(model);
1849
+ }
1850
+ return canonical;
1851
+ }
1852
+ function canonicalizeSpecBaseModel(body) {
1853
+ if ("base_model" in body && body.base_model !== void 0) {
1854
+ return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
1855
+ }
1856
+ return body;
1857
+ }
1858
+
1859
+ // src/project-spec.ts
1860
+ var CLOUD_SPEC_KEYS = [
1861
+ "name",
1862
+ "description",
1863
+ "base_model",
1864
+ "system_prompt",
1865
+ "guidelines",
1866
+ "constraints",
1867
+ "examples",
1868
+ "eval_cases"
1297
1869
  ];
1298
- function catalogForMode(mode) {
1299
- return COMMAND_CATALOG.filter((command) => command.modes.includes(mode));
1870
+ var LOCAL_ONLY_SPEC_KEYS = [
1871
+ "hyperparameters",
1872
+ "dataset_prebuilt"
1873
+ ];
1874
+ var LOCAL_SPEC_KEYS = [
1875
+ "id",
1876
+ "name",
1877
+ "description",
1878
+ "system_prompt",
1879
+ "guidelines",
1880
+ "constraints",
1881
+ "base_model",
1882
+ "examples",
1883
+ ...LOCAL_ONLY_SPEC_KEYS
1884
+ ];
1885
+ var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
1886
+ var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
1887
+ var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
1888
+ var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
1889
+ ...CLOUD_SPEC_KEYS,
1890
+ ...LOCAL_SPEC_KEYS
1891
+ ]);
1892
+ function projectSpec(raw, supportedKeys) {
1893
+ const body = {};
1894
+ const droppedKeys = [];
1895
+ for (const [key, value] of Object.entries(raw)) {
1896
+ if (supportedKeys.has(key)) {
1897
+ body[key] = value;
1898
+ } else {
1899
+ droppedKeys.push(key);
1900
+ }
1901
+ }
1902
+ return {
1903
+ body: canonicalizeSpecBaseModel(body),
1904
+ droppedKeys
1905
+ };
1300
1906
  }
1301
- function commandPathsForMode(mode) {
1302
- return [...new Set(catalogForMode(mode).map((command) => command.path))].sort((left, right) => left.localeCompare(right));
1907
+ function projectCloudSpec(raw) {
1908
+ return projectSpec(raw, CLOUD_SPEC_KEY_SET);
1303
1909
  }
1304
- function groupedCatalog(mode, query) {
1305
- const normalizedQuery = query?.trim().toLowerCase();
1306
- const matches = catalogForMode(mode).filter((command) => {
1307
- if (!normalizedQuery) return true;
1308
- return command.path.toLowerCase().includes(normalizedQuery) || command.description.toLowerCase().includes(normalizedQuery) || command.group.toLowerCase().includes(normalizedQuery);
1910
+ function projectLocalSpec(raw) {
1911
+ return projectSpec(raw, LOCAL_SPEC_KEY_SET);
1912
+ }
1913
+ function hasLocalOnlySpecFields(raw) {
1914
+ return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
1915
+ }
1916
+ function unknownProjectSpecKeys(raw) {
1917
+ return Object.keys(raw).filter(
1918
+ (key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
1919
+ );
1920
+ }
1921
+
1922
+ // src/output.ts
1923
+ import chalk3 from "chalk";
1924
+ import Table from "cli-table3";
1925
+ var jsonMode = false;
1926
+ function setJsonMode(enabled) {
1927
+ jsonMode = enabled;
1928
+ }
1929
+ function isJsonMode() {
1930
+ return jsonMode;
1931
+ }
1932
+ function printJson(data) {
1933
+ console.log(JSON.stringify(data, null, 2));
1934
+ }
1935
+ function printTable(headers, rows2, meta) {
1936
+ const table = new Table({
1937
+ head: headers.map((h) => chalk3.bold.cyan(h)),
1938
+ style: { head: [], border: [] }
1309
1939
  });
1310
- const groups = /* @__PURE__ */ new Map();
1311
- for (const command of matches) {
1312
- const group = groups.get(command.group) ?? [];
1313
- group.push(command);
1314
- groups.set(command.group, group);
1940
+ for (const row of rows2) {
1941
+ table.push(row);
1942
+ }
1943
+ console.log(table.toString());
1944
+ if (meta) {
1945
+ const totalPages = Math.ceil(meta.total / meta.per_page);
1946
+ console.log(
1947
+ chalk3.dim(
1948
+ `
1949
+ Page ${meta.page}/${totalPages} (${meta.total} total)`
1950
+ )
1951
+ );
1315
1952
  }
1316
- return groups;
1317
1953
  }
1318
- function completionTarget(line, activeMode) {
1319
- const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
1320
- const content = line.slice(leadingWhitespace.length);
1321
- const override = content.match(/^(cloud|local)(?:\s+|$)/);
1322
- if (!override) {
1323
- return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
1954
+ function printDetail(fields) {
1955
+ const maxLabel = Math.max(...fields.map(([l]) => l.length));
1956
+ for (const [label, value] of fields) {
1957
+ const paddedLabel = label.padEnd(maxLabel);
1958
+ console.log(`${chalk3.bold.cyan(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
1324
1959
  }
1325
- const mode = override[1];
1326
- const prefix = `${leadingWhitespace}${mode} `;
1327
- return {
1328
- prefix,
1329
- mode,
1330
- fragment: content.slice(override[0].length)
1331
- };
1332
1960
  }
1333
- function createCommandCompleter(getMode) {
1334
- return (line) => {
1335
- const trimmed = line.trimStart();
1336
- if (trimmed.startsWith("/")) {
1337
- const candidates = SLASH_COMMANDS.map((command) => command.path);
1338
- const matches2 = candidates.filter((candidate) => candidate.startsWith(trimmed));
1339
- return [matches2.length > 0 ? matches2 : candidates, line];
1340
- }
1341
- if (trimmed.startsWith("?")) {
1342
- return [["?"], line];
1343
- }
1344
- const { prefix, mode, fragment } = completionTarget(line, getMode());
1345
- const paths = commandPathsForMode(mode);
1346
- const targetCandidates = paths.map((path) => `${prefix}${path}`);
1347
- if (!prefix.trim()) targetCandidates.unshift("cloud ", "local ");
1348
- const normalizedLine = line.trimStart().toLowerCase();
1349
- const normalizedFragment = fragment.toLowerCase();
1350
- const matches = targetCandidates.filter((candidate) => {
1351
- if (prefix.trim()) return candidate.toLowerCase().startsWith(`${prefix.toLowerCase()}${normalizedFragment}`);
1352
- return candidate.toLowerCase().startsWith(normalizedLine);
1961
+ function printSuccess(message) {
1962
+ console.log(chalk3.green("\u2713") + " " + message);
1963
+ }
1964
+ function printWarning(message) {
1965
+ console.log(chalk3.yellow("!") + " " + message);
1966
+ }
1967
+ function printError(message) {
1968
+ console.error(chalk3.red("\u2717") + " " + message);
1969
+ }
1970
+ function reportError(err) {
1971
+ const isApi = err instanceof ApiError;
1972
+ const message = err?.message || String(err);
1973
+ if (jsonMode) {
1974
+ printJson({
1975
+ error: {
1976
+ status: isApi ? err.status : null,
1977
+ code: isApi ? err.code : "CLI_ERROR",
1978
+ message
1979
+ }
1353
1980
  });
1354
- return [matches.length > 0 ? matches : targetCandidates, line];
1981
+ } else if (isApi) {
1982
+ printError(`[${err.status}] ${message}`);
1983
+ } else {
1984
+ printError(message);
1985
+ }
1986
+ }
1987
+ function formatDate(iso) {
1988
+ if (!iso) return "\u2014";
1989
+ return new Date(iso).toLocaleString();
1990
+ }
1991
+ function formatStatus(status2) {
1992
+ const map = {
1993
+ completed: chalk3.green,
1994
+ running: chalk3.blue,
1995
+ training: chalk3.blue,
1996
+ evaluating: chalk3.blue,
1997
+ preparing: chalk3.yellow,
1998
+ pending: chalk3.yellow,
1999
+ uploading: chalk3.yellow,
2000
+ failed: chalk3.red,
2001
+ cancelled: chalk3.dim,
2002
+ validated: chalk3.green,
2003
+ invalid: chalk3.red
1355
2004
  };
2005
+ const colorFn = map[status2] || chalk3.white;
2006
+ return colorFn(status2);
2007
+ }
2008
+ function truncate(str, max) {
2009
+ if (str.length <= max) return str;
2010
+ return str.slice(0, max - 1) + "\u2026";
2011
+ }
2012
+ function shortId(id) {
2013
+ return id.slice(0, 8);
1356
2014
  }
1357
2015
 
1358
- // src/shell-context.ts
1359
- import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
1360
- import { homedir as homedir2 } from "os";
1361
- import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
1362
- async function readJsonObject(path) {
1363
- try {
1364
- const parsed = JSON.parse(await readFile2(path, "utf8"));
1365
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1366
- return { found: true, invalid: true };
1367
- }
1368
- return {
1369
- found: true,
1370
- value: parsed,
1371
- invalid: false
1372
- };
1373
- } catch (error) {
1374
- if (error.code === "ENOENT") {
1375
- return { found: false, invalid: false };
1376
- }
1377
- return { found: true, invalid: true };
2016
+ // src/local-output.ts
2017
+ function isRecord2(value) {
2018
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2019
+ }
2020
+ function text(value) {
2021
+ if (typeof value === "string") return value;
2022
+ if (typeof value === "number" || typeof value === "boolean") {
2023
+ return String(value);
1378
2024
  }
2025
+ return void 0;
1379
2026
  }
1380
- function stringField(value, key) {
1381
- const field = value?.[key];
1382
- return typeof field === "string" && field.trim() ? field : void 0;
2027
+ function number(value) {
2028
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2029
+ }
2030
+ function record2(value) {
2031
+ return isRecord2(value) ? value : void 0;
2032
+ }
2033
+ function rows(value) {
2034
+ return Array.isArray(value) ? value.filter(isRecord2) : [];
2035
+ }
2036
+ function displayId(value) {
2037
+ const id = text(value);
2038
+ return id ? shortId(id) : "\u2014";
2039
+ }
2040
+ function displayNumber(value, digits = 4) {
2041
+ const parsed = number(value);
2042
+ if (parsed === void 0) return void 0;
2043
+ return parsed.toFixed(digits).replace(/\.?0+$/, "");
2044
+ }
2045
+ function displayBytes(value) {
2046
+ const bytes = number(value);
2047
+ if (bytes === void 0 || bytes < 0) return void 0;
2048
+ const units = ["B", "KiB", "MiB", "GiB", "TiB"];
2049
+ let amount = bytes;
2050
+ let unit = 0;
2051
+ while (amount >= 1024 && unit < units.length - 1) {
2052
+ amount /= 1024;
2053
+ unit += 1;
2054
+ }
2055
+ const precision = amount >= 10 || unit === 0 ? 0 : 1;
2056
+ return `${amount.toFixed(precision)} ${units[unit]}`;
2057
+ }
2058
+ function displayCommand(value) {
2059
+ if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
2060
+ return void 0;
2061
+ }
2062
+ return value.map(
2063
+ (part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
2064
+ ).join(" ");
2065
+ }
2066
+ function status(value) {
2067
+ const parsed = text(value) ?? "unknown";
2068
+ return formatStatus(parsed);
2069
+ }
2070
+ function gateFrom(value) {
2071
+ return record2(record2(value)?.general_regression);
2072
+ }
2073
+ function renderGate(value) {
2074
+ const gate = gateFrom(value);
2075
+ if (!gate) {
2076
+ printWarning(
2077
+ "General regression was not run; this model cannot be activated yet."
2078
+ );
2079
+ return;
2080
+ }
2081
+ if (gate.passed === true) {
2082
+ printSuccess("General regression gate passed");
2083
+ return;
2084
+ }
2085
+ printWarning(
2086
+ "The local run completed, but the general regression gate failed."
2087
+ );
2088
+ const failures = Array.isArray(gate.failures) ? gate.failures.filter(
2089
+ (failure) => typeof failure === "string"
2090
+ ) : [];
2091
+ for (const failure of failures) printWarning(failure);
2092
+ }
2093
+ function renderDoctor(value) {
2094
+ const checks = rows(value.checks);
2095
+ printTable(
2096
+ ["Check", "Status", "Details"],
2097
+ checks.map((check) => [
2098
+ text(check.name) ?? "unknown",
2099
+ check.ok === true ? formatStatus("completed") : formatStatus("failed"),
2100
+ truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
2101
+ ])
2102
+ );
2103
+ if (value.ok === true) {
2104
+ printSuccess("Local environment is ready");
2105
+ } else {
2106
+ printError("Local environment needs attention");
2107
+ }
2108
+ if (value.config_path) {
2109
+ printDetail([["Config", text(value.config_path)]]);
2110
+ }
2111
+ }
2112
+ function renderInit(value) {
2113
+ printSuccess("Created local Tuned Tensor project");
2114
+ printDetail([
2115
+ ["Path", text(value.path)],
2116
+ ["Name", text(value.name)],
2117
+ ["ID", text(value.id)],
2118
+ ["Base model", text(value.base_model)],
2119
+ ["Config", text(value.config_path)]
2120
+ ]);
1383
2121
  }
1384
- function expandPath(value, baseDirectory, homeDirectory) {
1385
- if (value === "~") return homeDirectory;
1386
- if (value.startsWith("~/")) return resolve2(homeDirectory, value.slice(2));
1387
- return isAbsolute2(value) ? resolve2(value) : resolve2(baseDirectory, value);
2122
+ function renderValidate(value) {
2123
+ printSuccess("Local behavior spec is valid");
2124
+ printDetail([
2125
+ ["Input", text(value.input_path)],
2126
+ ["Config", text(value.config_path)],
2127
+ ["Spec", text(value.behavior_spec_id)],
2128
+ ["Base model", text(value.base_model)],
2129
+ ["Dataset", text(value.dataset_format)],
2130
+ ["Artifacts", text(value.artifact_root)],
2131
+ ["Store", text(value.store_root)],
2132
+ ["Dry run", text(value.dry_run)]
2133
+ ]);
1388
2134
  }
1389
- function environmentHome(env) {
1390
- return env.HOME ? resolve2(env.HOME) : homedir2();
2135
+ function renderRun(value) {
2136
+ const runStatus = text(value.status) ?? "unknown";
2137
+ if (runStatus === "completed") {
2138
+ printSuccess("Local run completed");
2139
+ } else {
2140
+ printWarning(`Local run finished with status ${runStatus}`);
2141
+ }
2142
+ const comparison = record2(value.comparison);
2143
+ printDetail([
2144
+ ["Run", text(value.run_id)],
2145
+ ["Status", status(runStatus)],
2146
+ ["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
2147
+ ["Report", text(value.report_path)],
2148
+ ["Artifacts", text(value.artifact_dir)],
2149
+ ["Score delta", displayNumber(comparison?.avg_score_delta)],
2150
+ ["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
2151
+ ]);
2152
+ renderGate(value);
1391
2153
  }
1392
- function cloudConfigPath(env, homeDirectory) {
1393
- const configHome = env.XDG_CONFIG_HOME ? resolve2(env.XDG_CONFIG_HOME) : join3(homeDirectory, ".config");
1394
- return join3(configHome, "tuned-tensor", "config.json");
2154
+ function renderRunsList(value) {
2155
+ const runRows = rows(value);
2156
+ if (runRows.length === 0) {
2157
+ printWarning("No local runs found.");
2158
+ return;
2159
+ }
2160
+ printTable(
2161
+ ["Run", "Spec", "Status", "Stage", "Model", "Updated"],
2162
+ runRows.map((run) => [
2163
+ displayId(run.id),
2164
+ truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
2165
+ status(run.status),
2166
+ text(run.current_stage) ?? "\u2014",
2167
+ displayId(run.model_id),
2168
+ formatDate(text(run.updated_at))
2169
+ ])
2170
+ );
1395
2171
  }
1396
- function redactApiKey(key) {
1397
- if (!key) return void 0;
1398
- if (key.length <= 4) return "\u2026";
1399
- const prefixLength = key.length > 8 ? 8 : Math.max(1, Math.floor(key.length / 2));
1400
- return `${key.slice(0, prefixLength)}\u2026`;
2172
+ function renderRunEvents(value) {
2173
+ const eventRows = rows(value);
2174
+ if (eventRows.length === 0) {
2175
+ printWarning("No local run events found.");
2176
+ return;
2177
+ }
2178
+ printTable(
2179
+ ["Time", "Stage", "Status", "Message"],
2180
+ eventRows.map((event) => [
2181
+ formatDate(text(event.occurred_at)),
2182
+ text(event.stage) ?? "\u2014",
2183
+ status(event.status),
2184
+ truncate(text(event.message) ?? "\u2014", 80)
2185
+ ])
2186
+ );
1401
2187
  }
1402
- function targetFromEnvironment(env) {
1403
- const value = env.TT_TARGET?.trim().toLowerCase();
1404
- return value === "cloud" || value === "local" ? value : void 0;
2188
+ function renderRunRecord(value) {
2189
+ printDetail([
2190
+ ["Run", text(value.id) ?? text(value.run_id)],
2191
+ ["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
2192
+ ["Status", status(value.status)],
2193
+ ["Stage", text(value.current_stage)],
2194
+ ["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
2195
+ ["Base model", text(value.base_model)],
2196
+ ["Report", text(value.report_path)],
2197
+ ["Artifacts", text(value.artifact_dir)],
2198
+ ["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
2199
+ ]);
1405
2200
  }
1406
- async function adjacentFile(path) {
1407
- try {
1408
- return (await stat2(path)).isFile();
1409
- } catch {
1410
- return false;
1411
- }
2201
+ function renderRunReport(value) {
2202
+ const baseline = record2(value.baseline);
2203
+ const candidate = record2(value.candidate);
2204
+ const comparison = record2(value.comparison);
2205
+ const metadata = record2(value.run_metadata);
2206
+ printDetail([
2207
+ ["Run", text(value.run_id)],
2208
+ ["Status", status(value.status)],
2209
+ ["Base model", text(value.base_model)],
2210
+ ["Tuned model", text(value.fine_tuned_model_id)],
2211
+ ["Training examples", text(metadata?.training_example_count)],
2212
+ ["Evaluation examples", text(metadata?.eval_examples_used)],
2213
+ ["Baseline score", displayNumber(baseline?.avg_score)],
2214
+ ["Candidate score", displayNumber(candidate?.avg_score)],
2215
+ ["Score delta", displayNumber(comparison?.avg_score_delta)],
2216
+ ["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
2217
+ ["Regressions", text(comparison?.regressions)],
2218
+ ["Improvements", text(comparison?.improvements)]
2219
+ ]);
2220
+ renderGate(value);
1412
2221
  }
1413
- async function readActiveModelId(storeRoot) {
1414
- const result = await readJsonObject(join3(storeRoot, "active-model.json"));
1415
- if (!result.found || result.invalid) return void 0;
1416
- const modelId = result.value?.model_id;
1417
- if (modelId === null) return "base";
1418
- return typeof modelId === "string" && modelId ? modelId : void 0;
2222
+ function renderRunComparison(value) {
2223
+ const runA = record2(value.run_a);
2224
+ const runB = record2(value.run_b);
2225
+ const shared = record2(value.shared);
2226
+ const sharedA = record2(shared?.run_a);
2227
+ const sharedB = record2(shared?.run_b);
2228
+ printTable(
2229
+ ["Run", "Candidate score", "Token F1", "Examples"],
2230
+ [
2231
+ [
2232
+ displayId(runA?.run_id),
2233
+ displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
2234
+ displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
2235
+ text(shared?.examples) ?? "0"
2236
+ ],
2237
+ [
2238
+ displayId(runB?.run_id),
2239
+ displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
2240
+ displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
2241
+ text(shared?.examples) ?? "0"
2242
+ ]
2243
+ ]
2244
+ );
2245
+ const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
2246
+ for (const note of notes) printWarning(note);
1419
2247
  }
1420
- async function readLatestRun(storeRoot) {
1421
- const runsRoot = join3(storeRoot, "runs");
1422
- let names;
1423
- try {
1424
- names = (await readdir(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1425
- } catch {
1426
- return void 0;
2248
+ function renderModelsList(value) {
2249
+ const modelRows = rows(value);
2250
+ if (modelRows.length === 0) {
2251
+ printWarning("No local models found.");
2252
+ return;
1427
2253
  }
1428
- const states = await Promise.all(names.map(async (name) => {
1429
- const result = await readJsonObject(join3(runsRoot, name, "state.json"));
1430
- if (!result.found || result.invalid || !result.value) return void 0;
1431
- const id = stringField(result.value, "id") ?? name;
1432
- const state = {
1433
- id,
1434
- status: stringField(result.value, "status"),
1435
- specName: stringField(result.value, "spec_name"),
1436
- updatedAt: stringField(result.value, "updated_at")
1437
- };
1438
- return state;
1439
- }));
1440
- return states.filter((state) => Boolean(state)).sort((left, right) => {
1441
- const leftUpdated = left.updatedAt ?? "";
1442
- const rightUpdated = right.updatedAt ?? "";
1443
- if (leftUpdated !== rightUpdated) {
1444
- return rightUpdated.localeCompare(leftUpdated);
1445
- }
1446
- return right.id.localeCompare(left.id);
1447
- })[0];
2254
+ printTable(
2255
+ ["Model", "Base model", "Run", "Provider", "Created"],
2256
+ modelRows.map((model) => [
2257
+ displayId(model.id),
2258
+ truncate(text(model.base_model) ?? "\u2014", 32),
2259
+ displayId(model.run_id),
2260
+ text(model.provider) ?? "\u2014",
2261
+ formatDate(text(model.created_at))
2262
+ ])
2263
+ );
1448
2264
  }
1449
- async function discoverShellContext(options = {}) {
1450
- const env = options.env ?? process.env;
1451
- const cwd = resolve2(options.cwd ?? process.cwd());
1452
- const homeDirectory = environmentHome(env);
1453
- const warnings = [];
1454
- const specPath = join3(cwd, "tunedtensor.json");
1455
- const specJson = await readJsonObject(specPath);
1456
- let spec;
1457
- if (specJson.found) {
1458
- if (specJson.invalid || !specJson.value) {
1459
- spec = { path: specPath, parseError: true };
1460
- warnings.push("tunedtensor.json could not be parsed.");
1461
- } else {
1462
- const examples = specJson.value.examples;
1463
- spec = {
1464
- path: specPath,
1465
- id: stringField(specJson.value, "id"),
1466
- name: stringField(specJson.value, "name"),
1467
- baseModel: stringField(specJson.value, "base_model"),
1468
- exampleCount: Array.isArray(examples) ? examples.length : void 0,
1469
- parseError: false
1470
- };
1471
- }
1472
- }
1473
- const localConfigPath = join3(cwd, "local-runner.json");
1474
- const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
1475
- const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
1476
- if (localConfigJson.invalid) {
1477
- warnings.push("local-runner.json could not be parsed.");
1478
- }
1479
- const localConfigDirectory = hasAdjacentLocalConfig ? dirname2(localConfigPath) : cwd;
1480
- const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
1481
- const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
1482
- const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve2(cwd, ".tt-local", "artifacts");
1483
- const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join3(homeDirectory, ".tuned-tensor-local");
1484
- const cloudPath = cloudConfigPath(env, homeDirectory);
1485
- const cloudConfigJson = await readJsonObject(cloudPath);
1486
- if (cloudConfigJson.invalid) {
1487
- warnings.push("The Tuned Tensor cloud config could not be parsed.");
1488
- }
1489
- const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
1490
- const storedApiKey = stringField(cloudConfigJson.value, "api_key");
1491
- const apiKey = environmentApiKey || storedApiKey;
1492
- const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
1493
- const environmentTarget = targetFromEnvironment(env);
1494
- if (env.TT_TARGET && !environmentTarget) {
1495
- warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
1496
- }
1497
- const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
1498
- const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
1499
- const [activeModelId, latestRun] = await Promise.all([
1500
- readActiveModelId(storeRoot),
1501
- readLatestRun(storeRoot)
1502
- ]);
1503
- return {
1504
- cwd,
1505
- projectName: basename3(cwd) || cwd,
1506
- inferredTarget,
1507
- targetSource,
1508
- spec,
1509
- cloud: {
1510
- authenticated: Boolean(apiKey),
1511
- keyPrefix: redactApiKey(apiKey),
1512
- baseUrl,
1513
- configPath: cloudPath,
1514
- configFound: cloudConfigJson.found
1515
- },
1516
- local: {
1517
- configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
1518
- artifactRoot,
1519
- storeRoot,
1520
- activeModelId,
1521
- latestRun
1522
- },
1523
- warnings
1524
- };
2265
+ function renderModelRecord(value) {
2266
+ const model = record2(value.model) ?? value;
2267
+ const artifact = record2(value.artifact);
2268
+ const integrity = record2(value.integrity);
2269
+ printDetail([
2270
+ ["Model", text(model.id) ?? text(value.active)],
2271
+ ["Base model", text(model.base_model)],
2272
+ ["Run", text(model.run_id)],
2273
+ ["Provider", text(model.provider)],
2274
+ ["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
2275
+ ["Manifest", text(value.manifest_path)],
2276
+ ["Verified files", text(integrity?.checked)],
2277
+ ["Created", formatDate(text(model.created_at))]
2278
+ ]);
1525
2279
  }
1526
- function valueOrDash(value) {
1527
- return value === void 0 || value === "" ? "\u2014" : String(value);
2280
+ function renderModelPrefetch(value) {
2281
+ printSuccess(
2282
+ value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
2283
+ );
2284
+ printDetail([
2285
+ ["Base model", text(value.base_model)],
2286
+ ["Revision", text(value.snapshot_revision)],
2287
+ ["Snapshot", text(value.snapshot_path)],
2288
+ ["Files", text(value.file_count)],
2289
+ ["Size", displayBytes(value.size_bytes)],
2290
+ ["Cache", text(value.model_cache) ?? text(value.hf_home)]
2291
+ ]);
1528
2292
  }
1529
- function targetSourceLabel(source) {
1530
- switch (source) {
1531
- case "environment":
1532
- return "TT_TARGET";
1533
- case "adjacent-config":
1534
- return "adjacent local-runner.json";
1535
- default:
1536
- return "default";
2293
+ function renderActiveModel(value) {
2294
+ const active = text(value.active) ?? "base";
2295
+ const pointer = record2(value.pointer);
2296
+ if (pointer?.action === "activate") {
2297
+ printSuccess(`Activated local model ${active}`);
2298
+ } else if (pointer?.action === "rollback") {
2299
+ printSuccess(`Rolled back to ${active}`);
1537
2300
  }
2301
+ printDetail([
2302
+ ["Active", active],
2303
+ ["Run", text(pointer?.run_id)],
2304
+ ["Previous", text(pointer?.previous_model_id) ?? "base"],
2305
+ ["Changed", formatDate(text(pointer?.activated_at))],
2306
+ ["Manifest", text(value.manifest_path)]
2307
+ ]);
1538
2308
  }
1539
- function formatShellContext(context, selectedTarget, targetSource) {
1540
- const specLabel = context.spec ? context.spec.parseError ? `${context.spec.path} (invalid JSON)` : context.spec.path : "not found";
1541
- const sourceLabel = targetSource === "session" ? "session" : targetSource === "command-option" ? "--target" : targetSourceLabel(targetSource);
1542
- const lines = [
1543
- `Target ${selectedTarget} (${sourceLabel})`,
1544
- `Project ${context.projectName}`,
1545
- `Directory ${context.cwd}`,
1546
- `Spec ${specLabel}`,
1547
- `Spec ID ${valueOrDash(context.spec?.id)}`,
1548
- `Base model ${valueOrDash(context.spec?.baseModel)}`,
1549
- `Examples ${valueOrDash(context.spec?.exampleCount)}`,
1550
- `Cloud endpoint ${context.cloud.baseUrl}`,
1551
- `Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
1552
- `Local config ${context.local.configPath ?? "not found"}`,
1553
- `Artifact root ${context.local.artifactRoot}`,
1554
- `Store root ${context.local.storeRoot}`
1555
- ];
1556
- for (const warning of context.warnings) lines.push(`Warning ${warning}`);
1557
- return lines;
1558
- }
1559
- function formatShellStatus(context, selectedTarget) {
1560
- const lines = [
1561
- `Workflow ${selectedTarget}`,
1562
- `Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
1563
- ];
1564
- if (selectedTarget === "cloud") {
1565
- lines.push(
1566
- `Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
1567
- `Endpoint ${context.cloud.baseUrl}`,
1568
- "Remote status not queried"
1569
- );
1570
- } else {
1571
- lines.push(
1572
- `Local config ${context.local.configPath ?? "not found"}`,
1573
- `Active model ${context.local.activeModelId ?? "base"}`,
1574
- `Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
1575
- "Host checks not run (use doctor)"
1576
- );
1577
- }
1578
- return lines;
2309
+ function renderServePlan(value) {
2310
+ printSuccess("Local serving plan is valid");
2311
+ printDetail([
2312
+ ["Model", text(value.model_id) ?? text(value.base_model)],
2313
+ ["URL", text(value.url)],
2314
+ ["Artifact", text(value.artifact_path)],
2315
+ ["Manifest", text(value.manifest_path)],
2316
+ ["Command", displayCommand(value.command)]
2317
+ ]);
1579
2318
  }
1580
-
1581
- // src/shell.ts
1582
- var ShellParseError = class extends Error {
1583
- constructor(message) {
1584
- super(message);
1585
- this.name = "ShellParseError";
2319
+ function renderKnownJson(args, value) {
2320
+ const command = args[0];
2321
+ const subcommand = args[1];
2322
+ if (command === "doctor" && isRecord2(value)) {
2323
+ renderDoctor(value);
2324
+ return true;
1586
2325
  }
1587
- };
1588
- function operatorError(operator) {
1589
- return new ShellParseError(
1590
- `Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
1591
- );
1592
- }
1593
- function tokenizeShellInput(input) {
1594
- if (input.includes("\0")) {
1595
- throw new ShellParseError("Command input must not contain NUL bytes.");
2326
+ if (command === "init" && isRecord2(value)) {
2327
+ renderInit(value);
2328
+ return true;
1596
2329
  }
1597
- if (/[\r\n]/.test(input)) {
1598
- throw new ShellParseError("Enter one command at a time.");
2330
+ if (command === "validate" && isRecord2(value)) {
2331
+ renderValidate(value);
2332
+ return true;
1599
2333
  }
1600
- if (input.trimStart().startsWith("!")) {
1601
- throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
2334
+ if (command === "run" && isRecord2(value)) {
2335
+ renderRun(value);
2336
+ return true;
1602
2337
  }
1603
- const tokens = [];
1604
- let token = "";
1605
- let tokenStarted = false;
1606
- let quote = "none";
1607
- const finishToken = () => {
1608
- if (!tokenStarted) return;
1609
- tokens.push(token);
1610
- token = "";
1611
- tokenStarted = false;
1612
- };
1613
- for (let index = 0; index < input.length; index += 1) {
1614
- const character = input[index];
1615
- if (quote === "single") {
1616
- if (character === "'") {
1617
- quote = "none";
1618
- } else {
1619
- token += character;
1620
- }
1621
- continue;
1622
- }
1623
- if (quote === "double") {
1624
- if (character === '"') {
1625
- quote = "none";
1626
- continue;
1627
- }
1628
- if (character === "\\") {
1629
- const escaped = input[index + 1];
1630
- if (escaped === void 0) {
1631
- throw new ShellParseError("Command ends with an unfinished escape.");
1632
- }
1633
- token += escaped;
1634
- tokenStarted = true;
1635
- index += 1;
1636
- continue;
1637
- }
1638
- if (character === "`") throw operatorError("`\u2026`");
1639
- if (character === "$" && input[index + 1] === "(") {
1640
- throw operatorError("$(\u2026)");
1641
- }
1642
- token += character;
1643
- tokenStarted = true;
1644
- continue;
1645
- }
1646
- if (/\s/.test(character)) {
1647
- finishToken();
1648
- continue;
1649
- }
1650
- if (character === "'") {
1651
- quote = "single";
1652
- tokenStarted = true;
1653
- continue;
1654
- }
1655
- if (character === '"') {
1656
- quote = "double";
1657
- tokenStarted = true;
1658
- continue;
1659
- }
1660
- if (character === "\\") {
1661
- const escaped = input[index + 1];
1662
- if (escaped === void 0) {
1663
- throw new ShellParseError("Command ends with an unfinished escape.");
1664
- }
1665
- token += escaped;
1666
- tokenStarted = true;
1667
- index += 1;
1668
- continue;
2338
+ if (command === "runs") {
2339
+ if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
2340
+ renderRunsList(value);
2341
+ return true;
1669
2342
  }
1670
- if (character === "`") throw operatorError("`\u2026`");
1671
- if (character === "$" && input[index + 1] === "(") {
1672
- throw operatorError("$(\u2026)");
2343
+ if (subcommand === "events" && Array.isArray(value)) {
2344
+ renderRunEvents(value);
2345
+ return true;
1673
2346
  }
1674
- if (character === "|" || character === ">" || character === "<" || character === ";" || character === "&") {
1675
- const doubled = input[index + 1] === character && (character === "|" || character === "&");
1676
- throw operatorError(doubled ? character.repeat(2) : character);
2347
+ if (subcommand === "get" && isRecord2(value)) {
2348
+ renderRunRecord(value);
2349
+ return true;
1677
2350
  }
1678
- token += character;
1679
- tokenStarted = true;
1680
- }
1681
- if (quote !== "none") {
1682
- throw new ShellParseError(
1683
- `Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
1684
- );
1685
- }
1686
- finishToken();
1687
- return tokens;
1688
- }
1689
- function routeShellCommand(input, activeMode) {
1690
- let args = tokenizeShellInput(input);
1691
- if (args.length === 0) return null;
1692
- if (args[0] === "tt") {
1693
- args = args.slice(1);
1694
- if (args.length === 0) {
1695
- throw new ShellParseError("The TT shell is already open; enter a command such as `runs list`.");
2351
+ if (subcommand === "report" && isRecord2(value)) {
2352
+ renderRunReport(value);
2353
+ return true;
1696
2354
  }
1697
- }
1698
- if (args[0]?.startsWith("/")) {
1699
- throw new ShellParseError("Slash commands are session commands and cannot be routed to a workflow.");
1700
- }
1701
- const override = args[0];
1702
- if (override === "cloud" || override === "local") {
1703
- if (args.length === 1) {
1704
- throw new ShellParseError(`Add a ${override} command, or use \`/mode ${override}\` to switch workflows.`);
2355
+ if (subcommand === "compare" && isRecord2(value)) {
2356
+ renderRunComparison(value);
2357
+ return true;
1705
2358
  }
1706
- return { target: override, args: args.slice(1) };
1707
- }
1708
- return { target: activeMode, args };
1709
- }
1710
- var SLASH_NAMES = /* @__PURE__ */ new Set([
1711
- "help",
1712
- "status",
1713
- "context",
1714
- "mode",
1715
- "model",
1716
- "clear",
1717
- "cd",
1718
- "exit"
1719
- ]);
1720
- var WORKFLOW_ROOT_COMMANDS = new Set(
1721
- COMMAND_CATALOG.map((command) => command.path.split(" ", 1)[0])
1722
- );
1723
- function editDistance(left, right) {
1724
- const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
1725
- for (let i = 1; i <= left.length; i += 1) {
1726
- let diagonal = previous[0];
1727
- previous[0] = i;
1728
- for (let j = 1; j <= right.length; j += 1) {
1729
- const above = previous[j];
1730
- previous[j] = Math.min(
1731
- previous[j] + 1,
1732
- previous[j - 1] + 1,
1733
- diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
1734
- );
1735
- diagonal = above;
2359
+ }
2360
+ if (command === "models") {
2361
+ if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
2362
+ renderModelsList(value);
2363
+ return true;
2364
+ }
2365
+ if ((subcommand === "get" || subcommand === "verify") && isRecord2(value)) {
2366
+ if (subcommand === "verify") printSuccess("Local model artifact is valid");
2367
+ renderModelRecord(value);
2368
+ return true;
2369
+ }
2370
+ if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord2(value)) {
2371
+ renderModelPrefetch(value);
2372
+ return true;
2373
+ }
2374
+ if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord2(value)) {
2375
+ renderActiveModel(value);
2376
+ return true;
2377
+ }
2378
+ if (subcommand === "serve" && isRecord2(value)) {
2379
+ renderServePlan(value);
2380
+ return true;
1736
2381
  }
1737
2382
  }
1738
- return previous[right.length];
2383
+ if (command === "serve" && isRecord2(value)) {
2384
+ renderServePlan(value);
2385
+ return true;
2386
+ }
2387
+ return false;
1739
2388
  }
1740
- function suggestSlashCommands(rawName) {
1741
- const names = [...SLASH_NAMES].sort();
1742
- const prefixMatches = names.filter((name) => name.startsWith(rawName));
1743
- if (prefixMatches.length > 0) return prefixMatches;
1744
- return names.filter((name) => editDistance(name, rawName) <= 2);
2389
+ function lastNonEmptyLine(value) {
2390
+ return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
1745
2391
  }
1746
- function unknownSlashCommandError(rawName) {
1747
- if (WORKFLOW_ROOT_COMMANDS.has(rawName)) {
1748
- return new ShellParseError(
1749
- `Unknown session command: /${rawName}. Workflow commands need no slash \u2014 try "${rawName} --help".`
1750
- );
1751
- }
1752
- const suggestions = suggestSlashCommands(rawName);
1753
- const hint = suggestions.length > 0 ? ` Did you mean ${suggestions.map((name) => `/${name}`).join(" or ")}?` : "";
1754
- return new ShellParseError(
1755
- `Unknown session command: /${rawName}.${hint} Use /help to see available commands.`
1756
- );
2392
+ function adaptLocalCliText(value) {
2393
+ return value.replace(
2394
+ /(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
2395
+ "tt local"
2396
+ ).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
1757
2397
  }
1758
- function parseSlashCommand(input) {
1759
- const trimmed = input.trim();
1760
- if (!trimmed) return null;
1761
- if (trimmed === "?") return { name: "help", args: [] };
1762
- if (trimmed.startsWith("? ")) {
1763
- return {
1764
- name: "help",
1765
- args: tokenizeShellInput(trimmed.slice(1))
1766
- };
1767
- }
1768
- if (!trimmed.startsWith("/")) return null;
1769
- if (trimmed === "/") return { name: "palette", args: [] };
1770
- const tokens = tokenizeShellInput(trimmed);
1771
- const rawName = tokens[0].slice(1).toLowerCase();
1772
- if (!SLASH_NAMES.has(rawName)) {
1773
- throw unknownSlashCommandError(rawName);
1774
- }
2398
+ function localCliErrorEnvelope(payload) {
2399
+ const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
1775
2400
  return {
1776
- name: rawName,
1777
- args: tokens.slice(1)
2401
+ error: {
2402
+ status: null,
2403
+ code: "LOCAL_CLI_ERROR",
2404
+ message: adaptLocalCliText(rawMessage),
2405
+ exit_code: payload.exitCode,
2406
+ signal: payload.signal
2407
+ }
1778
2408
  };
1779
2409
  }
1780
- function errorMessage(error) {
1781
- return error instanceof Error ? error.message : String(error);
1782
- }
1783
- var successMark = () => chalk2.green("\u2713");
1784
- var errorMark = () => chalk2.red("\u2717");
1785
- var accent = chalk2.hex("#8B5CF6");
1786
- var DETAIL_LABEL_WIDTH = 15;
1787
- function styleDetailLines(lines) {
1788
- return lines.map((line) => {
1789
- if (line.length <= DETAIL_LABEL_WIDTH) return line;
1790
- const label = line.slice(0, DETAIL_LABEL_WIDTH);
1791
- if (!label.trim()) return line;
1792
- const color = label.startsWith("Warning") ? chalk2.yellow : accent.bold;
1793
- return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
1794
- });
1795
- }
1796
- function detailLine(label, value) {
1797
- return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
1798
- }
1799
- function assertNoArgs(command, args) {
1800
- if (args.length > 0) {
1801
- throw new ShellParseError(`/${command} does not accept arguments.`);
2410
+ function localCliTextEnvelope(payload) {
2411
+ const output = adaptLocalCliText(payload.stdout).trim();
2412
+ if (payload.args[0] === "info") {
2413
+ const lines = output.split(/\r?\n/);
2414
+ const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
2415
+ const fields = Object.fromEntries(
2416
+ lines.slice(1).flatMap((line) => {
2417
+ const match = line.match(/^([^:]+):\s*(.*)$/);
2418
+ return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
2419
+ })
2420
+ );
2421
+ if (heading) {
2422
+ return {
2423
+ data: {
2424
+ name: heading[1],
2425
+ description: heading[2],
2426
+ ...fields
2427
+ }
2428
+ };
2429
+ }
1802
2430
  }
2431
+ return { data: { output } };
1803
2432
  }
1804
- function expandDirectory(value, cwd, env) {
1805
- const home = env.HOME ? resolve3(env.HOME) : homedir3();
1806
- if (value === "~") return home;
1807
- if (value.startsWith("~/")) return resolve3(home, value.slice(2));
1808
- return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
1809
- }
1810
- function helpText(mode, query, palette = false) {
1811
- const groups = groupedCatalog(mode, query);
1812
- const lines = [];
1813
- lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
1814
- if (groups.size === 0) {
1815
- lines.push(chalk2.dim(" No matching commands."));
1816
- } else {
1817
- for (const [group, commands] of groups) {
1818
- lines.push("");
1819
- lines.push(chalk2.bold(group));
1820
- for (const command of commands) {
1821
- lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
1822
- }
1823
- }
2433
+ function renderLocalOutput(payload, options = {}) {
2434
+ if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
2435
+ return;
1824
2436
  }
1825
- if (!query) {
1826
- lines.push("");
1827
- lines.push(chalk2.bold("Session"));
1828
- for (const command of SLASH_COMMANDS) {
1829
- lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
2437
+ const output = options.stdout ?? process.stdout;
2438
+ if (options.jsonMode) {
2439
+ if (payload.hasJson) {
2440
+ output.write(payload.stdout);
2441
+ return;
1830
2442
  }
1831
- lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
1832
- lines.push(chalk2.dim(
1833
- "\nTab completes commands. Shell operators and shell escapes are disabled."
1834
- ));
2443
+ if (payload.exitCode !== 0 || payload.errorMessage) {
2444
+ output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
2445
+ `);
2446
+ return;
2447
+ }
2448
+ output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
2449
+ `);
2450
+ return;
2451
+ }
2452
+ if (payload.droppedSpecKeys.length > 0) {
2453
+ printWarning(
2454
+ `Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
2455
+ );
2456
+ }
2457
+ if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
2458
+ if (payload.hasJson) {
2459
+ printJson(payload.json);
2460
+ return;
2461
+ }
2462
+ if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
2463
+ if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
2464
+ printError(
2465
+ payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
2466
+ );
1835
2467
  }
1836
- return `${lines.join("\n")}
1837
- `;
1838
- }
1839
- function activeModelLabel(snapshot) {
1840
- return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
1841
- }
1842
- function logoRows() {
1843
- const muted = chalk2.dim("\u2588\u2588");
1844
- return [
1845
- `${chalk2.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
1846
- `${muted} ${accent("\u2588\u2588")} ${muted}`,
1847
- `${muted} ${muted} ${chalk2.hex("#7C3AED")("\u2588\u2588")}`
1848
- ];
1849
2468
  }
1850
- function renderShellBanner(snapshot) {
1851
- const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
1852
- const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk2.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
1853
- const textRows = [
1854
- heading,
1855
- `${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
1856
- chalk2.dim("/help for commands \xB7 Tab completes \xB7 /mode cloud|local switches")
1857
- ];
1858
- const logo = logoRows();
1859
- const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
1860
- return `${lines.join("\n")}
1861
2469
 
1862
- `;
1863
- }
1864
- function renderShellPrompt(snapshot) {
1865
- return `${accent("tt")} ${chalk2.bold(snapshot.mode)} ${chalk2.dim(snapshot.context.projectName)} \u203A `;
2470
+ // src/local-runner.ts
2471
+ var localRequire = createRequire(import.meta.url);
2472
+ var DEFAULT_SPEC_NAME = "tunedtensor.json";
2473
+ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
2474
+ "--config",
2475
+ "--name",
2476
+ "--model",
2477
+ "--output",
2478
+ "--profile",
2479
+ "--host",
2480
+ "--port",
2481
+ "--device",
2482
+ "--max-tokens",
2483
+ "--temperature",
2484
+ "--top-p",
2485
+ "--max-concurrent-requests",
2486
+ "--spec",
2487
+ "--api-key-env"
2488
+ ]);
2489
+ function isOptionWithInlineValue(value) {
2490
+ const equals = value.indexOf("=");
2491
+ return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
1866
2492
  }
1867
- var TunedTensorShellSession = class _TunedTensorShellSession {
1868
- constructor(runner, io, env, contextProvider, version, initialContext) {
1869
- this.runner = runner;
1870
- this.io = io;
1871
- this.env = env;
1872
- this.contextProvider = contextProvider;
1873
- this.version = version;
1874
- this.mode = initialContext.inferredTarget;
1875
- this.modeSource = initialContext.targetSource;
1876
- this.cwd = initialContext.cwd;
1877
- this.context = initialContext;
2493
+ function positionalIndices(args, start) {
2494
+ const indices = [];
2495
+ let optionsEnded = false;
2496
+ for (let index = start; index < args.length; index += 1) {
2497
+ const value = args[index];
2498
+ if (!optionsEnded && value === "--") {
2499
+ optionsEnded = true;
2500
+ continue;
2501
+ }
2502
+ if (!optionsEnded && value.startsWith("-")) {
2503
+ if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
2504
+ continue;
2505
+ }
2506
+ indices.push(index);
1878
2507
  }
1879
- mode;
1880
- modeSource;
1881
- cwd;
1882
- context;
1883
- static async create(options) {
1884
- const env = options.env ?? process.env;
1885
- const cwd = resolve3(options.cwd ?? process.cwd());
1886
- const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
1887
- const initialContext = await contextProvider({ cwd, env });
1888
- return new _TunedTensorShellSession(
1889
- options.runner,
1890
- options.io,
1891
- env,
1892
- contextProvider,
1893
- options.version,
1894
- initialContext
1895
- );
2508
+ return indices;
2509
+ }
2510
+ function optionValueArgument(args, name) {
2511
+ for (let index = 0; index < args.length; index += 1) {
2512
+ const value = args[index];
2513
+ if (value === name) {
2514
+ const path = args[index + 1];
2515
+ if (!path) return null;
2516
+ return {
2517
+ path,
2518
+ replace(projectedPath) {
2519
+ args[index + 1] = projectedPath;
2520
+ }
2521
+ };
2522
+ }
2523
+ if (value.startsWith(`${name}=`)) {
2524
+ const path = value.slice(name.length + 1);
2525
+ if (!path) return null;
2526
+ return {
2527
+ path,
2528
+ replace(projectedPath) {
2529
+ args[index] = `${name}=${projectedPath}`;
2530
+ }
2531
+ };
2532
+ }
1896
2533
  }
1897
- snapshot() {
2534
+ return null;
2535
+ }
2536
+ function positionalSpecArgument(args, start, defaultPath) {
2537
+ const positional = positionalIndices(args, start)[0];
2538
+ if (positional !== void 0) {
1898
2539
  return {
1899
- mode: this.mode,
1900
- modeSource: this.modeSource,
1901
- cwd: this.cwd,
1902
- context: this.context,
1903
- version: this.version
2540
+ path: args[positional],
2541
+ replace(projectedPath) {
2542
+ args[positional] = projectedPath;
2543
+ }
1904
2544
  };
1905
2545
  }
1906
- prompt() {
1907
- return renderShellPrompt(this.snapshot());
1908
- }
1909
- banner() {
1910
- return renderShellBanner(this.snapshot());
1911
- }
1912
- async refreshContext() {
1913
- this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
1914
- this.cwd = this.context.cwd;
1915
- if (this.modeSource !== "session" && this.modeSource !== "environment") {
1916
- this.mode = this.context.inferredTarget;
1917
- this.modeSource = this.context.targetSource;
2546
+ return {
2547
+ path: defaultPath,
2548
+ replace(projectedPath) {
2549
+ args.splice(start, 0, projectedPath);
1918
2550
  }
2551
+ };
2552
+ }
2553
+ function selectedSpecArgument(args, cwd) {
2554
+ const command = args[0];
2555
+ if (["doctor", "validate", "run"].includes(command ?? "")) {
2556
+ return positionalSpecArgument(args, 1, join3(cwd, DEFAULT_SPEC_NAME));
1919
2557
  }
1920
- writeLines(lines) {
1921
- this.io.write(`${lines.join("\n")}
1922
- `);
2558
+ if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
2559
+ return positionalSpecArgument(args, 2, join3(cwd, DEFAULT_SPEC_NAME));
1923
2560
  }
1924
- modelLines() {
1925
- if (this.mode === "local") {
1926
- return styleDetailLines([
1927
- detailLine("Active model", this.context.local.activeModelId ?? "base"),
1928
- detailLine("Change", "/model <id> to activate a verified local model")
1929
- ]);
1930
- }
1931
- return styleDetailLines([
1932
- detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
1933
- detailLine("Change", "edit base_model in tunedtensor.json, then run push")
1934
- ]);
2561
+ const serves = command === "serve" || command === "models" && args[1] === "serve";
2562
+ if (!serves) return null;
2563
+ const explicit = optionValueArgument(args, "--spec");
2564
+ if (explicit) return explicit;
2565
+ return null;
2566
+ }
2567
+ async function pathIsFile(path) {
2568
+ return (await stat3(path).catch(() => null))?.isFile() ?? false;
2569
+ }
2570
+ function resolveFromCwd(path, cwd) {
2571
+ if (isAbsolute3(path)) return path;
2572
+ return resolve3(cwd, path);
2573
+ }
2574
+ async function projectSpecArguments(originalArgs, cwd) {
2575
+ const args = [...originalArgs];
2576
+ if (args.includes("--help") || args.includes("-h")) {
2577
+ return {
2578
+ args,
2579
+ droppedSpecKeys: [],
2580
+ pathReplacements: [],
2581
+ cleanup: async () => {
2582
+ }
2583
+ };
1935
2584
  }
1936
- async handleSlash(command) {
1937
- switch (command.name) {
1938
- case "palette":
1939
- this.io.write(helpText(this.mode, void 0, true));
1940
- return "continue";
1941
- case "help":
1942
- this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
1943
- return "continue";
1944
- case "status":
1945
- assertNoArgs("status", command.args);
1946
- await this.refreshContext();
1947
- this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
1948
- return "continue";
1949
- case "context":
1950
- assertNoArgs("context", command.args);
1951
- await this.refreshContext();
1952
- this.writeLines(styleDetailLines(
1953
- formatShellContext(this.context, this.mode, this.modeSource)
1954
- ));
1955
- return "continue";
1956
- case "mode": {
1957
- if (command.args.length === 0) {
1958
- this.io.write(`Current workflow: ${this.mode}
1959
- `);
1960
- return "continue";
1961
- }
1962
- if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
1963
- throw new ShellParseError("Usage: /mode cloud|local");
1964
- }
1965
- this.mode = command.args[0];
1966
- this.modeSource = "session";
1967
- this.io.write(`${successMark()} Workflow switched to ${this.mode}.
1968
- `);
1969
- return "continue";
2585
+ const selected = selectedSpecArgument(args, cwd);
2586
+ if (!selected) {
2587
+ return {
2588
+ args,
2589
+ droppedSpecKeys: [],
2590
+ pathReplacements: [],
2591
+ cleanup: async () => {
1970
2592
  }
1971
- case "model": {
1972
- if (command.args.length === 0) {
1973
- await this.refreshContext();
1974
- this.writeLines(this.modelLines());
1975
- return "continue";
1976
- }
1977
- if (command.args.length !== 1) {
1978
- throw new ShellParseError("Usage: /model [model-id]");
1979
- }
1980
- if (this.mode !== "local") {
1981
- throw new ShellParseError(
1982
- "Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
1983
- );
1984
- }
1985
- await this.runner({
1986
- target: "local",
1987
- args: ["models", "activate", command.args[0]],
1988
- cwd: this.cwd
1989
- });
1990
- await this.refreshContext();
1991
- return "continue";
2593
+ };
2594
+ }
2595
+ const sourcePath = resolveFromCwd(selected.path, cwd);
2596
+ if (!await pathIsFile(sourcePath)) {
2597
+ return {
2598
+ args,
2599
+ droppedSpecKeys: [],
2600
+ pathReplacements: [],
2601
+ cleanup: async () => {
1992
2602
  }
1993
- case "clear":
1994
- assertNoArgs("clear", command.args);
1995
- this.io.clear();
1996
- return "continue";
1997
- case "cd": {
1998
- if (command.args.length === 0) {
1999
- this.io.write(`${this.cwd}
2000
- `);
2001
- return "continue";
2002
- }
2003
- if (command.args.length !== 1) {
2004
- throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
2005
- }
2006
- const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
2007
- const metadata = await stat3(nextDirectory).catch(() => null);
2008
- if (!metadata?.isDirectory()) {
2009
- throw new ShellParseError(`Not a directory: ${nextDirectory}`);
2010
- }
2011
- this.cwd = nextDirectory;
2012
- await this.refreshContext();
2013
- this.io.write(`${successMark()} Directory: ${this.cwd}
2014
- `);
2015
- return "continue";
2603
+ };
2604
+ }
2605
+ const raw = JSON.parse(await readFile2(sourcePath, "utf8"));
2606
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
2607
+ throw new Error(`${sourcePath} must contain a JSON object.`);
2608
+ }
2609
+ const body = raw;
2610
+ const runRequestKeys = [
2611
+ "run_id",
2612
+ "behavior_spec_id",
2613
+ "run_number",
2614
+ "spec_snapshot"
2615
+ ].filter((key) => key in body);
2616
+ if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
2617
+ return {
2618
+ args,
2619
+ droppedSpecKeys: [],
2620
+ pathReplacements: [],
2621
+ cleanup: async () => {
2016
2622
  }
2017
- case "exit":
2018
- assertNoArgs("exit", command.args);
2019
- return "exit";
2020
- }
2623
+ };
2021
2624
  }
2022
- async handleLine(input) {
2023
- try {
2024
- if (/^(exit|quit)$/i.test(input.trim())) return "exit";
2025
- const slash = parseSlashCommand(input);
2026
- if (slash) return await this.handleSlash(slash);
2027
- const routed = routeShellCommand(input, this.mode);
2028
- if (!routed) return "continue";
2029
- await this.runner({
2030
- target: routed.target,
2031
- args: [...routed.args],
2032
- cwd: this.cwd
2033
- });
2034
- await this.refreshContext();
2035
- return "continue";
2036
- } catch (error) {
2037
- this.io.writeError(`${errorMark()} ${errorMessage(error)}
2038
- `);
2039
- return "continue";
2040
- }
2625
+ const projected = projectLocalSpec(body);
2626
+ if (projected.droppedKeys.length === 0) {
2627
+ return {
2628
+ args,
2629
+ droppedSpecKeys: [],
2630
+ pathReplacements: [],
2631
+ cleanup: async () => {
2632
+ }
2633
+ };
2041
2634
  }
2042
- };
2043
- async function createShellSession(options) {
2044
- return TunedTensorShellSession.create(options);
2635
+ const temporaryPath = join3(
2636
+ dirname2(sourcePath),
2637
+ `.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
2638
+ );
2639
+ await writeFile(
2640
+ temporaryPath,
2641
+ `${JSON.stringify(projected.body, null, 2)}
2642
+ `,
2643
+ { encoding: "utf8", flag: "wx", mode: 384 }
2644
+ );
2645
+ selected.replace(temporaryPath);
2646
+ return {
2647
+ args,
2648
+ droppedSpecKeys: projected.droppedKeys,
2649
+ pathReplacements: [{
2650
+ sourcePath,
2651
+ projectedPath: temporaryPath
2652
+ }],
2653
+ cleanup: () => rm(temporaryPath, { force: true })
2654
+ };
2045
2655
  }
2046
- function streamIsTTY(stream) {
2047
- return Boolean(stream.isTTY);
2656
+ function resolveLocalCliEntrypoint() {
2657
+ try {
2658
+ return localRequire.resolve("@tuned-tensor/local");
2659
+ } catch (error) {
2660
+ throw new Error(
2661
+ "TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
2662
+ { cause: error }
2663
+ );
2664
+ }
2048
2665
  }
2049
- async function withForegroundSignalHandoff(control, run) {
2050
- const signals = control.signals ?? process;
2051
- const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
2052
- const keepParentAlive = () => {
2053
- };
2054
- control.pauseReadline();
2055
- if (restoreRawMode) control.input.setRawMode(false);
2056
- signals.on("SIGINT", keepParentAlive);
2666
+ function localCommandStreamsStdout(args) {
2667
+ const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
2668
+ return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
2669
+ }
2670
+ function parseJsonOutput(stdout2) {
2671
+ if (!stdout2.trim()) return { hasJson: false, json: void 0 };
2057
2672
  try {
2058
- return await run();
2059
- } finally {
2060
- signals.off("SIGINT", keepParentAlive);
2061
- if (restoreRawMode) control.input.setRawMode(true);
2062
- control.resumeReadline();
2673
+ return { hasJson: true, json: JSON.parse(stdout2) };
2674
+ } catch {
2675
+ return { hasJson: false, json: void 0 };
2063
2676
  }
2064
2677
  }
2065
- function streamIO(output, error) {
2066
- return {
2067
- write(text2) {
2068
- output.write(text2);
2069
- },
2070
- writeError(text2) {
2071
- error.write(text2);
2072
- },
2073
- clear() {
2074
- output.write("\x1B[2J\x1B[H");
2075
- }
2076
- };
2678
+ function signalExitCode(signal) {
2679
+ if (!signal) return 1;
2680
+ const signalNumber = osConstants.signals[signal];
2681
+ return typeof signalNumber === "number" ? 128 + signalNumber : 1;
2077
2682
  }
2078
- async function startInteractiveShell(options) {
2079
- const input = options.input ?? process.stdin;
2080
- const output = options.output ?? process.stdout;
2081
- const error = options.error ?? process.stderr;
2082
- if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
2083
- throw new Error("The interactive TT shell requires a TTY.");
2683
+ function writeStream(stream, chunk) {
2684
+ stream.write(chunk);
2685
+ }
2686
+ function normalizeProjectedPaths(value, replacements) {
2687
+ let normalized = value;
2688
+ for (const replacement of replacements) {
2689
+ const encodedProjectedPath = JSON.stringify(
2690
+ replacement.projectedPath
2691
+ ).slice(1, -1);
2692
+ const encodedSourcePath = JSON.stringify(
2693
+ replacement.sourcePath
2694
+ ).slice(1, -1);
2695
+ normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
2084
2696
  }
2085
- let readline;
2086
- const terminalInput = input;
2087
- const foregroundRunner = (request2) => withForegroundSignalHandoff(
2697
+ return normalized;
2698
+ }
2699
+ async function spawnLocalCli(args) {
2700
+ const output = args.stdout ?? process.stdout;
2701
+ const errors = args.stderr ?? process.stderr;
2702
+ const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
2703
+ const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
2704
+ const child = spawn(
2705
+ args.nodeExecutable,
2706
+ [args.entrypoint, ...args.commandArgs],
2088
2707
  {
2089
- input: terminalInput,
2090
- pauseReadline: () => readline?.pause(),
2091
- resumeReadline: () => readline?.resume()
2092
- },
2093
- () => options.runner(request2)
2708
+ cwd: args.cwd,
2709
+ env: args.env,
2710
+ stdio: [
2711
+ pipeInput ? "pipe" : "inherit",
2712
+ args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
2713
+ "pipe"
2714
+ ],
2715
+ detached: process.platform !== "win32"
2716
+ }
2094
2717
  );
2095
- const session = await createShellSession({
2096
- runner: foregroundRunner,
2097
- io: streamIO(output, error),
2098
- cwd: options.cwd,
2099
- env: options.env,
2100
- version: options.version
2718
+ if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
2719
+ let stdout2 = "";
2720
+ let stderr = "";
2721
+ child.stdout?.setEncoding("utf8");
2722
+ child.stderr?.setEncoding("utf8");
2723
+ child.stdout?.on("data", (chunk) => {
2724
+ const normalized = normalizeProjectedPaths(
2725
+ chunk,
2726
+ args.pathReplacements
2727
+ );
2728
+ if (args.streamingStdout) writeStream(output, normalized);
2729
+ else stdout2 += normalized;
2101
2730
  });
2102
- const completer = createCommandCompleter(() => session.snapshot().mode);
2103
- readline = createInterface({
2104
- input,
2105
- output,
2106
- terminal: true,
2107
- historySize: 100,
2108
- removeHistoryDuplicates: true,
2109
- completer
2731
+ child.stderr?.on("data", (chunk) => {
2732
+ const normalized = normalizeProjectedPaths(
2733
+ chunk,
2734
+ args.pathReplacements
2735
+ );
2736
+ stderr += normalized;
2737
+ writeStream(
2738
+ errors,
2739
+ args.jsonMode ? normalized : adaptLocalCliText(normalized)
2740
+ );
2110
2741
  });
2111
- readline.on("SIGINT", () => {
2112
- output.write("\n");
2113
- readline?.setPrompt(session.prompt());
2114
- readline?.write(null, { ctrl: true, name: "u" });
2742
+ return new Promise((resolveChild) => {
2743
+ let spawnError;
2744
+ const forwardSignal = (signal) => {
2745
+ const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
2746
+ if (child.pid && process.platform !== "win32") {
2747
+ try {
2748
+ process.kill(-child.pid, childSignal);
2749
+ return;
2750
+ } catch {
2751
+ }
2752
+ }
2753
+ child.kill(childSignal);
2754
+ };
2755
+ const onSigint = () => forwardSignal("SIGINT");
2756
+ const onSigterm = () => forwardSignal("SIGTERM");
2757
+ const onSighup = () => forwardSignal("SIGHUP");
2758
+ const cleanup = () => {
2759
+ process.off("SIGINT", onSigint);
2760
+ process.off("SIGTERM", onSigterm);
2761
+ if (process.platform !== "win32") process.off("SIGHUP", onSighup);
2762
+ };
2763
+ process.on("SIGINT", onSigint);
2764
+ process.on("SIGTERM", onSigterm);
2765
+ if (process.platform !== "win32") process.on("SIGHUP", onSighup);
2766
+ child.once("error", (error) => {
2767
+ spawnError = error;
2768
+ });
2769
+ child.once("close", (code, signal) => {
2770
+ cleanup();
2771
+ resolveChild({
2772
+ exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
2773
+ signal,
2774
+ stdout: stdout2,
2775
+ stderr,
2776
+ ...spawnError ? { errorMessage: spawnError.message } : {}
2777
+ });
2778
+ });
2115
2779
  });
2116
- output.write(session.banner());
2117
- readline.setPrompt(session.prompt());
2118
- readline.prompt();
2780
+ }
2781
+ function setupFailure(originalArgs, error) {
2782
+ const message = error instanceof Error ? error.message : String(error);
2783
+ return {
2784
+ args: [...originalArgs],
2785
+ projectedArgs: [...originalArgs],
2786
+ entrypoint: null,
2787
+ exitCode: 1,
2788
+ signal: null,
2789
+ stdout: "",
2790
+ stderr: "",
2791
+ hasJson: false,
2792
+ json: void 0,
2793
+ streamingStdout: false,
2794
+ droppedSpecKeys: [],
2795
+ errorMessage: message
2796
+ };
2797
+ }
2798
+ async function runLocalCommand(args, options = {}) {
2799
+ const cwd = resolve3(options.cwd ?? process.cwd());
2800
+ let projected;
2119
2801
  try {
2120
- for await (const line of readline) {
2121
- const action = await session.handleLine(line);
2122
- if (action === "exit") {
2123
- readline.close();
2124
- break;
2125
- }
2126
- readline.setPrompt(session.prompt());
2127
- readline.prompt();
2128
- }
2802
+ const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
2803
+ projected = await projectSpecArguments(args, cwd);
2804
+ const streamingStdout = localCommandStreamsStdout(projected.args);
2805
+ const result = await spawnLocalCli({
2806
+ entrypoint,
2807
+ commandArgs: projected.args,
2808
+ cwd,
2809
+ env: options.env ?? process.env,
2810
+ nodeExecutable: options.nodeExecutable ?? process.execPath,
2811
+ streamingStdout,
2812
+ jsonMode: options.jsonMode ?? false,
2813
+ stdin: options.stdin,
2814
+ stdout: options.stdout,
2815
+ stderr: options.stderr,
2816
+ pathReplacements: projected.pathReplacements
2817
+ });
2818
+ const parsed = parseJsonOutput(result.stdout);
2819
+ return {
2820
+ args: [...args],
2821
+ projectedArgs: projected.args,
2822
+ entrypoint,
2823
+ ...result,
2824
+ ...parsed,
2825
+ streamingStdout,
2826
+ droppedSpecKeys: projected.droppedSpecKeys
2827
+ };
2828
+ } catch (error) {
2829
+ return setupFailure(args, error);
2129
2830
  } finally {
2130
- readline.close();
2831
+ await projected?.cleanup().catch(() => void 0);
2131
2832
  }
2132
2833
  }
2133
- function ciEnabled(value) {
2134
- if (!value) return false;
2135
- return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
2136
- }
2137
- function shouldStartInteractiveShell(options) {
2138
- const env = options.env ?? process.env;
2139
- return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
2834
+ async function executeLocalCommand(args, options = {}) {
2835
+ const result = await runLocalCommand(args, options);
2836
+ renderLocalOutput(result, {
2837
+ jsonMode: options.jsonMode,
2838
+ stdout: options.stdout
2839
+ });
2840
+ if (result.exitCode !== 0) process.exitCode = result.exitCode;
2841
+ return result;
2140
2842
  }
2141
2843
 
2142
2844
  // src/commands/auth.ts
2143
2845
  import { createInterface as createInterface2 } from "readline/promises";
2144
2846
  import { stdin, stdout } from "process";
2145
2847
  import { Writable } from "stream";
2146
- import chalk3 from "chalk";
2848
+ import chalk4 from "chalk";
2147
2849
  async function promptForApiKey(input = stdin, output = stdout) {
2148
2850
  if (input.isTTY !== true || output.isTTY !== true) {
2149
2851
  throw new Error(
@@ -2185,9 +2887,9 @@ function registerAuthCommands(parent) {
2185
2887
  const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
2186
2888
  console.log();
2187
2889
  console.log(
2188
- ` To get your API key, go to ${chalk3.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
2890
+ ` To get your API key, go to ${chalk4.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
2189
2891
  );
2190
- console.log(` ${chalk3.cyan.underline(settingsUrl)}`);
2892
+ console.log(` ${chalk4.cyan.underline(settingsUrl)}`);
2191
2893
  console.log();
2192
2894
  apiKey = await promptForApiKey();
2193
2895
  }
@@ -3074,18 +3776,18 @@ function findControlChar(value) {
3074
3776
  function formatCodepoint(code) {
3075
3777
  return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
3076
3778
  }
3077
- function detectRowFormat(record2) {
3078
- const input = record2.input;
3079
- if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record2.output === "string") {
3779
+ function detectRowFormat(record3) {
3780
+ const input = record3.input;
3781
+ if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record3.output === "string") {
3080
3782
  return "document_ocr_jsonl";
3081
3783
  }
3082
- if (typeof record2.input === "string" || typeof record2.output === "string" || !("input" in record2) || !("output" in record2)) {
3784
+ if (typeof record3.input === "string" || typeof record3.output === "string" || !("input" in record3) || !("output" in record3)) {
3083
3785
  return "jsonl";
3084
3786
  }
3085
3787
  return null;
3086
3788
  }
3087
- function validateDocumentOcrRow(record2, rowNumber, errors) {
3088
- const input = record2.input;
3789
+ function validateDocumentOcrRow(record3, rowNumber, errors) {
3790
+ const input = record3.input;
3089
3791
  const assets = input.assets;
3090
3792
  if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
3091
3793
  errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
@@ -3097,10 +3799,10 @@ function validateDocumentOcrRow(record2, rowNumber, errors) {
3097
3799
  );
3098
3800
  }
3099
3801
  }
3100
- if (typeof record2.output !== "string" || record2.output.trim().length === 0) {
3802
+ if (typeof record3.output !== "string" || record3.output.trim().length === 0) {
3101
3803
  errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
3102
3804
  } else {
3103
- const hit = findControlChar(record2.output);
3805
+ const hit = findControlChar(record3.output);
3104
3806
  if (hit) {
3105
3807
  errors.push(
3106
3808
  `Row ${rowNumber}: OCR output contains invisible control character ${formatCodepoint(hit.codepoint)} (${hit.name}) at offset ${hit.offset} \u2014 strip or escape before uploading; some training paths split on it`
@@ -3169,14 +3871,14 @@ function validateDatasetFile(file, requestedFormat) {
3169
3871
  errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
3170
3872
  continue;
3171
3873
  }
3172
- const record2 = row;
3173
- if ("messages" in record2 && !("input" in record2) && !("output" in record2)) {
3874
+ const record3 = row;
3875
+ if ("messages" in record3 && !("input" in record3) && !("output" in record3)) {
3174
3876
  errors.push(
3175
3877
  `Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
3176
3878
  );
3177
3879
  continue;
3178
3880
  }
3179
- const rowFormat = detectRowFormat(record2);
3881
+ const rowFormat = detectRowFormat(record3);
3180
3882
  if (!rowFormat) {
3181
3883
  errors.push(
3182
3884
  `Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
@@ -3193,17 +3895,17 @@ function validateDatasetFile(file, requestedFormat) {
3193
3895
  continue;
3194
3896
  }
3195
3897
  if (rowFormat === "document_ocr_jsonl") {
3196
- validateDocumentOcrRow(record2, rowNumber, errors);
3898
+ validateDocumentOcrRow(record3, rowNumber, errors);
3197
3899
  continue;
3198
3900
  }
3199
- if (typeof record2.input !== "string") {
3901
+ if (typeof record3.input !== "string") {
3200
3902
  errors.push(`Row ${rowNumber}: missing string "input" field`);
3201
3903
  }
3202
- if (typeof record2.output !== "string") {
3904
+ if (typeof record3.output !== "string") {
3203
3905
  errors.push(`Row ${rowNumber}: missing string "output" field`);
3204
3906
  }
3205
3907
  for (const field of ["input", "output"]) {
3206
- const value = record2[field];
3908
+ const value = record3[field];
3207
3909
  if (typeof value !== "string") continue;
3208
3910
  const hit = findControlChar(value);
3209
3911
  if (hit) {
@@ -4206,12 +4908,12 @@ var ManagedModelServer = class {
4206
4908
  clearTimeout(this.idleTimer);
4207
4909
  this.idleTimer = null;
4208
4910
  }
4209
- writeLog(record2) {
4911
+ writeLog(record3) {
4210
4912
  if (this.logRecordOverride) {
4211
- this.logRecordOverride(record2);
4913
+ this.logRecordOverride(record3);
4212
4914
  return;
4213
4915
  }
4214
- const line = `${JSON.stringify(record2)}
4916
+ const line = `${JSON.stringify(record3)}
4215
4917
  `;
4216
4918
  if (this.options.logFile) {
4217
4919
  mkdirSync2(dirname3(this.options.logFile), { recursive: true });
@@ -5651,7 +6353,7 @@ function registerModelsCommands(parent) {
5651
6353
  }
5652
6354
 
5653
6355
  // src/commands/balance.ts
5654
- import chalk4 from "chalk";
6356
+ import chalk5 from "chalk";
5655
6357
  var KIND_LABELS = {
5656
6358
  signup_bonus: "Signup bonus",
5657
6359
  topup: "Top-up",
@@ -5666,8 +6368,8 @@ function formatCents3(cents) {
5666
6368
  return `${sign}$${(abs / 100).toFixed(2)}`;
5667
6369
  }
5668
6370
  function formatSigned(cents) {
5669
- if (cents > 0) return chalk4.green(`+${formatCents3(cents)}`);
5670
- if (cents < 0) return chalk4.red(formatCents3(cents));
6371
+ if (cents > 0) return chalk5.green(`+${formatCents3(cents)}`);
6372
+ if (cents < 0) return chalk5.red(formatCents3(cents));
5671
6373
  return formatCents3(cents);
5672
6374
  }
5673
6375
  function registerBalanceCommands(parent) {
@@ -5682,7 +6384,7 @@ function registerBalanceCommands(parent) {
5682
6384
  return printJson({ balance, transactions });
5683
6385
  }
5684
6386
  const lowBalance = balance.balance_cents < 100;
5685
- const balanceLine = lowBalance ? chalk4.red(formatCents3(balance.balance_cents)) + chalk4.dim(" (low)") : chalk4.bold.green(formatCents3(balance.balance_cents));
6387
+ const balanceLine = lowBalance ? chalk5.red(formatCents3(balance.balance_cents)) + chalk5.dim(" (low)") : chalk5.bold.green(formatCents3(balance.balance_cents));
5686
6388
  printDetail([
5687
6389
  ["Credits", balanceLine],
5688
6390
  [
@@ -5697,7 +6399,7 @@ function registerBalanceCommands(parent) {
5697
6399
  ]);
5698
6400
  if (transactions && transactions.length > 0) {
5699
6401
  console.log(`
5700
- ${chalk4.bold("Recent transactions")}`);
6402
+ ${chalk5.bold("Recent transactions")}`);
5701
6403
  printTable(
5702
6404
  ["Date", "Type", "Amount", "Balance", "Reference"],
5703
6405
  transactions.map((row) => [
@@ -5705,19 +6407,19 @@ ${chalk4.bold("Recent transactions")}`);
5705
6407
  KIND_LABELS[row.kind] ?? row.kind,
5706
6408
  formatSigned(row.amount_cents),
5707
6409
  formatCents3(row.balance_after_cents),
5708
- row.reference_id ? shortId(row.reference_id) : chalk4.dim("\u2014")
6410
+ row.reference_id ? shortId(row.reference_id) : chalk5.dim("\u2014")
5709
6411
  ])
5710
6412
  );
5711
6413
  } else {
5712
6414
  console.log(
5713
6415
  `
5714
- ${chalk4.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
6416
+ ${chalk5.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
5715
6417
  );
5716
6418
  }
5717
6419
  if (lowBalance) {
5718
6420
  console.log(
5719
6421
  `
5720
- ${formatStatus("preparing")} ${chalk4.yellow(
6422
+ ${formatStatus("preparing")} ${chalk5.yellow(
5721
6423
  "Run `tt topup` to add more credits before starting a run."
5722
6424
  )}`
5723
6425
  );
@@ -5726,7 +6428,7 @@ ${formatStatus("preparing")} ${chalk4.yellow(
5726
6428
  }
5727
6429
 
5728
6430
  // src/commands/topup.ts
5729
- import chalk5 from "chalk";
6431
+ import chalk6 from "chalk";
5730
6432
  import open from "open";
5731
6433
  var PRESETS_USD = [10, 25, 50, 100];
5732
6434
  var MIN_USD = 5;
@@ -5765,9 +6467,9 @@ function registerTopupCommands(parent) {
5765
6467
  "amount is required in --json mode (use --amount <usd>)"
5766
6468
  );
5767
6469
  }
5768
- console.log(chalk5.bold("Add credits via Stripe Checkout"));
6470
+ console.log(chalk6.bold("Add credits via Stripe Checkout"));
5769
6471
  console.log(
5770
- chalk5.dim(
6472
+ chalk6.dim(
5771
6473
  `Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
5772
6474
  )
5773
6475
  );
@@ -5794,8 +6496,8 @@ function registerTopupCommands(parent) {
5794
6496
  }
5795
6497
  const url = data.checkout_url;
5796
6498
  console.log(
5797
- `${chalk5.bold("Checkout URL:")} ${chalk5.cyan(url)}
5798
- ` + chalk5.dim("Complete the payment to credit your account.")
6499
+ `${chalk6.bold("Checkout URL:")} ${chalk6.cyan(url)}
6500
+ ` + chalk6.dim("Complete the payment to credit your account.")
5799
6501
  );
5800
6502
  if (options.open !== false) {
5801
6503
  try {
@@ -5803,7 +6505,7 @@ function registerTopupCommands(parent) {
5803
6505
  printSuccess("Opened Stripe Checkout in your browser.");
5804
6506
  } catch {
5805
6507
  console.log(
5806
- chalk5.yellow(
6508
+ chalk6.yellow(
5807
6509
  "Could not open browser automatically \u2014 copy the URL above."
5808
6510
  )
5809
6511
  );
@@ -5813,7 +6515,7 @@ function registerTopupCommands(parent) {
5813
6515
  }
5814
6516
 
5815
6517
  // src/commands/eval.ts
5816
- import chalk6 from "chalk";
6518
+ import chalk7 from "chalk";
5817
6519
 
5818
6520
  // src/eval/rules.ts
5819
6521
  function validateSpec(spec) {
@@ -5978,7 +6680,7 @@ function registerEvalCommand(parent) {
5978
6680
  const spec = loadSpec(cmdOpts.file);
5979
6681
  const validation = validateSpec(spec);
5980
6682
  if (!isJsonMode()) {
5981
- console.log(chalk6.dim(`
6683
+ console.log(chalk7.dim(`
5982
6684
  Spec: ${cmdOpts.file}
5983
6685
  `));
5984
6686
  }
@@ -5993,10 +6695,10 @@ Spec: ${cmdOpts.file}
5993
6695
  });
5994
6696
  }
5995
6697
  function printValidation(checks) {
5996
- console.log(chalk6.bold("Spec Validation"));
6698
+ console.log(chalk7.bold("Spec Validation"));
5997
6699
  for (const check of checks) {
5998
- const icon = check.passed ? chalk6.green("\u2713") : chalk6.red("\u2717");
5999
- const msg = check.message ? chalk6.dim(` \u2014 ${check.message}`) : "";
6700
+ const icon = check.passed ? chalk7.green("\u2713") : chalk7.red("\u2717");
6701
+ const msg = check.message ? chalk7.dim(` \u2014 ${check.message}`) : "";
6000
6702
  console.log(` ${icon} ${check.name}${msg}`);
6001
6703
  }
6002
6704
  }
@@ -6152,17 +6854,28 @@ function createProgram(version, runtime = {}) {
6152
6854
  const openShell = async () => {
6153
6855
  const root = program.opts();
6154
6856
  const shellEnvironment = childEnvironment(root, env);
6857
+ const output = runtime.stdout ?? process.stdout;
6858
+ const error = runtime.stderr ?? process.stderr;
6859
+ const agent = createShellAgent({
6860
+ client: runtime.agentClient ?? createAgentClient({
6861
+ apiKey: root.apiKey ?? shellEnvironment.TUNED_TENSOR_API_KEY,
6862
+ baseUrl: root.baseUrl ?? shellEnvironment.TUNED_TENSOR_URL
6863
+ }),
6864
+ output,
6865
+ error
6866
+ });
6155
6867
  await launchShell({
6156
6868
  runner: shellRunner,
6869
+ agent,
6157
6870
  input: runtime.stdin ?? process.stdin,
6158
- output: runtime.stdout ?? process.stdout,
6159
- error: runtime.stderr ?? process.stderr,
6871
+ output,
6872
+ error,
6160
6873
  cwd,
6161
6874
  env: shellEnvironment,
6162
6875
  version
6163
6876
  });
6164
6877
  };
6165
- program.name("tt").description("Tuned Tensor \u2014 one terminal for cloud and local fine-tuning").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
6878
+ program.name("tt").description("Tuned Tensor \u2014 converse, train, and inspect from one terminal").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
6166
6879
  "-u, --base-url <url>",
6167
6880
  "API base URL (default: https://tunedtensor.com)"
6168
6881
  ).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
@@ -6170,7 +6883,7 @@ function createProgram(version, runtime = {}) {
6170
6883
  if (root.json) setJsonMode(true);
6171
6884
  if (root.color === false) {
6172
6885
  process.env.FORCE_COLOR = "0";
6173
- chalk7.level = 0;
6886
+ chalk8.level = 0;
6174
6887
  }
6175
6888
  });
6176
6889
  registerAuthCommands(program);
@@ -6203,12 +6916,12 @@ function createProgram(version, runtime = {}) {
6203
6916
  `);
6204
6917
  return;
6205
6918
  }
6206
- output.write(`${chalk7.bold.cyan("Tuned Tensor status")}
6919
+ output.write(`${chalk8.bold.cyan("Tuned Tensor status")}
6207
6920
  `);
6208
6921
  output.write(`${formatShellStatus(context, target).join("\n")}
6209
6922
 
6210
6923
  `);
6211
- output.write(`${chalk7.bold("Context")}
6924
+ output.write(`${chalk8.bold("Context")}
6212
6925
  `);
6213
6926
  output.write(
6214
6927
  `${formatShellContext(
@@ -6222,7 +6935,7 @@ function createProgram(version, runtime = {}) {
6222
6935
  program.command("local").description("Run the local CUDA training and model workflow").helpOption(false).allowUnknownOption().argument("[args...]", "TT Local command and arguments").action(async (args) => {
6223
6936
  const root = program.opts();
6224
6937
  const passthrough = extractPassthroughOptions(args, root);
6225
- if (!passthrough.color) chalk7.level = 0;
6938
+ if (!passthrough.color) chalk8.level = 0;
6226
6939
  const result = await invokeLocal(
6227
6940
  passthrough.args.length > 0 ? passthrough.args : ["--help"],
6228
6941
  {
@@ -6262,7 +6975,7 @@ function createProgram(version, runtime = {}) {
6262
6975
  "after",
6263
6976
  `
6264
6977
  Workflows:
6265
- tt Open the interactive terminal (TTY only)
6978
+ tt Open the conversational terminal (TTY only)
6266
6979
  tt status Inspect cloud/local project context
6267
6980
  tt cloud <command> Run a hosted command explicitly
6268
6981
  tt local <command> Run a local GPU command
@@ -6275,6 +6988,22 @@ Examples:
6275
6988
  );
6276
6989
  return program;
6277
6990
  }
6991
+ function createShellAgent(options) {
6992
+ return new TunedTensorAgentSession({
6993
+ client: options.client,
6994
+ io: {
6995
+ write(text2) {
6996
+ options.output.write(text2);
6997
+ },
6998
+ writeError(text2) {
6999
+ options.error.write(text2);
7000
+ },
7001
+ clear() {
7002
+ options.output.write("\x1B[2J\x1B[H");
7003
+ }
7004
+ }
7005
+ });
7006
+ }
6278
7007
  async function runCli(version, runtime = {}) {
6279
7008
  const argv = runtime.argv ?? process.argv;
6280
7009
  const env = runtime.env ?? process.env;
@@ -6286,6 +7015,16 @@ async function runCli(version, runtime = {}) {
6286
7015
  env
6287
7016
  })) {
6288
7017
  const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
7018
+ const output = runtime.stdout ?? process.stdout;
7019
+ const error = runtime.stderr ?? process.stderr;
7020
+ const agent = createShellAgent({
7021
+ client: runtime.agentClient ?? createAgentClient({
7022
+ apiKey: env.TUNED_TENSOR_API_KEY,
7023
+ baseUrl: env.TUNED_TENSOR_URL
7024
+ }),
7025
+ output,
7026
+ error
7027
+ });
6289
7028
  await (runtime.startShell ?? startInteractiveShell)({
6290
7029
  runner: async (request2) => await invokeSelf(
6291
7030
  request2.target === "local" ? ["local", ...request2.args] : request2.args,
@@ -6295,9 +7034,10 @@ async function runCli(version, runtime = {}) {
6295
7034
  entrypoint: argv[1]
6296
7035
  }
6297
7036
  ),
7037
+ agent,
6298
7038
  input: runtime.stdin ?? process.stdin,
6299
- output: runtime.stdout ?? process.stdout,
6300
- error: runtime.stderr ?? process.stderr,
7039
+ output,
7040
+ error,
6301
7041
  cwd: runtime.cwd ?? process.cwd(),
6302
7042
  env,
6303
7043
  version
@@ -6309,7 +7049,7 @@ async function runCli(version, runtime = {}) {
6309
7049
  }
6310
7050
 
6311
7051
  // src/index.ts
6312
- runCli("0.6.0").catch((err) => {
7052
+ runCli("0.8.0").catch((err) => {
6313
7053
  reportError(err);
6314
7054
  process.exit(1);
6315
7055
  });