@tuned-tensor/cli 0.6.0 → 0.7.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/README.md +26 -15
- package/dist/index.js +2545 -1865
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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,2622 @@ function del(path, opts) {
|
|
|
295
170
|
return request("DELETE", path, { opts });
|
|
296
171
|
}
|
|
297
172
|
|
|
298
|
-
// src/
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
|
342
|
-
|
|
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
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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/
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
448
|
+
return groups;
|
|
400
449
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
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
|
|
405
|
-
return
|
|
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
|
-
|
|
408
|
-
|
|
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
|
|
411
|
-
const
|
|
412
|
-
return
|
|
519
|
+
function stringField(value, key) {
|
|
520
|
+
const field = value?.[key];
|
|
521
|
+
return typeof field === "string" && field.trim() ? field : void 0;
|
|
413
522
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
if (
|
|
417
|
-
return
|
|
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
|
|
420
|
-
|
|
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
|
|
433
|
-
|
|
434
|
-
|
|
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
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
445
|
-
|
|
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
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
|
563
|
-
|
|
564
|
-
|
|
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 (
|
|
705
|
-
|
|
706
|
-
return true;
|
|
743
|
+
if (/[\r\n]/.test(input)) {
|
|
744
|
+
throw new ShellParseError("Enter one command at a time.");
|
|
707
745
|
}
|
|
708
|
-
if (
|
|
709
|
-
|
|
710
|
-
return true;
|
|
746
|
+
if (input.trimStart().startsWith("!")) {
|
|
747
|
+
throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
|
|
711
748
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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 (
|
|
726
|
-
|
|
727
|
-
|
|
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 (
|
|
730
|
-
|
|
731
|
-
|
|
792
|
+
if (/\s/.test(character)) {
|
|
793
|
+
finishToken();
|
|
794
|
+
continue;
|
|
732
795
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return true;
|
|
796
|
+
if (character === "'") {
|
|
797
|
+
quote = "single";
|
|
798
|
+
tokenStarted = true;
|
|
799
|
+
continue;
|
|
738
800
|
}
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
801
|
+
if (character === '"') {
|
|
802
|
+
quote = "double";
|
|
803
|
+
tokenStarted = true;
|
|
804
|
+
continue;
|
|
743
805
|
}
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
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 (
|
|
749
|
-
|
|
750
|
-
|
|
816
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
817
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
818
|
+
throw operatorError("$(\u2026)");
|
|
751
819
|
}
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
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 (
|
|
758
|
-
|
|
759
|
-
|
|
827
|
+
if (quote !== "none") {
|
|
828
|
+
throw new ShellParseError(
|
|
829
|
+
`Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
|
|
830
|
+
);
|
|
760
831
|
}
|
|
761
|
-
|
|
832
|
+
finishToken();
|
|
833
|
+
return tokens;
|
|
762
834
|
}
|
|
763
|
-
function
|
|
764
|
-
|
|
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
|
|
767
|
-
return
|
|
768
|
-
|
|
769
|
-
"
|
|
770
|
-
|
|
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
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
|
903
|
+
return previous[right.length];
|
|
806
904
|
}
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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 (
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
|
|
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));
|
|
945
|
+
function errorMessage(error) {
|
|
946
|
+
return error instanceof Error ? error.message : String(error);
|
|
866
947
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
indices.push(index);
|
|
881
|
-
}
|
|
882
|
-
return indices;
|
|
948
|
+
var successMark = () => chalk.green("\u2713");
|
|
949
|
+
var errorMark = () => chalk.red("\u2717");
|
|
950
|
+
var accent = chalk.hex("#8B5CF6");
|
|
951
|
+
var DETAIL_LABEL_WIDTH = 15;
|
|
952
|
+
function styleDetailLines(lines) {
|
|
953
|
+
return lines.map((line) => {
|
|
954
|
+
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
955
|
+
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
956
|
+
if (!label.trim()) return line;
|
|
957
|
+
const color = label.startsWith("Warning") ? chalk.yellow : accent.bold;
|
|
958
|
+
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
959
|
+
});
|
|
883
960
|
}
|
|
884
|
-
function
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
}
|
|
961
|
+
function detailLine(label, value) {
|
|
962
|
+
return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
|
|
963
|
+
}
|
|
964
|
+
function assertNoArgs(command, args) {
|
|
965
|
+
if (args.length > 0) {
|
|
966
|
+
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
907
967
|
}
|
|
908
|
-
return null;
|
|
909
968
|
}
|
|
910
|
-
function
|
|
911
|
-
const
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
969
|
+
function expandDirectory(value, cwd, env) {
|
|
970
|
+
const home = env.HOME ? resolve2(env.HOME) : homedir3();
|
|
971
|
+
if (value === "~") return home;
|
|
972
|
+
if (value.startsWith("~/")) return resolve2(home, value.slice(2));
|
|
973
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(cwd, value);
|
|
974
|
+
}
|
|
975
|
+
function helpText(mode, query, palette = false) {
|
|
976
|
+
const groups = groupedCatalog(mode, query);
|
|
977
|
+
const lines = [];
|
|
978
|
+
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)}` : ""}`));
|
|
979
|
+
if (groups.size === 0) {
|
|
980
|
+
lines.push(chalk.dim(" No matching commands."));
|
|
981
|
+
} else {
|
|
982
|
+
for (const [group, commands] of groups) {
|
|
983
|
+
lines.push("");
|
|
984
|
+
lines.push(chalk.bold(group));
|
|
985
|
+
for (const command of commands) {
|
|
986
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
917
987
|
}
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
return {
|
|
921
|
-
path: defaultPath,
|
|
922
|
-
replace(projectedPath) {
|
|
923
|
-
args.splice(start, 0, projectedPath);
|
|
924
988
|
}
|
|
925
|
-
};
|
|
926
|
-
}
|
|
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
989
|
}
|
|
932
|
-
if (
|
|
933
|
-
|
|
990
|
+
if (!query) {
|
|
991
|
+
lines.push("");
|
|
992
|
+
lines.push(chalk.bold("Conversation"));
|
|
993
|
+
lines.push(` ${accent("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
|
|
994
|
+
lines.push(` ${accent("/new".padEnd(22))} Start a new agent conversation.`);
|
|
995
|
+
lines.push(` ${accent("/threads".padEnd(22))} List recent conversations.`);
|
|
996
|
+
lines.push(` ${accent("/resume <id>".padEnd(22))} Resume a conversation.`);
|
|
997
|
+
lines.push(` ${accent("/approve [id]".padEnd(22))} Approve a proposed action.`);
|
|
998
|
+
lines.push(` ${accent("/reject [id]".padEnd(22))} Reject a proposed action.`);
|
|
999
|
+
lines.push("");
|
|
1000
|
+
lines.push(chalk.bold("Session"));
|
|
1001
|
+
for (const command of SLASH_COMMANDS) {
|
|
1002
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
1003
|
+
}
|
|
1004
|
+
lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
|
|
1005
|
+
lines.push(chalk.dim(
|
|
1006
|
+
"\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
|
|
1007
|
+
));
|
|
934
1008
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const explicit = optionValueArgument(args, "--spec");
|
|
938
|
-
if (explicit) return explicit;
|
|
939
|
-
return null;
|
|
1009
|
+
return `${lines.join("\n")}
|
|
1010
|
+
`;
|
|
940
1011
|
}
|
|
941
|
-
|
|
942
|
-
return
|
|
1012
|
+
function activeModelLabel(snapshot) {
|
|
1013
|
+
return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
|
|
943
1014
|
}
|
|
944
|
-
function
|
|
945
|
-
|
|
946
|
-
return
|
|
1015
|
+
function logoRows() {
|
|
1016
|
+
const muted = chalk.dim("\u2588\u2588");
|
|
1017
|
+
return [
|
|
1018
|
+
`${chalk.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
|
|
1019
|
+
`${muted} ${accent("\u2588\u2588")} ${muted}`,
|
|
1020
|
+
`${muted} ${muted} ${chalk.hex("#7C3AED")("\u2588\u2588")}`
|
|
1021
|
+
];
|
|
947
1022
|
}
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1023
|
+
function renderShellBanner(snapshot) {
|
|
1024
|
+
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1025
|
+
const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
|
|
1026
|
+
const textRows = [
|
|
1027
|
+
heading,
|
|
1028
|
+
`${chalk.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
|
|
1029
|
+
chalk.dim("Ask TT anything \xB7 known commands run directly \xB7 /help for more")
|
|
1030
|
+
];
|
|
1031
|
+
const logo = logoRows();
|
|
1032
|
+
const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
|
|
1033
|
+
return `${lines.join("\n")}
|
|
1034
|
+
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
function renderShellPrompt(snapshot) {
|
|
1038
|
+
return `${accent("tt")} ${chalk.bold(snapshot.mode)} ${chalk.dim(snapshot.context.projectName)} \u203A `;
|
|
1039
|
+
}
|
|
1040
|
+
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
1041
|
+
constructor(runner, io, agent, env, contextProvider, version, initialContext) {
|
|
1042
|
+
this.runner = runner;
|
|
1043
|
+
this.io = io;
|
|
1044
|
+
this.agent = agent;
|
|
1045
|
+
this.env = env;
|
|
1046
|
+
this.contextProvider = contextProvider;
|
|
1047
|
+
this.version = version;
|
|
1048
|
+
this.mode = initialContext.inferredTarget;
|
|
1049
|
+
this.modeSource = initialContext.targetSource;
|
|
1050
|
+
this.cwd = initialContext.cwd;
|
|
1051
|
+
this.context = initialContext;
|
|
968
1052
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
};
|
|
1053
|
+
mode;
|
|
1054
|
+
modeSource;
|
|
1055
|
+
cwd;
|
|
1056
|
+
context;
|
|
1057
|
+
static async create(options) {
|
|
1058
|
+
const env = options.env ?? process.env;
|
|
1059
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
1060
|
+
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1061
|
+
const initialContext = await contextProvider({ cwd, env });
|
|
1062
|
+
return new _TunedTensorShellSession(
|
|
1063
|
+
options.runner,
|
|
1064
|
+
options.io,
|
|
1065
|
+
options.agent,
|
|
1066
|
+
env,
|
|
1067
|
+
contextProvider,
|
|
1068
|
+
options.version,
|
|
1069
|
+
initialContext
|
|
1070
|
+
);
|
|
978
1071
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
1072
|
+
interruptAgent() {
|
|
1073
|
+
return this.agent?.interrupt() ?? false;
|
|
982
1074
|
}
|
|
983
|
-
|
|
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) {
|
|
1075
|
+
snapshot() {
|
|
991
1076
|
return {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1077
|
+
mode: this.mode,
|
|
1078
|
+
modeSource: this.modeSource,
|
|
1079
|
+
cwd: this.cwd,
|
|
1080
|
+
context: this.context,
|
|
1081
|
+
version: this.version
|
|
997
1082
|
};
|
|
998
1083
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
return {
|
|
1002
|
-
args,
|
|
1003
|
-
droppedSpecKeys: [],
|
|
1004
|
-
pathReplacements: [],
|
|
1005
|
-
cleanup: async () => {
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1084
|
+
prompt() {
|
|
1085
|
+
return renderShellPrompt(this.snapshot());
|
|
1008
1086
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
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
|
-
);
|
|
1087
|
+
banner() {
|
|
1088
|
+
return renderShellBanner(this.snapshot());
|
|
1038
1089
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
try {
|
|
1047
|
-
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
1048
|
-
} catch {
|
|
1049
|
-
return { hasJson: false, json: void 0 };
|
|
1090
|
+
async refreshContext() {
|
|
1091
|
+
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
1092
|
+
this.cwd = this.context.cwd;
|
|
1093
|
+
if (this.modeSource !== "session" && this.modeSource !== "environment") {
|
|
1094
|
+
this.mode = this.context.inferredTarget;
|
|
1095
|
+
this.modeSource = this.context.targetSource;
|
|
1096
|
+
}
|
|
1050
1097
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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);
|
|
1098
|
+
writeLines(lines) {
|
|
1099
|
+
this.io.write(`${lines.join("\n")}
|
|
1100
|
+
`);
|
|
1070
1101
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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"
|
|
1102
|
+
modelLines() {
|
|
1103
|
+
if (this.mode === "local") {
|
|
1104
|
+
return styleDetailLines([
|
|
1105
|
+
detailLine("Active model", this.context.local.activeModelId ?? "base"),
|
|
1106
|
+
detailLine("Change", "/model <id> to activate a verified local model")
|
|
1107
|
+
]);
|
|
1090
1108
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
} catch {
|
|
1109
|
+
return styleDetailLines([
|
|
1110
|
+
detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
|
|
1111
|
+
detailLine("Change", "edit base_model in tunedtensor.json, then run push")
|
|
1112
|
+
]);
|
|
1113
|
+
}
|
|
1114
|
+
async handleSlash(command) {
|
|
1115
|
+
switch (command.name) {
|
|
1116
|
+
case "palette":
|
|
1117
|
+
this.io.write(helpText(this.mode, void 0, true));
|
|
1118
|
+
return "continue";
|
|
1119
|
+
case "help":
|
|
1120
|
+
this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
|
|
1121
|
+
return "continue";
|
|
1122
|
+
case "status":
|
|
1123
|
+
assertNoArgs("status", command.args);
|
|
1124
|
+
await this.refreshContext();
|
|
1125
|
+
this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
|
|
1126
|
+
return "continue";
|
|
1127
|
+
case "context":
|
|
1128
|
+
assertNoArgs("context", command.args);
|
|
1129
|
+
await this.refreshContext();
|
|
1130
|
+
this.writeLines(styleDetailLines(
|
|
1131
|
+
formatShellContext(this.context, this.mode, this.modeSource)
|
|
1132
|
+
));
|
|
1133
|
+
return "continue";
|
|
1134
|
+
case "mode": {
|
|
1135
|
+
if (command.args.length === 0) {
|
|
1136
|
+
this.io.write(`Current workflow: ${this.mode}
|
|
1137
|
+
`);
|
|
1138
|
+
return "continue";
|
|
1139
|
+
}
|
|
1140
|
+
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1141
|
+
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1125
1142
|
}
|
|
1143
|
+
this.mode = command.args[0];
|
|
1144
|
+
this.modeSource = "session";
|
|
1145
|
+
this.io.write(`${successMark()} Workflow switched to ${this.mode}.
|
|
1146
|
+
`);
|
|
1147
|
+
return "continue";
|
|
1126
1148
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1149
|
+
case "model": {
|
|
1150
|
+
if (command.args.length === 0) {
|
|
1151
|
+
await this.refreshContext();
|
|
1152
|
+
this.writeLines(this.modelLines());
|
|
1153
|
+
return "continue";
|
|
1154
|
+
}
|
|
1155
|
+
if (command.args.length !== 1) {
|
|
1156
|
+
throw new ShellParseError("Usage: /model [model-id]");
|
|
1157
|
+
}
|
|
1158
|
+
if (this.mode !== "local") {
|
|
1159
|
+
throw new ShellParseError(
|
|
1160
|
+
"Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
await this.runner({
|
|
1164
|
+
target: "local",
|
|
1165
|
+
args: ["models", "activate", command.args[0]],
|
|
1166
|
+
cwd: this.cwd
|
|
1167
|
+
});
|
|
1168
|
+
await this.refreshContext();
|
|
1169
|
+
return "continue";
|
|
1170
|
+
}
|
|
1171
|
+
case "clear":
|
|
1172
|
+
assertNoArgs("clear", command.args);
|
|
1173
|
+
this.io.clear();
|
|
1174
|
+
return "continue";
|
|
1175
|
+
case "cd": {
|
|
1176
|
+
if (command.args.length === 0) {
|
|
1177
|
+
this.io.write(`${this.cwd}
|
|
1178
|
+
`);
|
|
1179
|
+
return "continue";
|
|
1180
|
+
}
|
|
1181
|
+
if (command.args.length !== 1) {
|
|
1182
|
+
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
1183
|
+
}
|
|
1184
|
+
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
1185
|
+
const metadata = await stat2(nextDirectory).catch(() => null);
|
|
1186
|
+
if (!metadata?.isDirectory()) {
|
|
1187
|
+
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
1188
|
+
}
|
|
1189
|
+
this.cwd = nextDirectory;
|
|
1190
|
+
await this.refreshContext();
|
|
1191
|
+
this.io.write(`${successMark()} Directory: ${this.cwd}
|
|
1192
|
+
`);
|
|
1193
|
+
return "continue";
|
|
1194
|
+
}
|
|
1195
|
+
case "exit":
|
|
1196
|
+
assertNoArgs("exit", command.args);
|
|
1197
|
+
return "exit";
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
async handleLine(input) {
|
|
1201
|
+
try {
|
|
1202
|
+
if (/^(exit|quit)$/i.test(input.trim())) return "exit";
|
|
1203
|
+
const trimmed = input.trim();
|
|
1204
|
+
if (trimmed.startsWith("/")) {
|
|
1205
|
+
const rawName = trimmed.slice(1).split(/\s+/, 1)[0].toLowerCase();
|
|
1206
|
+
if (AGENT_SLASH_COMMANDS.has(rawName)) {
|
|
1207
|
+
if (!this.agent) {
|
|
1208
|
+
throw new ShellParseError("The conversational agent is unavailable.");
|
|
1209
|
+
}
|
|
1210
|
+
await this.agent.handleLine(trimmed);
|
|
1211
|
+
await this.refreshContext();
|
|
1212
|
+
return "continue";
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const slash = parseSlashCommand(input);
|
|
1216
|
+
if (slash) return await this.handleSlash(slash);
|
|
1217
|
+
const explicitCommand = trimmed.startsWith(":") ? trimmed.slice(1).trimStart() : null;
|
|
1218
|
+
if (explicitCommand !== null && !explicitCommand) {
|
|
1219
|
+
throw new ShellParseError("Add a TT command after :.");
|
|
1220
|
+
}
|
|
1221
|
+
const explicitWorkflowPrefix = /^(?:tt\s+)?(?:cloud|local)(?:\s|$)/.test(trimmed) || /^tt(?:\s|$)/.test(trimmed);
|
|
1222
|
+
if (explicitCommand === null && !explicitWorkflowPrefix && this.agent && !hasCatalogIntent(input, this.mode)) {
|
|
1223
|
+
await this.agent.handleLine(input);
|
|
1224
|
+
await this.refreshContext();
|
|
1225
|
+
return "continue";
|
|
1226
|
+
}
|
|
1227
|
+
const commandInput = explicitCommand ?? input;
|
|
1228
|
+
const routed = routeShellCommand(commandInput, this.mode);
|
|
1229
|
+
if (!routed) return "continue";
|
|
1230
|
+
await this.runner({
|
|
1231
|
+
target: routed.target,
|
|
1232
|
+
args: [...routed.args],
|
|
1233
|
+
cwd: this.cwd
|
|
1151
1234
|
});
|
|
1152
|
-
|
|
1153
|
-
|
|
1235
|
+
await this.refreshContext();
|
|
1236
|
+
return "continue";
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
this.io.writeError(`${errorMark()} ${errorMessage(error)}
|
|
1239
|
+
`);
|
|
1240
|
+
return "continue";
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
async function createShellSession(options) {
|
|
1245
|
+
return TunedTensorShellSession.create(options);
|
|
1154
1246
|
}
|
|
1155
|
-
function
|
|
1156
|
-
|
|
1247
|
+
function streamIsTTY(stream) {
|
|
1248
|
+
return Boolean(stream.isTTY);
|
|
1249
|
+
}
|
|
1250
|
+
async function withForegroundSignalHandoff(control, run) {
|
|
1251
|
+
const signals = control.signals ?? process;
|
|
1252
|
+
const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
|
|
1253
|
+
const keepParentAlive = () => {
|
|
1254
|
+
};
|
|
1255
|
+
control.pauseReadline();
|
|
1256
|
+
if (restoreRawMode) control.input.setRawMode(false);
|
|
1257
|
+
signals.on("SIGINT", keepParentAlive);
|
|
1258
|
+
try {
|
|
1259
|
+
return await run();
|
|
1260
|
+
} finally {
|
|
1261
|
+
signals.off("SIGINT", keepParentAlive);
|
|
1262
|
+
if (restoreRawMode) control.input.setRawMode(true);
|
|
1263
|
+
control.resumeReadline();
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function streamIO(output, error) {
|
|
1157
1267
|
return {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
streamingStdout: false,
|
|
1168
|
-
droppedSpecKeys: [],
|
|
1169
|
-
errorMessage: message
|
|
1268
|
+
write(text2) {
|
|
1269
|
+
output.write(text2);
|
|
1270
|
+
},
|
|
1271
|
+
writeError(text2) {
|
|
1272
|
+
error.write(text2);
|
|
1273
|
+
},
|
|
1274
|
+
clear() {
|
|
1275
|
+
output.write("\x1B[2J\x1B[H");
|
|
1276
|
+
}
|
|
1170
1277
|
};
|
|
1171
1278
|
}
|
|
1172
|
-
async function
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1279
|
+
async function startInteractiveShell(options) {
|
|
1280
|
+
const input = options.input ?? process.stdin;
|
|
1281
|
+
const output = options.output ?? process.stdout;
|
|
1282
|
+
const error = options.error ?? process.stderr;
|
|
1283
|
+
if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
|
|
1284
|
+
throw new Error("The interactive TT shell requires a TTY.");
|
|
1285
|
+
}
|
|
1286
|
+
let readline;
|
|
1287
|
+
const terminalInput = input;
|
|
1288
|
+
const foregroundRunner = (request2) => withForegroundSignalHandoff(
|
|
1289
|
+
{
|
|
1290
|
+
input: terminalInput,
|
|
1291
|
+
pauseReadline: () => readline?.pause(),
|
|
1292
|
+
resumeReadline: () => readline?.resume()
|
|
1293
|
+
},
|
|
1294
|
+
() => options.runner(request2)
|
|
1295
|
+
);
|
|
1296
|
+
const session = await createShellSession({
|
|
1297
|
+
runner: foregroundRunner,
|
|
1298
|
+
io: streamIO(output, error),
|
|
1299
|
+
agent: options.agent,
|
|
1300
|
+
cwd: options.cwd,
|
|
1301
|
+
env: options.env,
|
|
1302
|
+
version: options.version
|
|
1303
|
+
});
|
|
1304
|
+
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
1305
|
+
readline = createInterface({
|
|
1306
|
+
input,
|
|
1307
|
+
output,
|
|
1308
|
+
terminal: true,
|
|
1309
|
+
historySize: 100,
|
|
1310
|
+
removeHistoryDuplicates: true,
|
|
1311
|
+
completer
|
|
1312
|
+
});
|
|
1313
|
+
readline.on("SIGINT", () => {
|
|
1314
|
+
if (session.interruptAgent()) {
|
|
1315
|
+
output.write("\n");
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
output.write("\n");
|
|
1319
|
+
readline?.setPrompt(session.prompt());
|
|
1320
|
+
readline?.write(null, { ctrl: true, name: "u" });
|
|
1321
|
+
});
|
|
1322
|
+
output.write(session.banner());
|
|
1323
|
+
readline.setPrompt(session.prompt());
|
|
1324
|
+
readline.prompt();
|
|
1175
1325
|
try {
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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);
|
|
1326
|
+
for await (const line of readline) {
|
|
1327
|
+
const action = await session.handleLine(line);
|
|
1328
|
+
if (action === "exit") {
|
|
1329
|
+
readline.close();
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
readline.setPrompt(session.prompt());
|
|
1333
|
+
readline.prompt();
|
|
1334
|
+
}
|
|
1204
1335
|
} finally {
|
|
1205
|
-
|
|
1336
|
+
readline.close();
|
|
1206
1337
|
}
|
|
1207
1338
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return result;
|
|
1339
|
+
function ciEnabled(value) {
|
|
1340
|
+
if (!value) return false;
|
|
1341
|
+
return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
1342
|
+
}
|
|
1343
|
+
function shouldStartInteractiveShell(options) {
|
|
1344
|
+
const env = options.env ?? process.env;
|
|
1345
|
+
return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
|
|
1216
1346
|
}
|
|
1217
1347
|
|
|
1218
|
-
// src/
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
{
|
|
1233
|
-
{
|
|
1234
|
-
{
|
|
1235
|
-
{
|
|
1236
|
-
{
|
|
1237
|
-
{
|
|
1238
|
-
{
|
|
1239
|
-
{
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
{
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
{
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
{
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1348
|
+
// src/agent.ts
|
|
1349
|
+
var accent2 = chalk2.hex("#8B5CF6");
|
|
1350
|
+
var successMark2 = () => chalk2.green("\u2713");
|
|
1351
|
+
var errorMark2 = () => chalk2.red("\u2717");
|
|
1352
|
+
function isRecord(value) {
|
|
1353
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1354
|
+
}
|
|
1355
|
+
function stringValue2(value) {
|
|
1356
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1357
|
+
}
|
|
1358
|
+
function helpText2() {
|
|
1359
|
+
return [
|
|
1360
|
+
accent2.bold("Agent commands"),
|
|
1361
|
+
"",
|
|
1362
|
+
` ${accent2("/new".padEnd(22))} Start a new conversation.`,
|
|
1363
|
+
` ${accent2("/threads".padEnd(22))} List recent conversations.`,
|
|
1364
|
+
` ${accent2("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
|
|
1365
|
+
` ${accent2("/approve [id]".padEnd(22))} Approve a proposed action.`,
|
|
1366
|
+
` ${accent2("/reject [id]".padEnd(22))} Reject a proposed action.`,
|
|
1367
|
+
` ${accent2("/status".padEnd(22))} Show the active conversation.`,
|
|
1368
|
+
` ${accent2("/clear".padEnd(22))} Clear the terminal.`,
|
|
1369
|
+
` ${accent2("/exit".padEnd(22))} Exit the agent.`,
|
|
1370
|
+
"",
|
|
1371
|
+
chalk2.dim(
|
|
1372
|
+
"Everything else is sent to the Tuned Tensor agent; known TT commands still run directly in the shell."
|
|
1373
|
+
),
|
|
1374
|
+
""
|
|
1375
|
+
].join("\n");
|
|
1376
|
+
}
|
|
1377
|
+
function formatThread(thread, activeId) {
|
|
1378
|
+
const active = thread.id === activeId ? accent2("\u25CF") : chalk2.dim("\u25CB");
|
|
1379
|
+
const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
|
|
1380
|
+
const date = new Date(time);
|
|
1381
|
+
const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
|
|
1382
|
+
month: "short",
|
|
1383
|
+
day: "numeric",
|
|
1384
|
+
hour: "2-digit",
|
|
1385
|
+
minute: "2-digit"
|
|
1386
|
+
});
|
|
1387
|
+
return `${active} ${chalk2.dim(thread.id.slice(0, 8))} ${thread.title} ${chalk2.dim(dateLabel)}`;
|
|
1388
|
+
}
|
|
1389
|
+
function actionFromPayload(payload) {
|
|
1390
|
+
const value = isRecord(payload.action) ? payload.action : isRecord(payload.approval) ? payload.approval : payload;
|
|
1391
|
+
const id = stringValue2(value.id);
|
|
1392
|
+
const title = stringValue2(value.title);
|
|
1393
|
+
if (!id || !title) return null;
|
|
1394
|
+
return {
|
|
1395
|
+
id,
|
|
1396
|
+
title,
|
|
1397
|
+
summary: stringValue2(value.summary) ?? "",
|
|
1398
|
+
risk: stringValue2(value.risk) ?? "unknown",
|
|
1399
|
+
status: stringValue2(value.status) ?? "proposed",
|
|
1400
|
+
operation: stringValue2(value.operation) ?? void 0,
|
|
1401
|
+
thread_id: stringValue2(value.thread_id) ?? void 0,
|
|
1402
|
+
turn_id: stringValue2(value.turn_id) ?? void 0,
|
|
1403
|
+
arguments: value.arguments,
|
|
1404
|
+
preview: value.preview,
|
|
1405
|
+
method: stringValue2(value.method) ?? void 0,
|
|
1406
|
+
path: stringValue2(value.path) ?? void 0
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
function formatJson(value) {
|
|
1410
|
+
try {
|
|
1411
|
+
return JSON.stringify(value, null, 2);
|
|
1412
|
+
} catch {
|
|
1413
|
+
return String(value);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
var TunedTensorAgentSession = class {
|
|
1417
|
+
constructor(options) {
|
|
1418
|
+
this.options = options;
|
|
1419
|
+
this.thread = options.thread ?? null;
|
|
1420
|
+
}
|
|
1421
|
+
thread;
|
|
1422
|
+
pendingActions = /* @__PURE__ */ new Map();
|
|
1423
|
+
activeRequest = null;
|
|
1424
|
+
responseStarted = false;
|
|
1425
|
+
lineOpen = false;
|
|
1426
|
+
get busy() {
|
|
1427
|
+
return this.activeRequest !== null;
|
|
1428
|
+
}
|
|
1429
|
+
snapshot() {
|
|
1430
|
+
return {
|
|
1431
|
+
thread: this.thread,
|
|
1432
|
+
pendingActions: [...this.pendingActions.values()]
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
interrupt() {
|
|
1436
|
+
if (!this.activeRequest) return false;
|
|
1437
|
+
this.activeRequest.abort("user-stop");
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
endOpenLine() {
|
|
1441
|
+
if (this.lineOpen) this.options.io.write("\n");
|
|
1442
|
+
this.lineOpen = false;
|
|
1443
|
+
}
|
|
1444
|
+
renderAction(action) {
|
|
1445
|
+
this.endOpenLine();
|
|
1446
|
+
this.options.io.write(
|
|
1447
|
+
`
|
|
1448
|
+
${chalk2.yellow.bold("Approval required")} ${chalk2.dim(action.id.slice(0, 8))}
|
|
1449
|
+
`
|
|
1450
|
+
);
|
|
1451
|
+
this.options.io.write(`${chalk2.bold(action.title)} ${chalk2.yellow(`[${action.risk} risk]`)}
|
|
1452
|
+
`);
|
|
1453
|
+
if (action.summary) this.options.io.write(`${action.summary}
|
|
1454
|
+
`);
|
|
1455
|
+
const request2 = [action.method, action.path].filter(Boolean).join(" ");
|
|
1456
|
+
const technical = {
|
|
1457
|
+
operation: action.operation,
|
|
1458
|
+
...request2 ? { request: request2 } : {},
|
|
1459
|
+
arguments: action.arguments ?? null,
|
|
1460
|
+
preview: action.preview ?? null
|
|
1461
|
+
};
|
|
1462
|
+
this.options.io.write(`${chalk2.bold("Proposed action")}
|
|
1463
|
+
`);
|
|
1464
|
+
this.options.io.write(`${formatJson(technical)}
|
|
1465
|
+
`);
|
|
1466
|
+
this.options.io.write(
|
|
1467
|
+
chalk2.dim("Use /approve or /reject. The action will not run without approval.") + "\n"
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
renderEvent(event) {
|
|
1471
|
+
const payload = event.payload;
|
|
1472
|
+
if (event.type === "text_delta") {
|
|
1473
|
+
const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
|
|
1474
|
+
if (!delta) return;
|
|
1475
|
+
if (!this.responseStarted) {
|
|
1476
|
+
this.options.io.write(`${accent2.bold("TT")} `);
|
|
1477
|
+
this.responseStarted = true;
|
|
1478
|
+
this.lineOpen = true;
|
|
1479
|
+
}
|
|
1480
|
+
this.options.io.write(delta);
|
|
1481
|
+
this.lineOpen = !delta.endsWith("\n");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (event.type === "tool_call") {
|
|
1485
|
+
this.endOpenLine();
|
|
1486
|
+
const name = stringValue2(payload.name) ?? stringValue2(isRecord(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
1487
|
+
this.options.io.write(chalk2.dim(` \u25CB ${name}
|
|
1488
|
+
`));
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (event.type === "tool_result") {
|
|
1492
|
+
this.endOpenLine();
|
|
1493
|
+
const failed = payload.status === "error" || Boolean(payload.error);
|
|
1494
|
+
this.options.io.write(
|
|
1495
|
+
chalk2.dim(` ${failed ? chalk2.red("\u2717") : chalk2.green("\u2713")} Tool ${failed ? "failed" : "complete"}
|
|
1496
|
+
`)
|
|
1497
|
+
);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (event.type === "approval_required") {
|
|
1501
|
+
const action = actionFromPayload(payload);
|
|
1502
|
+
if (!action) return;
|
|
1503
|
+
this.pendingActions.set(action.id, action);
|
|
1504
|
+
this.renderAction(action);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
if (event.type === "action_started") {
|
|
1508
|
+
this.endOpenLine();
|
|
1509
|
+
this.options.io.write(chalk2.dim(" \u25CB Running approved action\u2026\n"));
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
if (event.type === "action_result") {
|
|
1513
|
+
this.endOpenLine();
|
|
1514
|
+
const failed = payload.status === "failed" || Boolean(payload.error);
|
|
1515
|
+
this.options.io.write(
|
|
1516
|
+
`${failed ? errorMark2() : successMark2()} Approved action ${failed ? "failed" : "completed"}.
|
|
1517
|
+
`
|
|
1518
|
+
);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
if (event.type === "error") {
|
|
1522
|
+
this.endOpenLine();
|
|
1523
|
+
const message = stringValue2(payload.message) ?? stringValue2(payload.error) ?? "The agent could not complete this request.";
|
|
1524
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1525
|
+
`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
async ensureThread() {
|
|
1529
|
+
if (!this.thread) this.thread = await this.options.client.createThread();
|
|
1530
|
+
return this.thread;
|
|
1531
|
+
}
|
|
1532
|
+
async runStream(operation) {
|
|
1533
|
+
if (this.activeRequest) {
|
|
1534
|
+
this.options.io.writeError(`${errorMark2()} A response is already running.
|
|
1535
|
+
`);
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const controller = new AbortController();
|
|
1539
|
+
this.activeRequest = controller;
|
|
1540
|
+
this.responseStarted = false;
|
|
1541
|
+
this.lineOpen = false;
|
|
1542
|
+
try {
|
|
1543
|
+
const result = await operation(controller.signal);
|
|
1544
|
+
this.endOpenLine();
|
|
1545
|
+
for (const action of result.actions) {
|
|
1546
|
+
this.pendingActions.set(action.id, action);
|
|
1547
|
+
}
|
|
1548
|
+
return result;
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
this.endOpenLine();
|
|
1551
|
+
if (controller.signal.aborted) {
|
|
1552
|
+
this.options.io.write(chalk2.dim("Response stopped.\n"));
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
throw error;
|
|
1556
|
+
} finally {
|
|
1557
|
+
this.activeRequest = null;
|
|
1558
|
+
this.responseStarted = false;
|
|
1559
|
+
this.lineOpen = false;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
async send(prompt) {
|
|
1563
|
+
const normalized = prompt.trim();
|
|
1564
|
+
if (!normalized) return null;
|
|
1565
|
+
const thread = await this.ensureThread();
|
|
1566
|
+
return await this.runStream(
|
|
1567
|
+
async (signal) => await this.options.client.runTurn(
|
|
1568
|
+
thread.id,
|
|
1569
|
+
normalized,
|
|
1570
|
+
(event) => this.renderEvent(event),
|
|
1571
|
+
signal
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
resolvePendingAction(idOrPrefix) {
|
|
1576
|
+
const candidates = [...this.pendingActions.values()].filter(
|
|
1577
|
+
(action) => idOrPrefix ? action.id.startsWith(idOrPrefix) : true
|
|
1578
|
+
);
|
|
1579
|
+
if (candidates.length === 0) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
idOrPrefix ? `No pending action matches ${idOrPrefix}.` : "There is no pending action."
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
if (candidates.length > 1) {
|
|
1585
|
+
throw new Error("More than one action is pending; include its ID or prefix.");
|
|
1586
|
+
}
|
|
1587
|
+
return candidates[0];
|
|
1588
|
+
}
|
|
1589
|
+
async approve(idOrPrefix) {
|
|
1590
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1591
|
+
const result = await this.runStream(
|
|
1592
|
+
async (signal) => await this.options.client.approveAction(
|
|
1593
|
+
action.id,
|
|
1594
|
+
(event) => this.renderEvent(event),
|
|
1595
|
+
signal
|
|
1596
|
+
)
|
|
1597
|
+
);
|
|
1598
|
+
if (result) this.pendingActions.delete(action.id);
|
|
1599
|
+
}
|
|
1600
|
+
async reject(idOrPrefix) {
|
|
1601
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1602
|
+
await this.options.client.rejectAction(action.id);
|
|
1603
|
+
this.pendingActions.delete(action.id);
|
|
1604
|
+
this.options.io.write(`${successMark2()} Action rejected.
|
|
1605
|
+
`);
|
|
1606
|
+
}
|
|
1607
|
+
async resolveThread(idOrPrefix) {
|
|
1608
|
+
const id = idOrPrefix.trim();
|
|
1609
|
+
if (!id) throw new Error("Usage: /resume <conversation-id>");
|
|
1610
|
+
if (id.length >= 32) return await this.options.client.getThread(id);
|
|
1611
|
+
const matches = (await this.options.client.listThreads()).filter((thread) => thread.id.startsWith(id));
|
|
1612
|
+
if (matches.length === 0) throw new Error(`No conversation matches ${id}.`);
|
|
1613
|
+
if (matches.length > 1) {
|
|
1614
|
+
throw new Error(`Conversation prefix ${id} is ambiguous.`);
|
|
1615
|
+
}
|
|
1616
|
+
return await this.options.client.getThread(matches[0].id);
|
|
1617
|
+
}
|
|
1618
|
+
async handleCommand(input) {
|
|
1619
|
+
const tokens = tokenizeShellInput(input);
|
|
1620
|
+
const command = tokens[0].slice(1).toLowerCase();
|
|
1621
|
+
const args = tokens.slice(1);
|
|
1622
|
+
switch (command) {
|
|
1623
|
+
case "help":
|
|
1624
|
+
this.options.io.write(helpText2());
|
|
1625
|
+
return "continue";
|
|
1626
|
+
case "new":
|
|
1627
|
+
if (args.length > 0) throw new Error("/new does not accept arguments.");
|
|
1628
|
+
this.thread = null;
|
|
1629
|
+
this.pendingActions.clear();
|
|
1630
|
+
this.options.io.write(`${successMark2()} New conversation ready.
|
|
1631
|
+
`);
|
|
1632
|
+
return "continue";
|
|
1633
|
+
case "threads": {
|
|
1634
|
+
if (args.length > 0) throw new Error("/threads does not accept arguments.");
|
|
1635
|
+
const threads = await this.options.client.listThreads();
|
|
1636
|
+
if (threads.length === 0) {
|
|
1637
|
+
this.options.io.write(chalk2.dim("No conversations yet.\n"));
|
|
1638
|
+
return "continue";
|
|
1639
|
+
}
|
|
1640
|
+
this.options.io.write(
|
|
1641
|
+
`${threads.slice(0, 20).map((thread) => formatThread(thread, this.thread?.id)).join("\n")}
|
|
1642
|
+
`
|
|
1643
|
+
);
|
|
1644
|
+
return "continue";
|
|
1645
|
+
}
|
|
1646
|
+
case "resume":
|
|
1647
|
+
if (args.length !== 1) throw new Error("Usage: /resume <conversation-id>");
|
|
1648
|
+
{
|
|
1649
|
+
const detail = await this.resolveThread(args[0]);
|
|
1650
|
+
this.thread = detail.thread;
|
|
1651
|
+
this.pendingActions.clear();
|
|
1652
|
+
for (const action of detail.actions) {
|
|
1653
|
+
if (action.status === "proposed") {
|
|
1654
|
+
this.pendingActions.set(action.id, action);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
this.options.io.write(
|
|
1659
|
+
`${successMark2()} Resumed ${this.thread.title} (${this.thread.id.slice(0, 8)}).
|
|
1660
|
+
`
|
|
1661
|
+
);
|
|
1662
|
+
return "continue";
|
|
1663
|
+
case "approve":
|
|
1664
|
+
if (args.length > 1) throw new Error("Usage: /approve [action-id]");
|
|
1665
|
+
await this.approve(args[0]);
|
|
1666
|
+
return "continue";
|
|
1667
|
+
case "reject":
|
|
1668
|
+
if (args.length > 1) throw new Error("Usage: /reject [action-id]");
|
|
1669
|
+
await this.reject(args[0]);
|
|
1670
|
+
return "continue";
|
|
1671
|
+
case "status":
|
|
1672
|
+
if (args.length > 0) throw new Error("/status does not accept arguments.");
|
|
1673
|
+
this.options.io.write(
|
|
1674
|
+
this.thread ? `${formatThread(this.thread, this.thread.id)}
|
|
1675
|
+
` : chalk2.dim("New conversation; send a message to create it.\n")
|
|
1676
|
+
);
|
|
1677
|
+
this.options.io.write(
|
|
1678
|
+
chalk2.dim(`${this.pendingActions.size} pending approval(s).
|
|
1679
|
+
`)
|
|
1680
|
+
);
|
|
1681
|
+
return "continue";
|
|
1682
|
+
case "clear":
|
|
1683
|
+
if (args.length > 0) throw new Error("/clear does not accept arguments.");
|
|
1684
|
+
this.options.io.clear();
|
|
1685
|
+
return "continue";
|
|
1686
|
+
case "exit":
|
|
1687
|
+
if (args.length > 0) throw new Error("/exit does not accept arguments.");
|
|
1688
|
+
return "exit";
|
|
1689
|
+
default:
|
|
1690
|
+
throw new Error(`Unknown agent command: /${command}. Use /help.`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
async handleLine(input) {
|
|
1694
|
+
try {
|
|
1695
|
+
const normalized = input.trim();
|
|
1696
|
+
if (!normalized) return "continue";
|
|
1697
|
+
if (/^(exit|quit)$/i.test(normalized)) return "exit";
|
|
1698
|
+
if (normalized.startsWith("/")) return await this.handleCommand(normalized);
|
|
1699
|
+
await this.send(normalized);
|
|
1700
|
+
return "continue";
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1703
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1704
|
+
`);
|
|
1705
|
+
return "continue";
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
// src/local-runner.ts
|
|
1711
|
+
import { constants as osConstants } from "os";
|
|
1712
|
+
import { randomUUID } from "crypto";
|
|
1713
|
+
import { createRequire } from "module";
|
|
1714
|
+
import { spawn } from "child_process";
|
|
1715
|
+
import {
|
|
1716
|
+
readFile as readFile2,
|
|
1717
|
+
rm,
|
|
1718
|
+
stat as stat3,
|
|
1719
|
+
writeFile
|
|
1720
|
+
} from "fs/promises";
|
|
1721
|
+
import {
|
|
1722
|
+
basename as basename3,
|
|
1723
|
+
dirname as dirname2,
|
|
1724
|
+
isAbsolute as isAbsolute3,
|
|
1725
|
+
join as join3,
|
|
1726
|
+
resolve as resolve3
|
|
1727
|
+
} from "path";
|
|
1728
|
+
|
|
1729
|
+
// src/base-models.ts
|
|
1730
|
+
var SUPPORTED_BASE_MODELS = [
|
|
1731
|
+
"google/gemma-4-E2B-it",
|
|
1732
|
+
"google/gemma-4-E4B-it",
|
|
1733
|
+
"Qwen/Qwen3.5-2B",
|
|
1734
|
+
"Qwen/Qwen3-VL-2B-Instruct",
|
|
1735
|
+
"Qwen/Qwen3.5-4B",
|
|
1736
|
+
"meta-llama/Llama-3.2-3B-Instruct",
|
|
1737
|
+
"microsoft/Phi-4-mini-instruct",
|
|
1738
|
+
"ibm-granite/granite-3.3-2b-instruct",
|
|
1739
|
+
"bigcode/starcoder2-3b"
|
|
1286
1740
|
];
|
|
1287
|
-
var
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1741
|
+
var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
|
|
1742
|
+
for (const model of SUPPORTED_BASE_MODELS) {
|
|
1743
|
+
BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
|
|
1744
|
+
}
|
|
1745
|
+
for (const [alias, model] of [
|
|
1746
|
+
["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
|
|
1747
|
+
["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
|
|
1748
|
+
["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
|
|
1749
|
+
["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
|
|
1750
|
+
["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
|
|
1751
|
+
["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
|
|
1752
|
+
["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
|
|
1753
|
+
["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
|
|
1754
|
+
["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
|
|
1755
|
+
["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1756
|
+
["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1757
|
+
["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1758
|
+
["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
|
|
1759
|
+
["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
|
|
1760
|
+
["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
|
|
1761
|
+
["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1762
|
+
["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1763
|
+
["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1764
|
+
["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1765
|
+
["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1766
|
+
["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1767
|
+
["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1768
|
+
["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
|
|
1769
|
+
["starcoder2-3b", "bigcode/starcoder2-3b"]
|
|
1770
|
+
]) {
|
|
1771
|
+
BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
|
|
1772
|
+
}
|
|
1773
|
+
var UnsupportedBaseModelError = class extends Error {
|
|
1774
|
+
constructor(model) {
|
|
1775
|
+
const supported = SUPPORTED_BASE_MODELS.join(", ");
|
|
1776
|
+
super(
|
|
1777
|
+
`Unsupported base_model "${String(model)}". Supported base models: ${supported}`
|
|
1778
|
+
);
|
|
1779
|
+
this.name = "UnsupportedBaseModelError";
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
function canonicalizeBaseModel(model) {
|
|
1783
|
+
if (typeof model !== "string" || !model.trim()) {
|
|
1784
|
+
throw new UnsupportedBaseModelError(model);
|
|
1785
|
+
}
|
|
1786
|
+
const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
|
|
1787
|
+
if (!canonical) {
|
|
1788
|
+
throw new UnsupportedBaseModelError(model);
|
|
1789
|
+
}
|
|
1790
|
+
return canonical;
|
|
1791
|
+
}
|
|
1792
|
+
function canonicalizeSpecBaseModel(body) {
|
|
1793
|
+
if ("base_model" in body && body.base_model !== void 0) {
|
|
1794
|
+
return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
|
|
1795
|
+
}
|
|
1796
|
+
return body;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// src/project-spec.ts
|
|
1800
|
+
var CLOUD_SPEC_KEYS = [
|
|
1801
|
+
"name",
|
|
1802
|
+
"description",
|
|
1803
|
+
"base_model",
|
|
1804
|
+
"system_prompt",
|
|
1805
|
+
"guidelines",
|
|
1806
|
+
"constraints",
|
|
1807
|
+
"examples",
|
|
1808
|
+
"eval_cases"
|
|
1297
1809
|
];
|
|
1298
|
-
|
|
1299
|
-
|
|
1810
|
+
var LOCAL_ONLY_SPEC_KEYS = [
|
|
1811
|
+
"hyperparameters",
|
|
1812
|
+
"dataset_prebuilt"
|
|
1813
|
+
];
|
|
1814
|
+
var LOCAL_SPEC_KEYS = [
|
|
1815
|
+
"id",
|
|
1816
|
+
"name",
|
|
1817
|
+
"description",
|
|
1818
|
+
"system_prompt",
|
|
1819
|
+
"guidelines",
|
|
1820
|
+
"constraints",
|
|
1821
|
+
"base_model",
|
|
1822
|
+
"examples",
|
|
1823
|
+
...LOCAL_ONLY_SPEC_KEYS
|
|
1824
|
+
];
|
|
1825
|
+
var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
|
|
1826
|
+
var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
|
|
1827
|
+
var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
|
|
1828
|
+
var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
|
|
1829
|
+
...CLOUD_SPEC_KEYS,
|
|
1830
|
+
...LOCAL_SPEC_KEYS
|
|
1831
|
+
]);
|
|
1832
|
+
function projectSpec(raw, supportedKeys) {
|
|
1833
|
+
const body = {};
|
|
1834
|
+
const droppedKeys = [];
|
|
1835
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1836
|
+
if (supportedKeys.has(key)) {
|
|
1837
|
+
body[key] = value;
|
|
1838
|
+
} else {
|
|
1839
|
+
droppedKeys.push(key);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return {
|
|
1843
|
+
body: canonicalizeSpecBaseModel(body),
|
|
1844
|
+
droppedKeys
|
|
1845
|
+
};
|
|
1300
1846
|
}
|
|
1301
|
-
function
|
|
1302
|
-
return
|
|
1847
|
+
function projectCloudSpec(raw) {
|
|
1848
|
+
return projectSpec(raw, CLOUD_SPEC_KEY_SET);
|
|
1303
1849
|
}
|
|
1304
|
-
function
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1850
|
+
function projectLocalSpec(raw) {
|
|
1851
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET);
|
|
1852
|
+
}
|
|
1853
|
+
function hasLocalOnlySpecFields(raw) {
|
|
1854
|
+
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
1855
|
+
}
|
|
1856
|
+
function unknownProjectSpecKeys(raw) {
|
|
1857
|
+
return Object.keys(raw).filter(
|
|
1858
|
+
(key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// src/output.ts
|
|
1863
|
+
import chalk3 from "chalk";
|
|
1864
|
+
import Table from "cli-table3";
|
|
1865
|
+
var jsonMode = false;
|
|
1866
|
+
function setJsonMode(enabled) {
|
|
1867
|
+
jsonMode = enabled;
|
|
1868
|
+
}
|
|
1869
|
+
function isJsonMode() {
|
|
1870
|
+
return jsonMode;
|
|
1871
|
+
}
|
|
1872
|
+
function printJson(data) {
|
|
1873
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1874
|
+
}
|
|
1875
|
+
function printTable(headers, rows2, meta) {
|
|
1876
|
+
const table = new Table({
|
|
1877
|
+
head: headers.map((h) => chalk3.bold.cyan(h)),
|
|
1878
|
+
style: { head: [], border: [] }
|
|
1309
1879
|
});
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1880
|
+
for (const row of rows2) {
|
|
1881
|
+
table.push(row);
|
|
1882
|
+
}
|
|
1883
|
+
console.log(table.toString());
|
|
1884
|
+
if (meta) {
|
|
1885
|
+
const totalPages = Math.ceil(meta.total / meta.per_page);
|
|
1886
|
+
console.log(
|
|
1887
|
+
chalk3.dim(
|
|
1888
|
+
`
|
|
1889
|
+
Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1315
1892
|
}
|
|
1316
|
-
return groups;
|
|
1317
1893
|
}
|
|
1318
|
-
function
|
|
1319
|
-
const
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
1894
|
+
function printDetail(fields) {
|
|
1895
|
+
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
1896
|
+
for (const [label, value] of fields) {
|
|
1897
|
+
const paddedLabel = label.padEnd(maxLabel);
|
|
1898
|
+
console.log(`${chalk3.bold.cyan(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
|
|
1324
1899
|
}
|
|
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
1900
|
}
|
|
1333
|
-
function
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
1901
|
+
function printSuccess(message) {
|
|
1902
|
+
console.log(chalk3.green("\u2713") + " " + message);
|
|
1903
|
+
}
|
|
1904
|
+
function printWarning(message) {
|
|
1905
|
+
console.log(chalk3.yellow("!") + " " + message);
|
|
1906
|
+
}
|
|
1907
|
+
function printError(message) {
|
|
1908
|
+
console.error(chalk3.red("\u2717") + " " + message);
|
|
1909
|
+
}
|
|
1910
|
+
function reportError(err) {
|
|
1911
|
+
const isApi = err instanceof ApiError;
|
|
1912
|
+
const message = err?.message || String(err);
|
|
1913
|
+
if (jsonMode) {
|
|
1914
|
+
printJson({
|
|
1915
|
+
error: {
|
|
1916
|
+
status: isApi ? err.status : null,
|
|
1917
|
+
code: isApi ? err.code : "CLI_ERROR",
|
|
1918
|
+
message
|
|
1919
|
+
}
|
|
1353
1920
|
});
|
|
1354
|
-
|
|
1921
|
+
} else if (isApi) {
|
|
1922
|
+
printError(`[${err.status}] ${message}`);
|
|
1923
|
+
} else {
|
|
1924
|
+
printError(message);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function formatDate(iso) {
|
|
1928
|
+
if (!iso) return "\u2014";
|
|
1929
|
+
return new Date(iso).toLocaleString();
|
|
1930
|
+
}
|
|
1931
|
+
function formatStatus(status2) {
|
|
1932
|
+
const map = {
|
|
1933
|
+
completed: chalk3.green,
|
|
1934
|
+
running: chalk3.blue,
|
|
1935
|
+
training: chalk3.blue,
|
|
1936
|
+
evaluating: chalk3.blue,
|
|
1937
|
+
preparing: chalk3.yellow,
|
|
1938
|
+
pending: chalk3.yellow,
|
|
1939
|
+
uploading: chalk3.yellow,
|
|
1940
|
+
failed: chalk3.red,
|
|
1941
|
+
cancelled: chalk3.dim,
|
|
1942
|
+
validated: chalk3.green,
|
|
1943
|
+
invalid: chalk3.red
|
|
1355
1944
|
};
|
|
1945
|
+
const colorFn = map[status2] || chalk3.white;
|
|
1946
|
+
return colorFn(status2);
|
|
1947
|
+
}
|
|
1948
|
+
function truncate(str, max) {
|
|
1949
|
+
if (str.length <= max) return str;
|
|
1950
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
1951
|
+
}
|
|
1952
|
+
function shortId(id) {
|
|
1953
|
+
return id.slice(0, 8);
|
|
1356
1954
|
}
|
|
1357
1955
|
|
|
1358
|
-
// src/
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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 };
|
|
1956
|
+
// src/local-output.ts
|
|
1957
|
+
function isRecord2(value) {
|
|
1958
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1959
|
+
}
|
|
1960
|
+
function text(value) {
|
|
1961
|
+
if (typeof value === "string") return value;
|
|
1962
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1963
|
+
return String(value);
|
|
1378
1964
|
}
|
|
1965
|
+
return void 0;
|
|
1379
1966
|
}
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
return typeof field === "string" && field.trim() ? field : void 0;
|
|
1967
|
+
function number(value) {
|
|
1968
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1383
1969
|
}
|
|
1384
|
-
function
|
|
1385
|
-
|
|
1386
|
-
if (value.startsWith("~/")) return resolve2(homeDirectory, value.slice(2));
|
|
1387
|
-
return isAbsolute2(value) ? resolve2(value) : resolve2(baseDirectory, value);
|
|
1970
|
+
function record2(value) {
|
|
1971
|
+
return isRecord2(value) ? value : void 0;
|
|
1388
1972
|
}
|
|
1389
|
-
function
|
|
1390
|
-
return
|
|
1973
|
+
function rows(value) {
|
|
1974
|
+
return Array.isArray(value) ? value.filter(isRecord2) : [];
|
|
1391
1975
|
}
|
|
1392
|
-
function
|
|
1393
|
-
const
|
|
1394
|
-
return
|
|
1976
|
+
function displayId(value) {
|
|
1977
|
+
const id = text(value);
|
|
1978
|
+
return id ? shortId(id) : "\u2014";
|
|
1979
|
+
}
|
|
1980
|
+
function displayNumber(value, digits = 4) {
|
|
1981
|
+
const parsed = number(value);
|
|
1982
|
+
if (parsed === void 0) return void 0;
|
|
1983
|
+
return parsed.toFixed(digits).replace(/\.?0+$/, "");
|
|
1984
|
+
}
|
|
1985
|
+
function displayBytes(value) {
|
|
1986
|
+
const bytes = number(value);
|
|
1987
|
+
if (bytes === void 0 || bytes < 0) return void 0;
|
|
1988
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
1989
|
+
let amount = bytes;
|
|
1990
|
+
let unit = 0;
|
|
1991
|
+
while (amount >= 1024 && unit < units.length - 1) {
|
|
1992
|
+
amount /= 1024;
|
|
1993
|
+
unit += 1;
|
|
1994
|
+
}
|
|
1995
|
+
const precision = amount >= 10 || unit === 0 ? 0 : 1;
|
|
1996
|
+
return `${amount.toFixed(precision)} ${units[unit]}`;
|
|
1997
|
+
}
|
|
1998
|
+
function displayCommand(value) {
|
|
1999
|
+
if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
|
|
2000
|
+
return void 0;
|
|
2001
|
+
}
|
|
2002
|
+
return value.map(
|
|
2003
|
+
(part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
|
|
2004
|
+
).join(" ");
|
|
2005
|
+
}
|
|
2006
|
+
function status(value) {
|
|
2007
|
+
const parsed = text(value) ?? "unknown";
|
|
2008
|
+
return formatStatus(parsed);
|
|
2009
|
+
}
|
|
2010
|
+
function gateFrom(value) {
|
|
2011
|
+
return record2(record2(value)?.general_regression);
|
|
2012
|
+
}
|
|
2013
|
+
function renderGate(value) {
|
|
2014
|
+
const gate = gateFrom(value);
|
|
2015
|
+
if (!gate) {
|
|
2016
|
+
printWarning(
|
|
2017
|
+
"General regression was not run; this model cannot be activated yet."
|
|
2018
|
+
);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
if (gate.passed === true) {
|
|
2022
|
+
printSuccess("General regression gate passed");
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
printWarning(
|
|
2026
|
+
"The local run completed, but the general regression gate failed."
|
|
2027
|
+
);
|
|
2028
|
+
const failures = Array.isArray(gate.failures) ? gate.failures.filter(
|
|
2029
|
+
(failure) => typeof failure === "string"
|
|
2030
|
+
) : [];
|
|
2031
|
+
for (const failure of failures) printWarning(failure);
|
|
2032
|
+
}
|
|
2033
|
+
function renderDoctor(value) {
|
|
2034
|
+
const checks = rows(value.checks);
|
|
2035
|
+
printTable(
|
|
2036
|
+
["Check", "Status", "Details"],
|
|
2037
|
+
checks.map((check) => [
|
|
2038
|
+
text(check.name) ?? "unknown",
|
|
2039
|
+
check.ok === true ? formatStatus("completed") : formatStatus("failed"),
|
|
2040
|
+
truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
|
|
2041
|
+
])
|
|
2042
|
+
);
|
|
2043
|
+
if (value.ok === true) {
|
|
2044
|
+
printSuccess("Local environment is ready");
|
|
2045
|
+
} else {
|
|
2046
|
+
printError("Local environment needs attention");
|
|
2047
|
+
}
|
|
2048
|
+
if (value.config_path) {
|
|
2049
|
+
printDetail([["Config", text(value.config_path)]]);
|
|
2050
|
+
}
|
|
1395
2051
|
}
|
|
1396
|
-
function
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
2052
|
+
function renderInit(value) {
|
|
2053
|
+
printSuccess("Created local Tuned Tensor project");
|
|
2054
|
+
printDetail([
|
|
2055
|
+
["Path", text(value.path)],
|
|
2056
|
+
["Name", text(value.name)],
|
|
2057
|
+
["ID", text(value.id)],
|
|
2058
|
+
["Base model", text(value.base_model)],
|
|
2059
|
+
["Config", text(value.config_path)]
|
|
2060
|
+
]);
|
|
1401
2061
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
|
|
2062
|
+
function renderValidate(value) {
|
|
2063
|
+
printSuccess("Local behavior spec is valid");
|
|
2064
|
+
printDetail([
|
|
2065
|
+
["Input", text(value.input_path)],
|
|
2066
|
+
["Config", text(value.config_path)],
|
|
2067
|
+
["Spec", text(value.behavior_spec_id)],
|
|
2068
|
+
["Base model", text(value.base_model)],
|
|
2069
|
+
["Dataset", text(value.dataset_format)],
|
|
2070
|
+
["Artifacts", text(value.artifact_root)],
|
|
2071
|
+
["Store", text(value.store_root)],
|
|
2072
|
+
["Dry run", text(value.dry_run)]
|
|
2073
|
+
]);
|
|
1405
2074
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
2075
|
+
function renderRun(value) {
|
|
2076
|
+
const runStatus = text(value.status) ?? "unknown";
|
|
2077
|
+
if (runStatus === "completed") {
|
|
2078
|
+
printSuccess("Local run completed");
|
|
2079
|
+
} else {
|
|
2080
|
+
printWarning(`Local run finished with status ${runStatus}`);
|
|
1411
2081
|
}
|
|
2082
|
+
const comparison = record2(value.comparison);
|
|
2083
|
+
printDetail([
|
|
2084
|
+
["Run", text(value.run_id)],
|
|
2085
|
+
["Status", status(runStatus)],
|
|
2086
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2087
|
+
["Report", text(value.report_path)],
|
|
2088
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2089
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2090
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
|
|
2091
|
+
]);
|
|
2092
|
+
renderGate(value);
|
|
1412
2093
|
}
|
|
1413
|
-
|
|
1414
|
-
const
|
|
1415
|
-
if (
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
1419
|
-
}
|
|
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;
|
|
2094
|
+
function renderRunsList(value) {
|
|
2095
|
+
const runRows = rows(value);
|
|
2096
|
+
if (runRows.length === 0) {
|
|
2097
|
+
printWarning("No local runs found.");
|
|
2098
|
+
return;
|
|
1427
2099
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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];
|
|
2100
|
+
printTable(
|
|
2101
|
+
["Run", "Spec", "Status", "Stage", "Model", "Updated"],
|
|
2102
|
+
runRows.map((run) => [
|
|
2103
|
+
displayId(run.id),
|
|
2104
|
+
truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
|
|
2105
|
+
status(run.status),
|
|
2106
|
+
text(run.current_stage) ?? "\u2014",
|
|
2107
|
+
displayId(run.model_id),
|
|
2108
|
+
formatDate(text(run.updated_at))
|
|
2109
|
+
])
|
|
2110
|
+
);
|
|
1448
2111
|
}
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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.`);
|
|
2112
|
+
function renderRunEvents(value) {
|
|
2113
|
+
const eventRows = rows(value);
|
|
2114
|
+
if (eventRows.length === 0) {
|
|
2115
|
+
printWarning("No local run events found.");
|
|
2116
|
+
return;
|
|
1496
2117
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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
|
-
};
|
|
2118
|
+
printTable(
|
|
2119
|
+
["Time", "Stage", "Status", "Message"],
|
|
2120
|
+
eventRows.map((event) => [
|
|
2121
|
+
formatDate(text(event.occurred_at)),
|
|
2122
|
+
text(event.stage) ?? "\u2014",
|
|
2123
|
+
status(event.status),
|
|
2124
|
+
truncate(text(event.message) ?? "\u2014", 80)
|
|
2125
|
+
])
|
|
2126
|
+
);
|
|
1525
2127
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
2128
|
+
function renderRunRecord(value) {
|
|
2129
|
+
printDetail([
|
|
2130
|
+
["Run", text(value.id) ?? text(value.run_id)],
|
|
2131
|
+
["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
|
|
2132
|
+
["Status", status(value.status)],
|
|
2133
|
+
["Stage", text(value.current_stage)],
|
|
2134
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2135
|
+
["Base model", text(value.base_model)],
|
|
2136
|
+
["Report", text(value.report_path)],
|
|
2137
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2138
|
+
["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
|
|
2139
|
+
]);
|
|
1528
2140
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
2141
|
+
function renderRunReport(value) {
|
|
2142
|
+
const baseline = record2(value.baseline);
|
|
2143
|
+
const candidate = record2(value.candidate);
|
|
2144
|
+
const comparison = record2(value.comparison);
|
|
2145
|
+
const metadata = record2(value.run_metadata);
|
|
2146
|
+
printDetail([
|
|
2147
|
+
["Run", text(value.run_id)],
|
|
2148
|
+
["Status", status(value.status)],
|
|
2149
|
+
["Base model", text(value.base_model)],
|
|
2150
|
+
["Tuned model", text(value.fine_tuned_model_id)],
|
|
2151
|
+
["Training examples", text(metadata?.training_example_count)],
|
|
2152
|
+
["Evaluation examples", text(metadata?.eval_examples_used)],
|
|
2153
|
+
["Baseline score", displayNumber(baseline?.avg_score)],
|
|
2154
|
+
["Candidate score", displayNumber(candidate?.avg_score)],
|
|
2155
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2156
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
|
|
2157
|
+
["Regressions", text(comparison?.regressions)],
|
|
2158
|
+
["Improvements", text(comparison?.improvements)]
|
|
2159
|
+
]);
|
|
2160
|
+
renderGate(value);
|
|
1538
2161
|
}
|
|
1539
|
-
function
|
|
1540
|
-
const
|
|
1541
|
-
const
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2162
|
+
function renderRunComparison(value) {
|
|
2163
|
+
const runA = record2(value.run_a);
|
|
2164
|
+
const runB = record2(value.run_b);
|
|
2165
|
+
const shared = record2(value.shared);
|
|
2166
|
+
const sharedA = record2(shared?.run_a);
|
|
2167
|
+
const sharedB = record2(shared?.run_b);
|
|
2168
|
+
printTable(
|
|
2169
|
+
["Run", "Candidate score", "Token F1", "Examples"],
|
|
2170
|
+
[
|
|
2171
|
+
[
|
|
2172
|
+
displayId(runA?.run_id),
|
|
2173
|
+
displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
|
|
2174
|
+
displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
|
|
2175
|
+
text(shared?.examples) ?? "0"
|
|
2176
|
+
],
|
|
2177
|
+
[
|
|
2178
|
+
displayId(runB?.run_id),
|
|
2179
|
+
displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
|
|
2180
|
+
displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
|
|
2181
|
+
text(shared?.examples) ?? "0"
|
|
2182
|
+
]
|
|
2183
|
+
]
|
|
2184
|
+
);
|
|
2185
|
+
const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
|
|
2186
|
+
for (const note of notes) printWarning(note);
|
|
1558
2187
|
}
|
|
1559
|
-
function
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
-
);
|
|
2188
|
+
function renderModelsList(value) {
|
|
2189
|
+
const modelRows = rows(value);
|
|
2190
|
+
if (modelRows.length === 0) {
|
|
2191
|
+
printWarning("No local models found.");
|
|
2192
|
+
return;
|
|
1577
2193
|
}
|
|
1578
|
-
|
|
2194
|
+
printTable(
|
|
2195
|
+
["Model", "Base model", "Run", "Provider", "Created"],
|
|
2196
|
+
modelRows.map((model) => [
|
|
2197
|
+
displayId(model.id),
|
|
2198
|
+
truncate(text(model.base_model) ?? "\u2014", 32),
|
|
2199
|
+
displayId(model.run_id),
|
|
2200
|
+
text(model.provider) ?? "\u2014",
|
|
2201
|
+
formatDate(text(model.created_at))
|
|
2202
|
+
])
|
|
2203
|
+
);
|
|
1579
2204
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2205
|
+
function renderModelRecord(value) {
|
|
2206
|
+
const model = record2(value.model) ?? value;
|
|
2207
|
+
const artifact = record2(value.artifact);
|
|
2208
|
+
const integrity = record2(value.integrity);
|
|
2209
|
+
printDetail([
|
|
2210
|
+
["Model", text(model.id) ?? text(value.active)],
|
|
2211
|
+
["Base model", text(model.base_model)],
|
|
2212
|
+
["Run", text(model.run_id)],
|
|
2213
|
+
["Provider", text(model.provider)],
|
|
2214
|
+
["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
|
|
2215
|
+
["Manifest", text(value.manifest_path)],
|
|
2216
|
+
["Verified files", text(integrity?.checked)],
|
|
2217
|
+
["Created", formatDate(text(model.created_at))]
|
|
2218
|
+
]);
|
|
2219
|
+
}
|
|
2220
|
+
function renderModelPrefetch(value) {
|
|
2221
|
+
printSuccess(
|
|
2222
|
+
value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
|
|
1591
2223
|
);
|
|
2224
|
+
printDetail([
|
|
2225
|
+
["Base model", text(value.base_model)],
|
|
2226
|
+
["Revision", text(value.snapshot_revision)],
|
|
2227
|
+
["Snapshot", text(value.snapshot_path)],
|
|
2228
|
+
["Files", text(value.file_count)],
|
|
2229
|
+
["Size", displayBytes(value.size_bytes)],
|
|
2230
|
+
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
2231
|
+
]);
|
|
1592
2232
|
}
|
|
1593
|
-
function
|
|
1594
|
-
|
|
1595
|
-
|
|
2233
|
+
function renderActiveModel(value) {
|
|
2234
|
+
const active = text(value.active) ?? "base";
|
|
2235
|
+
const pointer = record2(value.pointer);
|
|
2236
|
+
if (pointer?.action === "activate") {
|
|
2237
|
+
printSuccess(`Activated local model ${active}`);
|
|
2238
|
+
} else if (pointer?.action === "rollback") {
|
|
2239
|
+
printSuccess(`Rolled back to ${active}`);
|
|
1596
2240
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
2241
|
+
printDetail([
|
|
2242
|
+
["Active", active],
|
|
2243
|
+
["Run", text(pointer?.run_id)],
|
|
2244
|
+
["Previous", text(pointer?.previous_model_id) ?? "base"],
|
|
2245
|
+
["Changed", formatDate(text(pointer?.activated_at))],
|
|
2246
|
+
["Manifest", text(value.manifest_path)]
|
|
2247
|
+
]);
|
|
2248
|
+
}
|
|
2249
|
+
function renderServePlan(value) {
|
|
2250
|
+
printSuccess("Local serving plan is valid");
|
|
2251
|
+
printDetail([
|
|
2252
|
+
["Model", text(value.model_id) ?? text(value.base_model)],
|
|
2253
|
+
["URL", text(value.url)],
|
|
2254
|
+
["Artifact", text(value.artifact_path)],
|
|
2255
|
+
["Manifest", text(value.manifest_path)],
|
|
2256
|
+
["Command", displayCommand(value.command)]
|
|
2257
|
+
]);
|
|
2258
|
+
}
|
|
2259
|
+
function renderKnownJson(args, value) {
|
|
2260
|
+
const command = args[0];
|
|
2261
|
+
const subcommand = args[1];
|
|
2262
|
+
if (command === "doctor" && isRecord2(value)) {
|
|
2263
|
+
renderDoctor(value);
|
|
2264
|
+
return true;
|
|
1599
2265
|
}
|
|
1600
|
-
if (
|
|
1601
|
-
|
|
2266
|
+
if (command === "init" && isRecord2(value)) {
|
|
2267
|
+
renderInit(value);
|
|
2268
|
+
return true;
|
|
1602
2269
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
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;
|
|
2270
|
+
if (command === "validate" && isRecord2(value)) {
|
|
2271
|
+
renderValidate(value);
|
|
2272
|
+
return true;
|
|
2273
|
+
}
|
|
2274
|
+
if (command === "run" && isRecord2(value)) {
|
|
2275
|
+
renderRun(value);
|
|
2276
|
+
return true;
|
|
2277
|
+
}
|
|
2278
|
+
if (command === "runs") {
|
|
2279
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2280
|
+
renderRunsList(value);
|
|
2281
|
+
return true;
|
|
1659
2282
|
}
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1664
|
-
}
|
|
1665
|
-
token += escaped;
|
|
1666
|
-
tokenStarted = true;
|
|
1667
|
-
index += 1;
|
|
1668
|
-
continue;
|
|
2283
|
+
if (subcommand === "events" && Array.isArray(value)) {
|
|
2284
|
+
renderRunEvents(value);
|
|
2285
|
+
return true;
|
|
1669
2286
|
}
|
|
1670
|
-
if (
|
|
1671
|
-
|
|
1672
|
-
|
|
2287
|
+
if (subcommand === "get" && isRecord2(value)) {
|
|
2288
|
+
renderRunRecord(value);
|
|
2289
|
+
return true;
|
|
1673
2290
|
}
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1676
|
-
|
|
2291
|
+
if (subcommand === "report" && isRecord2(value)) {
|
|
2292
|
+
renderRunReport(value);
|
|
2293
|
+
return true;
|
|
1677
2294
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
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`.");
|
|
2295
|
+
if (subcommand === "compare" && isRecord2(value)) {
|
|
2296
|
+
renderRunComparison(value);
|
|
2297
|
+
return true;
|
|
1696
2298
|
}
|
|
1697
2299
|
}
|
|
1698
|
-
if (
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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.`);
|
|
2300
|
+
if (command === "models") {
|
|
2301
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2302
|
+
renderModelsList(value);
|
|
2303
|
+
return true;
|
|
1705
2304
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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;
|
|
2305
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord2(value)) {
|
|
2306
|
+
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
2307
|
+
renderModelRecord(value);
|
|
2308
|
+
return true;
|
|
2309
|
+
}
|
|
2310
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord2(value)) {
|
|
2311
|
+
renderModelPrefetch(value);
|
|
2312
|
+
return true;
|
|
2313
|
+
}
|
|
2314
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord2(value)) {
|
|
2315
|
+
renderActiveModel(value);
|
|
2316
|
+
return true;
|
|
2317
|
+
}
|
|
2318
|
+
if (subcommand === "serve" && isRecord2(value)) {
|
|
2319
|
+
renderServePlan(value);
|
|
2320
|
+
return true;
|
|
1736
2321
|
}
|
|
1737
2322
|
}
|
|
1738
|
-
|
|
2323
|
+
if (command === "serve" && isRecord2(value)) {
|
|
2324
|
+
renderServePlan(value);
|
|
2325
|
+
return true;
|
|
2326
|
+
}
|
|
2327
|
+
return false;
|
|
1739
2328
|
}
|
|
1740
|
-
function
|
|
1741
|
-
|
|
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);
|
|
2329
|
+
function lastNonEmptyLine(value) {
|
|
2330
|
+
return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
|
|
1745
2331
|
}
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
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
|
-
);
|
|
2332
|
+
function adaptLocalCliText(value) {
|
|
2333
|
+
return value.replace(
|
|
2334
|
+
/(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
|
|
2335
|
+
"tt local"
|
|
2336
|
+
).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
|
|
1757
2337
|
}
|
|
1758
|
-
function
|
|
1759
|
-
const
|
|
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
|
-
}
|
|
2338
|
+
function localCliErrorEnvelope(payload) {
|
|
2339
|
+
const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
|
|
1775
2340
|
return {
|
|
1776
|
-
|
|
1777
|
-
|
|
2341
|
+
error: {
|
|
2342
|
+
status: null,
|
|
2343
|
+
code: "LOCAL_CLI_ERROR",
|
|
2344
|
+
message: adaptLocalCliText(rawMessage),
|
|
2345
|
+
exit_code: payload.exitCode,
|
|
2346
|
+
signal: payload.signal
|
|
2347
|
+
}
|
|
1778
2348
|
};
|
|
1779
2349
|
}
|
|
1780
|
-
function
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
if (args.length > 0) {
|
|
1801
|
-
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
2350
|
+
function localCliTextEnvelope(payload) {
|
|
2351
|
+
const output = adaptLocalCliText(payload.stdout).trim();
|
|
2352
|
+
if (payload.args[0] === "info") {
|
|
2353
|
+
const lines = output.split(/\r?\n/);
|
|
2354
|
+
const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
|
|
2355
|
+
const fields = Object.fromEntries(
|
|
2356
|
+
lines.slice(1).flatMap((line) => {
|
|
2357
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
2358
|
+
return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
|
|
2359
|
+
})
|
|
2360
|
+
);
|
|
2361
|
+
if (heading) {
|
|
2362
|
+
return {
|
|
2363
|
+
data: {
|
|
2364
|
+
name: heading[1],
|
|
2365
|
+
description: heading[2],
|
|
2366
|
+
...fields
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
1802
2370
|
}
|
|
2371
|
+
return { data: { output } };
|
|
1803
2372
|
}
|
|
1804
|
-
function
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
-
}
|
|
2373
|
+
function renderLocalOutput(payload, options = {}) {
|
|
2374
|
+
if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
|
|
2375
|
+
return;
|
|
1824
2376
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
2377
|
+
const output = options.stdout ?? process.stdout;
|
|
2378
|
+
if (options.jsonMode) {
|
|
2379
|
+
if (payload.hasJson) {
|
|
2380
|
+
output.write(payload.stdout);
|
|
2381
|
+
return;
|
|
1830
2382
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2383
|
+
if (payload.exitCode !== 0 || payload.errorMessage) {
|
|
2384
|
+
output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
|
|
2385
|
+
`);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
|
|
2389
|
+
`);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (payload.droppedSpecKeys.length > 0) {
|
|
2393
|
+
printWarning(
|
|
2394
|
+
`Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
|
|
2398
|
+
if (payload.hasJson) {
|
|
2399
|
+
printJson(payload.json);
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
|
|
2403
|
+
if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
|
|
2404
|
+
printError(
|
|
2405
|
+
payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
|
|
2406
|
+
);
|
|
1835
2407
|
}
|
|
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
2408
|
}
|
|
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
2409
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2410
|
+
// src/local-runner.ts
|
|
2411
|
+
var localRequire = createRequire(import.meta.url);
|
|
2412
|
+
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
2413
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
2414
|
+
"--config",
|
|
2415
|
+
"--name",
|
|
2416
|
+
"--model",
|
|
2417
|
+
"--output",
|
|
2418
|
+
"--profile",
|
|
2419
|
+
"--host",
|
|
2420
|
+
"--port",
|
|
2421
|
+
"--device",
|
|
2422
|
+
"--max-tokens",
|
|
2423
|
+
"--temperature",
|
|
2424
|
+
"--top-p",
|
|
2425
|
+
"--max-concurrent-requests",
|
|
2426
|
+
"--spec",
|
|
2427
|
+
"--api-key-env"
|
|
2428
|
+
]);
|
|
2429
|
+
function isOptionWithInlineValue(value) {
|
|
2430
|
+
const equals = value.indexOf("=");
|
|
2431
|
+
return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
|
|
1866
2432
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2433
|
+
function positionalIndices(args, start) {
|
|
2434
|
+
const indices = [];
|
|
2435
|
+
let optionsEnded = false;
|
|
2436
|
+
for (let index = start; index < args.length; index += 1) {
|
|
2437
|
+
const value = args[index];
|
|
2438
|
+
if (!optionsEnded && value === "--") {
|
|
2439
|
+
optionsEnded = true;
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
if (!optionsEnded && value.startsWith("-")) {
|
|
2443
|
+
if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
indices.push(index);
|
|
1878
2447
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2448
|
+
return indices;
|
|
2449
|
+
}
|
|
2450
|
+
function optionValueArgument(args, name) {
|
|
2451
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2452
|
+
const value = args[index];
|
|
2453
|
+
if (value === name) {
|
|
2454
|
+
const path = args[index + 1];
|
|
2455
|
+
if (!path) return null;
|
|
2456
|
+
return {
|
|
2457
|
+
path,
|
|
2458
|
+
replace(projectedPath) {
|
|
2459
|
+
args[index + 1] = projectedPath;
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
if (value.startsWith(`${name}=`)) {
|
|
2464
|
+
const path = value.slice(name.length + 1);
|
|
2465
|
+
if (!path) return null;
|
|
2466
|
+
return {
|
|
2467
|
+
path,
|
|
2468
|
+
replace(projectedPath) {
|
|
2469
|
+
args[index] = `${name}=${projectedPath}`;
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
1896
2473
|
}
|
|
1897
|
-
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
function positionalSpecArgument(args, start, defaultPath) {
|
|
2477
|
+
const positional = positionalIndices(args, start)[0];
|
|
2478
|
+
if (positional !== void 0) {
|
|
1898
2479
|
return {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
version: this.version
|
|
2480
|
+
path: args[positional],
|
|
2481
|
+
replace(projectedPath) {
|
|
2482
|
+
args[positional] = projectedPath;
|
|
2483
|
+
}
|
|
1904
2484
|
};
|
|
1905
2485
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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;
|
|
2486
|
+
return {
|
|
2487
|
+
path: defaultPath,
|
|
2488
|
+
replace(projectedPath) {
|
|
2489
|
+
args.splice(start, 0, projectedPath);
|
|
1918
2490
|
}
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
function selectedSpecArgument(args, cwd) {
|
|
2494
|
+
const command = args[0];
|
|
2495
|
+
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
2496
|
+
return positionalSpecArgument(args, 1, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1919
2497
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
`);
|
|
2498
|
+
if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
|
|
2499
|
+
return positionalSpecArgument(args, 2, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1923
2500
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2501
|
+
const serves = command === "serve" || command === "models" && args[1] === "serve";
|
|
2502
|
+
if (!serves) return null;
|
|
2503
|
+
const explicit = optionValueArgument(args, "--spec");
|
|
2504
|
+
if (explicit) return explicit;
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
async function pathIsFile(path) {
|
|
2508
|
+
return (await stat3(path).catch(() => null))?.isFile() ?? false;
|
|
2509
|
+
}
|
|
2510
|
+
function resolveFromCwd(path, cwd) {
|
|
2511
|
+
if (isAbsolute3(path)) return path;
|
|
2512
|
+
return resolve3(cwd, path);
|
|
2513
|
+
}
|
|
2514
|
+
async function projectSpecArguments(originalArgs, cwd) {
|
|
2515
|
+
const args = [...originalArgs];
|
|
2516
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2517
|
+
return {
|
|
2518
|
+
args,
|
|
2519
|
+
droppedSpecKeys: [],
|
|
2520
|
+
pathReplacements: [],
|
|
2521
|
+
cleanup: async () => {
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
1935
2524
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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";
|
|
2525
|
+
const selected = selectedSpecArgument(args, cwd);
|
|
2526
|
+
if (!selected) {
|
|
2527
|
+
return {
|
|
2528
|
+
args,
|
|
2529
|
+
droppedSpecKeys: [],
|
|
2530
|
+
pathReplacements: [],
|
|
2531
|
+
cleanup: async () => {
|
|
1970
2532
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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";
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
const sourcePath = resolveFromCwd(selected.path, cwd);
|
|
2536
|
+
if (!await pathIsFile(sourcePath)) {
|
|
2537
|
+
return {
|
|
2538
|
+
args,
|
|
2539
|
+
droppedSpecKeys: [],
|
|
2540
|
+
pathReplacements: [],
|
|
2541
|
+
cleanup: async () => {
|
|
1992
2542
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
await this.refreshContext();
|
|
2013
|
-
this.io.write(`${successMark()} Directory: ${this.cwd}
|
|
2014
|
-
`);
|
|
2015
|
-
return "continue";
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
const raw = JSON.parse(await readFile2(sourcePath, "utf8"));
|
|
2546
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2547
|
+
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
2548
|
+
}
|
|
2549
|
+
const body = raw;
|
|
2550
|
+
const runRequestKeys = [
|
|
2551
|
+
"run_id",
|
|
2552
|
+
"behavior_spec_id",
|
|
2553
|
+
"run_number",
|
|
2554
|
+
"spec_snapshot"
|
|
2555
|
+
].filter((key) => key in body);
|
|
2556
|
+
if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
|
|
2557
|
+
return {
|
|
2558
|
+
args,
|
|
2559
|
+
droppedSpecKeys: [],
|
|
2560
|
+
pathReplacements: [],
|
|
2561
|
+
cleanup: async () => {
|
|
2016
2562
|
}
|
|
2017
|
-
|
|
2018
|
-
assertNoArgs("exit", command.args);
|
|
2019
|
-
return "exit";
|
|
2020
|
-
}
|
|
2563
|
+
};
|
|
2021
2564
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
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
|
-
}
|
|
2565
|
+
const projected = projectLocalSpec(body);
|
|
2566
|
+
if (projected.droppedKeys.length === 0) {
|
|
2567
|
+
return {
|
|
2568
|
+
args,
|
|
2569
|
+
droppedSpecKeys: [],
|
|
2570
|
+
pathReplacements: [],
|
|
2571
|
+
cleanup: async () => {
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2041
2574
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2575
|
+
const temporaryPath = join3(
|
|
2576
|
+
dirname2(sourcePath),
|
|
2577
|
+
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
|
|
2578
|
+
);
|
|
2579
|
+
await writeFile(
|
|
2580
|
+
temporaryPath,
|
|
2581
|
+
`${JSON.stringify(projected.body, null, 2)}
|
|
2582
|
+
`,
|
|
2583
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
2584
|
+
);
|
|
2585
|
+
selected.replace(temporaryPath);
|
|
2586
|
+
return {
|
|
2587
|
+
args,
|
|
2588
|
+
droppedSpecKeys: projected.droppedKeys,
|
|
2589
|
+
pathReplacements: [{
|
|
2590
|
+
sourcePath,
|
|
2591
|
+
projectedPath: temporaryPath
|
|
2592
|
+
}],
|
|
2593
|
+
cleanup: () => rm(temporaryPath, { force: true })
|
|
2594
|
+
};
|
|
2045
2595
|
}
|
|
2046
|
-
function
|
|
2047
|
-
|
|
2596
|
+
function resolveLocalCliEntrypoint() {
|
|
2597
|
+
try {
|
|
2598
|
+
return localRequire.resolve("@tuned-tensor/local");
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
throw new Error(
|
|
2601
|
+
"TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
|
|
2602
|
+
{ cause: error }
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2048
2605
|
}
|
|
2049
|
-
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (restoreRawMode) control.input.setRawMode(false);
|
|
2056
|
-
signals.on("SIGINT", keepParentAlive);
|
|
2606
|
+
function localCommandStreamsStdout(args) {
|
|
2607
|
+
const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
|
|
2608
|
+
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
2609
|
+
}
|
|
2610
|
+
function parseJsonOutput(stdout2) {
|
|
2611
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
2057
2612
|
try {
|
|
2058
|
-
return
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
if (restoreRawMode) control.input.setRawMode(true);
|
|
2062
|
-
control.resumeReadline();
|
|
2613
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
2614
|
+
} catch {
|
|
2615
|
+
return { hasJson: false, json: void 0 };
|
|
2063
2616
|
}
|
|
2064
2617
|
}
|
|
2065
|
-
function
|
|
2066
|
-
return
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
},
|
|
2070
|
-
writeError(text2) {
|
|
2071
|
-
error.write(text2);
|
|
2072
|
-
},
|
|
2073
|
-
clear() {
|
|
2074
|
-
output.write("\x1B[2J\x1B[H");
|
|
2075
|
-
}
|
|
2076
|
-
};
|
|
2618
|
+
function signalExitCode(signal) {
|
|
2619
|
+
if (!signal) return 1;
|
|
2620
|
+
const signalNumber = osConstants.signals[signal];
|
|
2621
|
+
return typeof signalNumber === "number" ? 128 + signalNumber : 1;
|
|
2077
2622
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2623
|
+
function writeStream(stream, chunk) {
|
|
2624
|
+
stream.write(chunk);
|
|
2625
|
+
}
|
|
2626
|
+
function normalizeProjectedPaths(value, replacements) {
|
|
2627
|
+
let normalized = value;
|
|
2628
|
+
for (const replacement of replacements) {
|
|
2629
|
+
const encodedProjectedPath = JSON.stringify(
|
|
2630
|
+
replacement.projectedPath
|
|
2631
|
+
).slice(1, -1);
|
|
2632
|
+
const encodedSourcePath = JSON.stringify(
|
|
2633
|
+
replacement.sourcePath
|
|
2634
|
+
).slice(1, -1);
|
|
2635
|
+
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
2084
2636
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2637
|
+
return normalized;
|
|
2638
|
+
}
|
|
2639
|
+
async function spawnLocalCli(args) {
|
|
2640
|
+
const output = args.stdout ?? process.stdout;
|
|
2641
|
+
const errors = args.stderr ?? process.stderr;
|
|
2642
|
+
const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
|
|
2643
|
+
const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
|
|
2644
|
+
const child = spawn(
|
|
2645
|
+
args.nodeExecutable,
|
|
2646
|
+
[args.entrypoint, ...args.commandArgs],
|
|
2088
2647
|
{
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2648
|
+
cwd: args.cwd,
|
|
2649
|
+
env: args.env,
|
|
2650
|
+
stdio: [
|
|
2651
|
+
pipeInput ? "pipe" : "inherit",
|
|
2652
|
+
args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
2653
|
+
"pipe"
|
|
2654
|
+
],
|
|
2655
|
+
detached: process.platform !== "win32"
|
|
2656
|
+
}
|
|
2094
2657
|
);
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2658
|
+
if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
|
|
2659
|
+
let stdout2 = "";
|
|
2660
|
+
let stderr = "";
|
|
2661
|
+
child.stdout?.setEncoding("utf8");
|
|
2662
|
+
child.stderr?.setEncoding("utf8");
|
|
2663
|
+
child.stdout?.on("data", (chunk) => {
|
|
2664
|
+
const normalized = normalizeProjectedPaths(
|
|
2665
|
+
chunk,
|
|
2666
|
+
args.pathReplacements
|
|
2667
|
+
);
|
|
2668
|
+
if (args.streamingStdout) writeStream(output, normalized);
|
|
2669
|
+
else stdout2 += normalized;
|
|
2101
2670
|
});
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2671
|
+
child.stderr?.on("data", (chunk) => {
|
|
2672
|
+
const normalized = normalizeProjectedPaths(
|
|
2673
|
+
chunk,
|
|
2674
|
+
args.pathReplacements
|
|
2675
|
+
);
|
|
2676
|
+
stderr += normalized;
|
|
2677
|
+
writeStream(
|
|
2678
|
+
errors,
|
|
2679
|
+
args.jsonMode ? normalized : adaptLocalCliText(normalized)
|
|
2680
|
+
);
|
|
2110
2681
|
});
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2682
|
+
return new Promise((resolveChild) => {
|
|
2683
|
+
let spawnError;
|
|
2684
|
+
const forwardSignal = (signal) => {
|
|
2685
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
2686
|
+
if (child.pid && process.platform !== "win32") {
|
|
2687
|
+
try {
|
|
2688
|
+
process.kill(-child.pid, childSignal);
|
|
2689
|
+
return;
|
|
2690
|
+
} catch {
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
child.kill(childSignal);
|
|
2694
|
+
};
|
|
2695
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
2696
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
2697
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
2698
|
+
const cleanup = () => {
|
|
2699
|
+
process.off("SIGINT", onSigint);
|
|
2700
|
+
process.off("SIGTERM", onSigterm);
|
|
2701
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
2702
|
+
};
|
|
2703
|
+
process.on("SIGINT", onSigint);
|
|
2704
|
+
process.on("SIGTERM", onSigterm);
|
|
2705
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
2706
|
+
child.once("error", (error) => {
|
|
2707
|
+
spawnError = error;
|
|
2708
|
+
});
|
|
2709
|
+
child.once("close", (code, signal) => {
|
|
2710
|
+
cleanup();
|
|
2711
|
+
resolveChild({
|
|
2712
|
+
exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
|
|
2713
|
+
signal,
|
|
2714
|
+
stdout: stdout2,
|
|
2715
|
+
stderr,
|
|
2716
|
+
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
2717
|
+
});
|
|
2718
|
+
});
|
|
2115
2719
|
});
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2720
|
+
}
|
|
2721
|
+
function setupFailure(originalArgs, error) {
|
|
2722
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2723
|
+
return {
|
|
2724
|
+
args: [...originalArgs],
|
|
2725
|
+
projectedArgs: [...originalArgs],
|
|
2726
|
+
entrypoint: null,
|
|
2727
|
+
exitCode: 1,
|
|
2728
|
+
signal: null,
|
|
2729
|
+
stdout: "",
|
|
2730
|
+
stderr: "",
|
|
2731
|
+
hasJson: false,
|
|
2732
|
+
json: void 0,
|
|
2733
|
+
streamingStdout: false,
|
|
2734
|
+
droppedSpecKeys: [],
|
|
2735
|
+
errorMessage: message
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
async function runLocalCommand(args, options = {}) {
|
|
2739
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
2740
|
+
let projected;
|
|
2119
2741
|
try {
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2742
|
+
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
2743
|
+
projected = await projectSpecArguments(args, cwd);
|
|
2744
|
+
const streamingStdout = localCommandStreamsStdout(projected.args);
|
|
2745
|
+
const result = await spawnLocalCli({
|
|
2746
|
+
entrypoint,
|
|
2747
|
+
commandArgs: projected.args,
|
|
2748
|
+
cwd,
|
|
2749
|
+
env: options.env ?? process.env,
|
|
2750
|
+
nodeExecutable: options.nodeExecutable ?? process.execPath,
|
|
2751
|
+
streamingStdout,
|
|
2752
|
+
jsonMode: options.jsonMode ?? false,
|
|
2753
|
+
stdin: options.stdin,
|
|
2754
|
+
stdout: options.stdout,
|
|
2755
|
+
stderr: options.stderr,
|
|
2756
|
+
pathReplacements: projected.pathReplacements
|
|
2757
|
+
});
|
|
2758
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
2759
|
+
return {
|
|
2760
|
+
args: [...args],
|
|
2761
|
+
projectedArgs: projected.args,
|
|
2762
|
+
entrypoint,
|
|
2763
|
+
...result,
|
|
2764
|
+
...parsed,
|
|
2765
|
+
streamingStdout,
|
|
2766
|
+
droppedSpecKeys: projected.droppedSpecKeys
|
|
2767
|
+
};
|
|
2768
|
+
} catch (error) {
|
|
2769
|
+
return setupFailure(args, error);
|
|
2129
2770
|
} finally {
|
|
2130
|
-
|
|
2771
|
+
await projected?.cleanup().catch(() => void 0);
|
|
2131
2772
|
}
|
|
2132
2773
|
}
|
|
2133
|
-
function
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2774
|
+
async function executeLocalCommand(args, options = {}) {
|
|
2775
|
+
const result = await runLocalCommand(args, options);
|
|
2776
|
+
renderLocalOutput(result, {
|
|
2777
|
+
jsonMode: options.jsonMode,
|
|
2778
|
+
stdout: options.stdout
|
|
2779
|
+
});
|
|
2780
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
2781
|
+
return result;
|
|
2140
2782
|
}
|
|
2141
2783
|
|
|
2142
2784
|
// src/commands/auth.ts
|
|
2143
2785
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2144
2786
|
import { stdin, stdout } from "process";
|
|
2145
2787
|
import { Writable } from "stream";
|
|
2146
|
-
import
|
|
2788
|
+
import chalk4 from "chalk";
|
|
2147
2789
|
async function promptForApiKey(input = stdin, output = stdout) {
|
|
2148
2790
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2149
2791
|
throw new Error(
|
|
@@ -2185,9 +2827,9 @@ function registerAuthCommands(parent) {
|
|
|
2185
2827
|
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
2186
2828
|
console.log();
|
|
2187
2829
|
console.log(
|
|
2188
|
-
` To get your API key, go to ${
|
|
2830
|
+
` To get your API key, go to ${chalk4.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
2189
2831
|
);
|
|
2190
|
-
console.log(` ${
|
|
2832
|
+
console.log(` ${chalk4.cyan.underline(settingsUrl)}`);
|
|
2191
2833
|
console.log();
|
|
2192
2834
|
apiKey = await promptForApiKey();
|
|
2193
2835
|
}
|
|
@@ -3074,18 +3716,18 @@ function findControlChar(value) {
|
|
|
3074
3716
|
function formatCodepoint(code) {
|
|
3075
3717
|
return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3076
3718
|
}
|
|
3077
|
-
function detectRowFormat(
|
|
3078
|
-
const input =
|
|
3079
|
-
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof
|
|
3719
|
+
function detectRowFormat(record3) {
|
|
3720
|
+
const input = record3.input;
|
|
3721
|
+
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record3.output === "string") {
|
|
3080
3722
|
return "document_ocr_jsonl";
|
|
3081
3723
|
}
|
|
3082
|
-
if (typeof
|
|
3724
|
+
if (typeof record3.input === "string" || typeof record3.output === "string" || !("input" in record3) || !("output" in record3)) {
|
|
3083
3725
|
return "jsonl";
|
|
3084
3726
|
}
|
|
3085
3727
|
return null;
|
|
3086
3728
|
}
|
|
3087
|
-
function validateDocumentOcrRow(
|
|
3088
|
-
const input =
|
|
3729
|
+
function validateDocumentOcrRow(record3, rowNumber, errors) {
|
|
3730
|
+
const input = record3.input;
|
|
3089
3731
|
const assets = input.assets;
|
|
3090
3732
|
if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
|
|
3091
3733
|
errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
|
|
@@ -3097,10 +3739,10 @@ function validateDocumentOcrRow(record2, rowNumber, errors) {
|
|
|
3097
3739
|
);
|
|
3098
3740
|
}
|
|
3099
3741
|
}
|
|
3100
|
-
if (typeof
|
|
3742
|
+
if (typeof record3.output !== "string" || record3.output.trim().length === 0) {
|
|
3101
3743
|
errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
|
|
3102
3744
|
} else {
|
|
3103
|
-
const hit = findControlChar(
|
|
3745
|
+
const hit = findControlChar(record3.output);
|
|
3104
3746
|
if (hit) {
|
|
3105
3747
|
errors.push(
|
|
3106
3748
|
`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 +3811,14 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3169
3811
|
errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
|
|
3170
3812
|
continue;
|
|
3171
3813
|
}
|
|
3172
|
-
const
|
|
3173
|
-
if ("messages" in
|
|
3814
|
+
const record3 = row;
|
|
3815
|
+
if ("messages" in record3 && !("input" in record3) && !("output" in record3)) {
|
|
3174
3816
|
errors.push(
|
|
3175
3817
|
`Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
|
|
3176
3818
|
);
|
|
3177
3819
|
continue;
|
|
3178
3820
|
}
|
|
3179
|
-
const rowFormat = detectRowFormat(
|
|
3821
|
+
const rowFormat = detectRowFormat(record3);
|
|
3180
3822
|
if (!rowFormat) {
|
|
3181
3823
|
errors.push(
|
|
3182
3824
|
`Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
|
|
@@ -3193,17 +3835,17 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3193
3835
|
continue;
|
|
3194
3836
|
}
|
|
3195
3837
|
if (rowFormat === "document_ocr_jsonl") {
|
|
3196
|
-
validateDocumentOcrRow(
|
|
3838
|
+
validateDocumentOcrRow(record3, rowNumber, errors);
|
|
3197
3839
|
continue;
|
|
3198
3840
|
}
|
|
3199
|
-
if (typeof
|
|
3841
|
+
if (typeof record3.input !== "string") {
|
|
3200
3842
|
errors.push(`Row ${rowNumber}: missing string "input" field`);
|
|
3201
3843
|
}
|
|
3202
|
-
if (typeof
|
|
3844
|
+
if (typeof record3.output !== "string") {
|
|
3203
3845
|
errors.push(`Row ${rowNumber}: missing string "output" field`);
|
|
3204
3846
|
}
|
|
3205
3847
|
for (const field of ["input", "output"]) {
|
|
3206
|
-
const value =
|
|
3848
|
+
const value = record3[field];
|
|
3207
3849
|
if (typeof value !== "string") continue;
|
|
3208
3850
|
const hit = findControlChar(value);
|
|
3209
3851
|
if (hit) {
|
|
@@ -4206,12 +4848,12 @@ var ManagedModelServer = class {
|
|
|
4206
4848
|
clearTimeout(this.idleTimer);
|
|
4207
4849
|
this.idleTimer = null;
|
|
4208
4850
|
}
|
|
4209
|
-
writeLog(
|
|
4851
|
+
writeLog(record3) {
|
|
4210
4852
|
if (this.logRecordOverride) {
|
|
4211
|
-
this.logRecordOverride(
|
|
4853
|
+
this.logRecordOverride(record3);
|
|
4212
4854
|
return;
|
|
4213
4855
|
}
|
|
4214
|
-
const line = `${JSON.stringify(
|
|
4856
|
+
const line = `${JSON.stringify(record3)}
|
|
4215
4857
|
`;
|
|
4216
4858
|
if (this.options.logFile) {
|
|
4217
4859
|
mkdirSync2(dirname3(this.options.logFile), { recursive: true });
|
|
@@ -5651,7 +6293,7 @@ function registerModelsCommands(parent) {
|
|
|
5651
6293
|
}
|
|
5652
6294
|
|
|
5653
6295
|
// src/commands/balance.ts
|
|
5654
|
-
import
|
|
6296
|
+
import chalk5 from "chalk";
|
|
5655
6297
|
var KIND_LABELS = {
|
|
5656
6298
|
signup_bonus: "Signup bonus",
|
|
5657
6299
|
topup: "Top-up",
|
|
@@ -5666,8 +6308,8 @@ function formatCents3(cents) {
|
|
|
5666
6308
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
5667
6309
|
}
|
|
5668
6310
|
function formatSigned(cents) {
|
|
5669
|
-
if (cents > 0) return
|
|
5670
|
-
if (cents < 0) return
|
|
6311
|
+
if (cents > 0) return chalk5.green(`+${formatCents3(cents)}`);
|
|
6312
|
+
if (cents < 0) return chalk5.red(formatCents3(cents));
|
|
5671
6313
|
return formatCents3(cents);
|
|
5672
6314
|
}
|
|
5673
6315
|
function registerBalanceCommands(parent) {
|
|
@@ -5682,7 +6324,7 @@ function registerBalanceCommands(parent) {
|
|
|
5682
6324
|
return printJson({ balance, transactions });
|
|
5683
6325
|
}
|
|
5684
6326
|
const lowBalance = balance.balance_cents < 100;
|
|
5685
|
-
const balanceLine = lowBalance ?
|
|
6327
|
+
const balanceLine = lowBalance ? chalk5.red(formatCents3(balance.balance_cents)) + chalk5.dim(" (low)") : chalk5.bold.green(formatCents3(balance.balance_cents));
|
|
5686
6328
|
printDetail([
|
|
5687
6329
|
["Credits", balanceLine],
|
|
5688
6330
|
[
|
|
@@ -5697,7 +6339,7 @@ function registerBalanceCommands(parent) {
|
|
|
5697
6339
|
]);
|
|
5698
6340
|
if (transactions && transactions.length > 0) {
|
|
5699
6341
|
console.log(`
|
|
5700
|
-
${
|
|
6342
|
+
${chalk5.bold("Recent transactions")}`);
|
|
5701
6343
|
printTable(
|
|
5702
6344
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
5703
6345
|
transactions.map((row) => [
|
|
@@ -5705,19 +6347,19 @@ ${chalk4.bold("Recent transactions")}`);
|
|
|
5705
6347
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
5706
6348
|
formatSigned(row.amount_cents),
|
|
5707
6349
|
formatCents3(row.balance_after_cents),
|
|
5708
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
6350
|
+
row.reference_id ? shortId(row.reference_id) : chalk5.dim("\u2014")
|
|
5709
6351
|
])
|
|
5710
6352
|
);
|
|
5711
6353
|
} else {
|
|
5712
6354
|
console.log(
|
|
5713
6355
|
`
|
|
5714
|
-
${
|
|
6356
|
+
${chalk5.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
5715
6357
|
);
|
|
5716
6358
|
}
|
|
5717
6359
|
if (lowBalance) {
|
|
5718
6360
|
console.log(
|
|
5719
6361
|
`
|
|
5720
|
-
${formatStatus("preparing")} ${
|
|
6362
|
+
${formatStatus("preparing")} ${chalk5.yellow(
|
|
5721
6363
|
"Run `tt topup` to add more credits before starting a run."
|
|
5722
6364
|
)}`
|
|
5723
6365
|
);
|
|
@@ -5726,7 +6368,7 @@ ${formatStatus("preparing")} ${chalk4.yellow(
|
|
|
5726
6368
|
}
|
|
5727
6369
|
|
|
5728
6370
|
// src/commands/topup.ts
|
|
5729
|
-
import
|
|
6371
|
+
import chalk6 from "chalk";
|
|
5730
6372
|
import open from "open";
|
|
5731
6373
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
5732
6374
|
var MIN_USD = 5;
|
|
@@ -5765,9 +6407,9 @@ function registerTopupCommands(parent) {
|
|
|
5765
6407
|
"amount is required in --json mode (use --amount <usd>)"
|
|
5766
6408
|
);
|
|
5767
6409
|
}
|
|
5768
|
-
console.log(
|
|
6410
|
+
console.log(chalk6.bold("Add credits via Stripe Checkout"));
|
|
5769
6411
|
console.log(
|
|
5770
|
-
|
|
6412
|
+
chalk6.dim(
|
|
5771
6413
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
5772
6414
|
)
|
|
5773
6415
|
);
|
|
@@ -5794,8 +6436,8 @@ function registerTopupCommands(parent) {
|
|
|
5794
6436
|
}
|
|
5795
6437
|
const url = data.checkout_url;
|
|
5796
6438
|
console.log(
|
|
5797
|
-
`${
|
|
5798
|
-
` +
|
|
6439
|
+
`${chalk6.bold("Checkout URL:")} ${chalk6.cyan(url)}
|
|
6440
|
+
` + chalk6.dim("Complete the payment to credit your account.")
|
|
5799
6441
|
);
|
|
5800
6442
|
if (options.open !== false) {
|
|
5801
6443
|
try {
|
|
@@ -5803,7 +6445,7 @@ function registerTopupCommands(parent) {
|
|
|
5803
6445
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
5804
6446
|
} catch {
|
|
5805
6447
|
console.log(
|
|
5806
|
-
|
|
6448
|
+
chalk6.yellow(
|
|
5807
6449
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
5808
6450
|
)
|
|
5809
6451
|
);
|
|
@@ -5813,7 +6455,7 @@ function registerTopupCommands(parent) {
|
|
|
5813
6455
|
}
|
|
5814
6456
|
|
|
5815
6457
|
// src/commands/eval.ts
|
|
5816
|
-
import
|
|
6458
|
+
import chalk7 from "chalk";
|
|
5817
6459
|
|
|
5818
6460
|
// src/eval/rules.ts
|
|
5819
6461
|
function validateSpec(spec) {
|
|
@@ -5978,7 +6620,7 @@ function registerEvalCommand(parent) {
|
|
|
5978
6620
|
const spec = loadSpec(cmdOpts.file);
|
|
5979
6621
|
const validation = validateSpec(spec);
|
|
5980
6622
|
if (!isJsonMode()) {
|
|
5981
|
-
console.log(
|
|
6623
|
+
console.log(chalk7.dim(`
|
|
5982
6624
|
Spec: ${cmdOpts.file}
|
|
5983
6625
|
`));
|
|
5984
6626
|
}
|
|
@@ -5993,10 +6635,10 @@ Spec: ${cmdOpts.file}
|
|
|
5993
6635
|
});
|
|
5994
6636
|
}
|
|
5995
6637
|
function printValidation(checks) {
|
|
5996
|
-
console.log(
|
|
6638
|
+
console.log(chalk7.bold("Spec Validation"));
|
|
5997
6639
|
for (const check of checks) {
|
|
5998
|
-
const icon = check.passed ?
|
|
5999
|
-
const msg = check.message ?
|
|
6640
|
+
const icon = check.passed ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
6641
|
+
const msg = check.message ? chalk7.dim(` \u2014 ${check.message}`) : "";
|
|
6000
6642
|
console.log(` ${icon} ${check.name}${msg}`);
|
|
6001
6643
|
}
|
|
6002
6644
|
}
|
|
@@ -6152,17 +6794,28 @@ function createProgram(version, runtime = {}) {
|
|
|
6152
6794
|
const openShell = async () => {
|
|
6153
6795
|
const root = program.opts();
|
|
6154
6796
|
const shellEnvironment = childEnvironment(root, env);
|
|
6797
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6798
|
+
const error = runtime.stderr ?? process.stderr;
|
|
6799
|
+
const agent = createShellAgent({
|
|
6800
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
6801
|
+
apiKey: root.apiKey ?? shellEnvironment.TUNED_TENSOR_API_KEY,
|
|
6802
|
+
baseUrl: root.baseUrl ?? shellEnvironment.TUNED_TENSOR_URL
|
|
6803
|
+
}),
|
|
6804
|
+
output,
|
|
6805
|
+
error
|
|
6806
|
+
});
|
|
6155
6807
|
await launchShell({
|
|
6156
6808
|
runner: shellRunner,
|
|
6809
|
+
agent,
|
|
6157
6810
|
input: runtime.stdin ?? process.stdin,
|
|
6158
|
-
output
|
|
6159
|
-
error
|
|
6811
|
+
output,
|
|
6812
|
+
error,
|
|
6160
6813
|
cwd,
|
|
6161
6814
|
env: shellEnvironment,
|
|
6162
6815
|
version
|
|
6163
6816
|
});
|
|
6164
6817
|
};
|
|
6165
|
-
program.name("tt").description("Tuned Tensor \u2014
|
|
6818
|
+
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
6819
|
"-u, --base-url <url>",
|
|
6167
6820
|
"API base URL (default: https://tunedtensor.com)"
|
|
6168
6821
|
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
|
|
@@ -6170,7 +6823,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6170
6823
|
if (root.json) setJsonMode(true);
|
|
6171
6824
|
if (root.color === false) {
|
|
6172
6825
|
process.env.FORCE_COLOR = "0";
|
|
6173
|
-
|
|
6826
|
+
chalk8.level = 0;
|
|
6174
6827
|
}
|
|
6175
6828
|
});
|
|
6176
6829
|
registerAuthCommands(program);
|
|
@@ -6203,12 +6856,12 @@ function createProgram(version, runtime = {}) {
|
|
|
6203
6856
|
`);
|
|
6204
6857
|
return;
|
|
6205
6858
|
}
|
|
6206
|
-
output.write(`${
|
|
6859
|
+
output.write(`${chalk8.bold.cyan("Tuned Tensor status")}
|
|
6207
6860
|
`);
|
|
6208
6861
|
output.write(`${formatShellStatus(context, target).join("\n")}
|
|
6209
6862
|
|
|
6210
6863
|
`);
|
|
6211
|
-
output.write(`${
|
|
6864
|
+
output.write(`${chalk8.bold("Context")}
|
|
6212
6865
|
`);
|
|
6213
6866
|
output.write(
|
|
6214
6867
|
`${formatShellContext(
|
|
@@ -6222,7 +6875,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6222
6875
|
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
6876
|
const root = program.opts();
|
|
6224
6877
|
const passthrough = extractPassthroughOptions(args, root);
|
|
6225
|
-
if (!passthrough.color)
|
|
6878
|
+
if (!passthrough.color) chalk8.level = 0;
|
|
6226
6879
|
const result = await invokeLocal(
|
|
6227
6880
|
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6228
6881
|
{
|
|
@@ -6262,7 +6915,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6262
6915
|
"after",
|
|
6263
6916
|
`
|
|
6264
6917
|
Workflows:
|
|
6265
|
-
tt Open the
|
|
6918
|
+
tt Open the conversational terminal (TTY only)
|
|
6266
6919
|
tt status Inspect cloud/local project context
|
|
6267
6920
|
tt cloud <command> Run a hosted command explicitly
|
|
6268
6921
|
tt local <command> Run a local GPU command
|
|
@@ -6275,6 +6928,22 @@ Examples:
|
|
|
6275
6928
|
);
|
|
6276
6929
|
return program;
|
|
6277
6930
|
}
|
|
6931
|
+
function createShellAgent(options) {
|
|
6932
|
+
return new TunedTensorAgentSession({
|
|
6933
|
+
client: options.client,
|
|
6934
|
+
io: {
|
|
6935
|
+
write(text2) {
|
|
6936
|
+
options.output.write(text2);
|
|
6937
|
+
},
|
|
6938
|
+
writeError(text2) {
|
|
6939
|
+
options.error.write(text2);
|
|
6940
|
+
},
|
|
6941
|
+
clear() {
|
|
6942
|
+
options.output.write("\x1B[2J\x1B[H");
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6278
6947
|
async function runCli(version, runtime = {}) {
|
|
6279
6948
|
const argv = runtime.argv ?? process.argv;
|
|
6280
6949
|
const env = runtime.env ?? process.env;
|
|
@@ -6286,6 +6955,16 @@ async function runCli(version, runtime = {}) {
|
|
|
6286
6955
|
env
|
|
6287
6956
|
})) {
|
|
6288
6957
|
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6958
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6959
|
+
const error = runtime.stderr ?? process.stderr;
|
|
6960
|
+
const agent = createShellAgent({
|
|
6961
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
6962
|
+
apiKey: env.TUNED_TENSOR_API_KEY,
|
|
6963
|
+
baseUrl: env.TUNED_TENSOR_URL
|
|
6964
|
+
}),
|
|
6965
|
+
output,
|
|
6966
|
+
error
|
|
6967
|
+
});
|
|
6289
6968
|
await (runtime.startShell ?? startInteractiveShell)({
|
|
6290
6969
|
runner: async (request2) => await invokeSelf(
|
|
6291
6970
|
request2.target === "local" ? ["local", ...request2.args] : request2.args,
|
|
@@ -6295,9 +6974,10 @@ async function runCli(version, runtime = {}) {
|
|
|
6295
6974
|
entrypoint: argv[1]
|
|
6296
6975
|
}
|
|
6297
6976
|
),
|
|
6977
|
+
agent,
|
|
6298
6978
|
input: runtime.stdin ?? process.stdin,
|
|
6299
|
-
output
|
|
6300
|
-
error
|
|
6979
|
+
output,
|
|
6980
|
+
error,
|
|
6301
6981
|
cwd: runtime.cwd ?? process.cwd(),
|
|
6302
6982
|
env,
|
|
6303
6983
|
version
|
|
@@ -6309,7 +6989,7 @@ async function runCli(version, runtime = {}) {
|
|
|
6309
6989
|
}
|
|
6310
6990
|
|
|
6311
6991
|
// src/index.ts
|
|
6312
|
-
runCli("0.
|
|
6992
|
+
runCli("0.7.0").catch((err) => {
|
|
6313
6993
|
reportError(err);
|
|
6314
6994
|
process.exit(1);
|
|
6315
6995
|
});
|