@tuned-tensor/cli 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -15
- package/dist/index.js +2608 -1868
- 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,2682 @@ 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));
|
|
866
|
-
}
|
|
867
|
-
function positionalIndices(args, start) {
|
|
868
|
-
const indices = [];
|
|
869
|
-
let optionsEnded = false;
|
|
870
|
-
for (let index = start; index < args.length; index += 1) {
|
|
871
|
-
const value = args[index];
|
|
872
|
-
if (!optionsEnded && value === "--") {
|
|
873
|
-
optionsEnded = true;
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
if (!optionsEnded && value.startsWith("-")) {
|
|
877
|
-
if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
indices.push(index);
|
|
881
|
-
}
|
|
882
|
-
return indices;
|
|
945
|
+
function errorMessage(error) {
|
|
946
|
+
return error instanceof Error ? error.message : String(error);
|
|
883
947
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if (!path) return null;
|
|
900
|
-
return {
|
|
901
|
-
path,
|
|
902
|
-
replace(projectedPath) {
|
|
903
|
-
args[index] = `${name}=${projectedPath}`;
|
|
904
|
-
}
|
|
905
|
-
};
|
|
906
|
-
}
|
|
948
|
+
var successMark = () => chalk.green("\u2713");
|
|
949
|
+
var errorMark = () => chalk.red("\u2717");
|
|
950
|
+
var accent = chalk.hex("#8B5CF6");
|
|
951
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
952
|
+
var ANSI_BOLD_OFF = "\x1B[22m";
|
|
953
|
+
var ANSI_CLEAR_TO_END = "\x1B[K";
|
|
954
|
+
var ANSI_RESET = "\x1B[0m";
|
|
955
|
+
function inputColorCodes() {
|
|
956
|
+
if (chalk.level === 0) return null;
|
|
957
|
+
if (chalk.level === 1) {
|
|
958
|
+
return {
|
|
959
|
+
background: "\x1B[100m",
|
|
960
|
+
accent: "\x1B[95m",
|
|
961
|
+
text: "\x1B[97m"
|
|
962
|
+
};
|
|
907
963
|
}
|
|
908
|
-
|
|
909
|
-
}
|
|
910
|
-
function positionalSpecArgument(args, start, defaultPath) {
|
|
911
|
-
const positional = positionalIndices(args, start)[0];
|
|
912
|
-
if (positional !== void 0) {
|
|
964
|
+
if (chalk.level === 2) {
|
|
913
965
|
return {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
966
|
+
background: "\x1B[48;5;238m",
|
|
967
|
+
accent: "\x1B[38;5;183m",
|
|
968
|
+
text: "\x1B[38;5;255m"
|
|
918
969
|
};
|
|
919
970
|
}
|
|
920
971
|
return {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
972
|
+
background: "\x1B[48;2;50;52;67m",
|
|
973
|
+
accent: "\x1B[38;2;196;181;253m",
|
|
974
|
+
text: "\x1B[38;2;245;243;255m"
|
|
925
975
|
};
|
|
926
976
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (!serves) return null;
|
|
937
|
-
const explicit = optionValueArgument(args, "--spec");
|
|
938
|
-
if (explicit) return explicit;
|
|
939
|
-
return null;
|
|
977
|
+
var DETAIL_LABEL_WIDTH = 15;
|
|
978
|
+
function styleDetailLines(lines) {
|
|
979
|
+
return lines.map((line) => {
|
|
980
|
+
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
981
|
+
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
982
|
+
if (!label.trim()) return line;
|
|
983
|
+
const color = label.startsWith("Warning") ? chalk.yellow : accent.bold;
|
|
984
|
+
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
985
|
+
});
|
|
940
986
|
}
|
|
941
|
-
|
|
942
|
-
return
|
|
987
|
+
function detailLine(label, value) {
|
|
988
|
+
return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
|
|
943
989
|
}
|
|
944
|
-
function
|
|
945
|
-
if (
|
|
946
|
-
|
|
990
|
+
function assertNoArgs(command, args) {
|
|
991
|
+
if (args.length > 0) {
|
|
992
|
+
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
993
|
+
}
|
|
947
994
|
}
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
995
|
+
function expandDirectory(value, cwd, env) {
|
|
996
|
+
const home = env.HOME ? resolve2(env.HOME) : homedir3();
|
|
997
|
+
if (value === "~") return home;
|
|
998
|
+
if (value.startsWith("~/")) return resolve2(home, value.slice(2));
|
|
999
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(cwd, value);
|
|
1000
|
+
}
|
|
1001
|
+
function helpText(mode, query, palette = false) {
|
|
1002
|
+
const groups = groupedCatalog(mode, query);
|
|
1003
|
+
const lines = [];
|
|
1004
|
+
lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
|
|
1005
|
+
if (groups.size === 0) {
|
|
1006
|
+
lines.push(chalk.dim(" No matching commands."));
|
|
1007
|
+
} else {
|
|
1008
|
+
for (const [group, commands] of groups) {
|
|
1009
|
+
lines.push("");
|
|
1010
|
+
lines.push(chalk.bold(group));
|
|
1011
|
+
for (const command of commands) {
|
|
1012
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
956
1013
|
}
|
|
957
|
-
}
|
|
1014
|
+
}
|
|
958
1015
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
};
|
|
1016
|
+
if (!query) {
|
|
1017
|
+
lines.push("");
|
|
1018
|
+
lines.push(chalk.bold("Conversation"));
|
|
1019
|
+
lines.push(` ${accent("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
|
|
1020
|
+
lines.push(` ${accent("/new".padEnd(22))} Start a new agent conversation.`);
|
|
1021
|
+
lines.push(` ${accent("/threads".padEnd(22))} List recent conversations.`);
|
|
1022
|
+
lines.push(` ${accent("/resume <id>".padEnd(22))} Resume a conversation.`);
|
|
1023
|
+
lines.push(` ${accent("/approve [id]".padEnd(22))} Approve a proposed action.`);
|
|
1024
|
+
lines.push(` ${accent("/reject [id]".padEnd(22))} Reject a proposed action.`);
|
|
1025
|
+
lines.push("");
|
|
1026
|
+
lines.push(chalk.bold("Session"));
|
|
1027
|
+
for (const command of SLASH_COMMANDS) {
|
|
1028
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
1029
|
+
}
|
|
1030
|
+
lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
|
|
1031
|
+
lines.push(chalk.dim(
|
|
1032
|
+
"\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
|
|
1033
|
+
));
|
|
968
1034
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1035
|
+
return `${lines.join("\n")}
|
|
1036
|
+
`;
|
|
1037
|
+
}
|
|
1038
|
+
function activeModelLabel(snapshot) {
|
|
1039
|
+
return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
|
|
1040
|
+
}
|
|
1041
|
+
function renderShellBanner(snapshot) {
|
|
1042
|
+
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1043
|
+
const heading = snapshot.version ? `${accent.bold("tt")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("tt");
|
|
1044
|
+
const lines = [
|
|
1045
|
+
heading,
|
|
1046
|
+
chalk.dim(
|
|
1047
|
+
`${snapshot.mode} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`
|
|
1048
|
+
),
|
|
1049
|
+
chalk.dim("ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
|
|
1050
|
+
"",
|
|
1051
|
+
chalk.dim("Ask TT anything. Known commands run directly.")
|
|
1052
|
+
];
|
|
1053
|
+
return `${lines.join("\n")}
|
|
1054
|
+
|
|
1055
|
+
`;
|
|
1056
|
+
}
|
|
1057
|
+
function renderShellPrompt() {
|
|
1058
|
+
const colors = inputColorCodes();
|
|
1059
|
+
if (!colors) return "\u203A ";
|
|
1060
|
+
return [
|
|
1061
|
+
colors.background,
|
|
1062
|
+
colors.accent,
|
|
1063
|
+
ANSI_BOLD,
|
|
1064
|
+
"\u203A",
|
|
1065
|
+
ANSI_BOLD_OFF,
|
|
1066
|
+
colors.text,
|
|
1067
|
+
" ",
|
|
1068
|
+
ANSI_CLEAR_TO_END
|
|
1069
|
+
].join("");
|
|
1070
|
+
}
|
|
1071
|
+
function resetShellPromptStyle() {
|
|
1072
|
+
return chalk.level === 0 ? "" : ANSI_RESET;
|
|
1073
|
+
}
|
|
1074
|
+
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
1075
|
+
constructor(runner, io, agent, env, contextProvider, version, initialContext) {
|
|
1076
|
+
this.runner = runner;
|
|
1077
|
+
this.io = io;
|
|
1078
|
+
this.agent = agent;
|
|
1079
|
+
this.env = env;
|
|
1080
|
+
this.contextProvider = contextProvider;
|
|
1081
|
+
this.version = version;
|
|
1082
|
+
this.mode = initialContext.inferredTarget;
|
|
1083
|
+
this.modeSource = initialContext.targetSource;
|
|
1084
|
+
this.cwd = initialContext.cwd;
|
|
1085
|
+
this.context = initialContext;
|
|
978
1086
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1087
|
+
mode;
|
|
1088
|
+
modeSource;
|
|
1089
|
+
cwd;
|
|
1090
|
+
context;
|
|
1091
|
+
static async create(options) {
|
|
1092
|
+
const env = options.env ?? process.env;
|
|
1093
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
1094
|
+
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1095
|
+
const initialContext = await contextProvider({ cwd, env });
|
|
1096
|
+
return new _TunedTensorShellSession(
|
|
1097
|
+
options.runner,
|
|
1098
|
+
options.io,
|
|
1099
|
+
options.agent,
|
|
1100
|
+
env,
|
|
1101
|
+
contextProvider,
|
|
1102
|
+
options.version,
|
|
1103
|
+
initialContext
|
|
1104
|
+
);
|
|
982
1105
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
"run_id",
|
|
986
|
-
"behavior_spec_id",
|
|
987
|
-
"run_number",
|
|
988
|
-
"spec_snapshot"
|
|
989
|
-
].filter((key) => key in body);
|
|
990
|
-
if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
|
|
991
|
-
return {
|
|
992
|
-
args,
|
|
993
|
-
droppedSpecKeys: [],
|
|
994
|
-
pathReplacements: [],
|
|
995
|
-
cleanup: async () => {
|
|
996
|
-
}
|
|
997
|
-
};
|
|
1106
|
+
interruptAgent() {
|
|
1107
|
+
return this.agent?.interrupt() ?? false;
|
|
998
1108
|
}
|
|
999
|
-
|
|
1000
|
-
if (projected.droppedKeys.length === 0) {
|
|
1109
|
+
snapshot() {
|
|
1001
1110
|
return {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1111
|
+
mode: this.mode,
|
|
1112
|
+
modeSource: this.modeSource,
|
|
1113
|
+
cwd: this.cwd,
|
|
1114
|
+
context: this.context,
|
|
1115
|
+
version: this.version
|
|
1007
1116
|
};
|
|
1008
1117
|
}
|
|
1009
|
-
|
|
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
|
-
);
|
|
1118
|
+
prompt() {
|
|
1119
|
+
return renderShellPrompt();
|
|
1038
1120
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
|
|
1042
|
-
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
1043
|
-
}
|
|
1044
|
-
function parseJsonOutput(stdout2) {
|
|
1045
|
-
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
1046
|
-
try {
|
|
1047
|
-
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
1048
|
-
} catch {
|
|
1049
|
-
return { hasJson: false, json: void 0 };
|
|
1121
|
+
banner() {
|
|
1122
|
+
return renderShellBanner(this.snapshot());
|
|
1050
1123
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
stream.write(chunk);
|
|
1059
|
-
}
|
|
1060
|
-
function normalizeProjectedPaths(value, replacements) {
|
|
1061
|
-
let normalized = value;
|
|
1062
|
-
for (const replacement of replacements) {
|
|
1063
|
-
const encodedProjectedPath = JSON.stringify(
|
|
1064
|
-
replacement.projectedPath
|
|
1065
|
-
).slice(1, -1);
|
|
1066
|
-
const encodedSourcePath = JSON.stringify(
|
|
1067
|
-
replacement.sourcePath
|
|
1068
|
-
).slice(1, -1);
|
|
1069
|
-
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
1124
|
+
async refreshContext() {
|
|
1125
|
+
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
1126
|
+
this.cwd = this.context.cwd;
|
|
1127
|
+
if (this.modeSource !== "session" && this.modeSource !== "environment") {
|
|
1128
|
+
this.mode = this.context.inferredTarget;
|
|
1129
|
+
this.modeSource = this.context.targetSource;
|
|
1130
|
+
}
|
|
1070
1131
|
}
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
{
|
|
1082
|
-
cwd: args.cwd,
|
|
1083
|
-
env: args.env,
|
|
1084
|
-
stdio: [
|
|
1085
|
-
pipeInput ? "pipe" : "inherit",
|
|
1086
|
-
args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
1087
|
-
"pipe"
|
|
1088
|
-
],
|
|
1089
|
-
detached: process.platform !== "win32"
|
|
1132
|
+
writeLines(lines) {
|
|
1133
|
+
this.io.write(`${lines.join("\n")}
|
|
1134
|
+
`);
|
|
1135
|
+
}
|
|
1136
|
+
modelLines() {
|
|
1137
|
+
if (this.mode === "local") {
|
|
1138
|
+
return styleDetailLines([
|
|
1139
|
+
detailLine("Active model", this.context.local.activeModelId ?? "base"),
|
|
1140
|
+
detailLine("Change", "/model <id> to activate a verified local model")
|
|
1141
|
+
]);
|
|
1090
1142
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
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 {
|
|
1143
|
+
return styleDetailLines([
|
|
1144
|
+
detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
|
|
1145
|
+
detailLine("Change", "edit base_model in tunedtensor.json, then run push")
|
|
1146
|
+
]);
|
|
1147
|
+
}
|
|
1148
|
+
async handleSlash(command) {
|
|
1149
|
+
switch (command.name) {
|
|
1150
|
+
case "palette":
|
|
1151
|
+
this.io.write(helpText(this.mode, void 0, true));
|
|
1152
|
+
return "continue";
|
|
1153
|
+
case "help":
|
|
1154
|
+
this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
|
|
1155
|
+
return "continue";
|
|
1156
|
+
case "status":
|
|
1157
|
+
assertNoArgs("status", command.args);
|
|
1158
|
+
await this.refreshContext();
|
|
1159
|
+
this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
|
|
1160
|
+
return "continue";
|
|
1161
|
+
case "context":
|
|
1162
|
+
assertNoArgs("context", command.args);
|
|
1163
|
+
await this.refreshContext();
|
|
1164
|
+
this.writeLines(styleDetailLines(
|
|
1165
|
+
formatShellContext(this.context, this.mode, this.modeSource)
|
|
1166
|
+
));
|
|
1167
|
+
return "continue";
|
|
1168
|
+
case "mode": {
|
|
1169
|
+
if (command.args.length === 0) {
|
|
1170
|
+
this.io.write(`Current workflow: ${this.mode}
|
|
1171
|
+
`);
|
|
1172
|
+
return "continue";
|
|
1173
|
+
}
|
|
1174
|
+
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1175
|
+
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1125
1176
|
}
|
|
1177
|
+
this.mode = command.args[0];
|
|
1178
|
+
this.modeSource = "session";
|
|
1179
|
+
this.io.write(`${successMark()} Workflow switched to ${this.mode}.
|
|
1180
|
+
`);
|
|
1181
|
+
return "continue";
|
|
1126
1182
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1183
|
+
case "model": {
|
|
1184
|
+
if (command.args.length === 0) {
|
|
1185
|
+
await this.refreshContext();
|
|
1186
|
+
this.writeLines(this.modelLines());
|
|
1187
|
+
return "continue";
|
|
1188
|
+
}
|
|
1189
|
+
if (command.args.length !== 1) {
|
|
1190
|
+
throw new ShellParseError("Usage: /model [model-id]");
|
|
1191
|
+
}
|
|
1192
|
+
if (this.mode !== "local") {
|
|
1193
|
+
throw new ShellParseError(
|
|
1194
|
+
"Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
await this.runner({
|
|
1198
|
+
target: "local",
|
|
1199
|
+
args: ["models", "activate", command.args[0]],
|
|
1200
|
+
cwd: this.cwd
|
|
1201
|
+
});
|
|
1202
|
+
await this.refreshContext();
|
|
1203
|
+
return "continue";
|
|
1204
|
+
}
|
|
1205
|
+
case "clear":
|
|
1206
|
+
assertNoArgs("clear", command.args);
|
|
1207
|
+
this.io.clear();
|
|
1208
|
+
return "continue";
|
|
1209
|
+
case "cd": {
|
|
1210
|
+
if (command.args.length === 0) {
|
|
1211
|
+
this.io.write(`${this.cwd}
|
|
1212
|
+
`);
|
|
1213
|
+
return "continue";
|
|
1214
|
+
}
|
|
1215
|
+
if (command.args.length !== 1) {
|
|
1216
|
+
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
1217
|
+
}
|
|
1218
|
+
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
1219
|
+
const metadata = await stat2(nextDirectory).catch(() => null);
|
|
1220
|
+
if (!metadata?.isDirectory()) {
|
|
1221
|
+
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
1222
|
+
}
|
|
1223
|
+
this.cwd = nextDirectory;
|
|
1224
|
+
await this.refreshContext();
|
|
1225
|
+
this.io.write(`${successMark()} Directory: ${this.cwd}
|
|
1226
|
+
`);
|
|
1227
|
+
return "continue";
|
|
1228
|
+
}
|
|
1229
|
+
case "exit":
|
|
1230
|
+
assertNoArgs("exit", command.args);
|
|
1231
|
+
return "exit";
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async handleLine(input) {
|
|
1235
|
+
try {
|
|
1236
|
+
if (/^(exit|quit)$/i.test(input.trim())) return "exit";
|
|
1237
|
+
const trimmed = input.trim();
|
|
1238
|
+
if (trimmed.startsWith("/")) {
|
|
1239
|
+
const rawName = trimmed.slice(1).split(/\s+/, 1)[0].toLowerCase();
|
|
1240
|
+
if (AGENT_SLASH_COMMANDS.has(rawName)) {
|
|
1241
|
+
if (!this.agent) {
|
|
1242
|
+
throw new ShellParseError("The conversational agent is unavailable.");
|
|
1243
|
+
}
|
|
1244
|
+
await this.agent.handleLine(trimmed);
|
|
1245
|
+
await this.refreshContext();
|
|
1246
|
+
return "continue";
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const slash = parseSlashCommand(input);
|
|
1250
|
+
if (slash) return await this.handleSlash(slash);
|
|
1251
|
+
const explicitCommand = trimmed.startsWith(":") ? trimmed.slice(1).trimStart() : null;
|
|
1252
|
+
if (explicitCommand !== null && !explicitCommand) {
|
|
1253
|
+
throw new ShellParseError("Add a TT command after :.");
|
|
1254
|
+
}
|
|
1255
|
+
const explicitWorkflowPrefix = /^(?:tt\s+)?(?:cloud|local)(?:\s|$)/.test(trimmed) || /^tt(?:\s|$)/.test(trimmed);
|
|
1256
|
+
if (explicitCommand === null && !explicitWorkflowPrefix && this.agent && !hasCatalogIntent(input, this.mode)) {
|
|
1257
|
+
await this.agent.handleLine(input);
|
|
1258
|
+
await this.refreshContext();
|
|
1259
|
+
return "continue";
|
|
1260
|
+
}
|
|
1261
|
+
const commandInput = explicitCommand ?? input;
|
|
1262
|
+
const routed = routeShellCommand(commandInput, this.mode);
|
|
1263
|
+
if (!routed) return "continue";
|
|
1264
|
+
await this.runner({
|
|
1265
|
+
target: routed.target,
|
|
1266
|
+
args: [...routed.args],
|
|
1267
|
+
cwd: this.cwd
|
|
1151
1268
|
});
|
|
1152
|
-
|
|
1153
|
-
|
|
1269
|
+
await this.refreshContext();
|
|
1270
|
+
return "continue";
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
this.io.writeError(`${errorMark()} ${errorMessage(error)}
|
|
1273
|
+
`);
|
|
1274
|
+
return "continue";
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
async function createShellSession(options) {
|
|
1279
|
+
return TunedTensorShellSession.create(options);
|
|
1154
1280
|
}
|
|
1155
|
-
function
|
|
1156
|
-
|
|
1281
|
+
function streamIsTTY(stream) {
|
|
1282
|
+
return Boolean(stream.isTTY);
|
|
1283
|
+
}
|
|
1284
|
+
async function withForegroundSignalHandoff(control, run) {
|
|
1285
|
+
const signals = control.signals ?? process;
|
|
1286
|
+
const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
|
|
1287
|
+
const keepParentAlive = () => {
|
|
1288
|
+
};
|
|
1289
|
+
control.pauseReadline();
|
|
1290
|
+
if (restoreRawMode) control.input.setRawMode(false);
|
|
1291
|
+
signals.on("SIGINT", keepParentAlive);
|
|
1292
|
+
try {
|
|
1293
|
+
return await run();
|
|
1294
|
+
} finally {
|
|
1295
|
+
signals.off("SIGINT", keepParentAlive);
|
|
1296
|
+
if (restoreRawMode) control.input.setRawMode(true);
|
|
1297
|
+
control.resumeReadline();
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function streamIO(output, error) {
|
|
1157
1301
|
return {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
streamingStdout: false,
|
|
1168
|
-
droppedSpecKeys: [],
|
|
1169
|
-
errorMessage: message
|
|
1302
|
+
write(text2) {
|
|
1303
|
+
output.write(text2);
|
|
1304
|
+
},
|
|
1305
|
+
writeError(text2) {
|
|
1306
|
+
error.write(text2);
|
|
1307
|
+
},
|
|
1308
|
+
clear() {
|
|
1309
|
+
output.write("\x1B[2J\x1B[H");
|
|
1310
|
+
}
|
|
1170
1311
|
};
|
|
1171
1312
|
}
|
|
1172
|
-
async function
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1313
|
+
async function startInteractiveShell(options) {
|
|
1314
|
+
const input = options.input ?? process.stdin;
|
|
1315
|
+
const output = options.output ?? process.stdout;
|
|
1316
|
+
const error = options.error ?? process.stderr;
|
|
1317
|
+
if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
|
|
1318
|
+
throw new Error("The interactive TT shell requires a TTY.");
|
|
1319
|
+
}
|
|
1320
|
+
let readline;
|
|
1321
|
+
const terminalInput = input;
|
|
1322
|
+
const foregroundRunner = (request2) => withForegroundSignalHandoff(
|
|
1323
|
+
{
|
|
1324
|
+
input: terminalInput,
|
|
1325
|
+
pauseReadline: () => readline?.pause(),
|
|
1326
|
+
resumeReadline: () => readline?.resume()
|
|
1327
|
+
},
|
|
1328
|
+
() => options.runner(request2)
|
|
1329
|
+
);
|
|
1330
|
+
const session = await createShellSession({
|
|
1331
|
+
runner: foregroundRunner,
|
|
1332
|
+
io: streamIO(output, error),
|
|
1333
|
+
agent: options.agent,
|
|
1334
|
+
cwd: options.cwd,
|
|
1335
|
+
env: options.env,
|
|
1336
|
+
version: options.version
|
|
1337
|
+
});
|
|
1338
|
+
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
1339
|
+
readline = createInterface({
|
|
1340
|
+
input,
|
|
1341
|
+
output,
|
|
1342
|
+
terminal: true,
|
|
1343
|
+
historySize: 100,
|
|
1344
|
+
removeHistoryDuplicates: true,
|
|
1345
|
+
completer
|
|
1346
|
+
});
|
|
1347
|
+
readline.on("SIGINT", () => {
|
|
1348
|
+
if (session.interruptAgent()) {
|
|
1349
|
+
output.write(`${resetShellPromptStyle()}
|
|
1350
|
+
`);
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
output.write(`${resetShellPromptStyle()}
|
|
1354
|
+
`);
|
|
1355
|
+
readline?.setPrompt(session.prompt());
|
|
1356
|
+
readline?.write(null, { ctrl: true, name: "u" });
|
|
1357
|
+
});
|
|
1358
|
+
output.write(session.banner());
|
|
1359
|
+
readline.setPrompt(session.prompt());
|
|
1360
|
+
readline.prompt();
|
|
1175
1361
|
try {
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
stdin: options.stdin,
|
|
1188
|
-
stdout: options.stdout,
|
|
1189
|
-
stderr: options.stderr,
|
|
1190
|
-
pathReplacements: projected.pathReplacements
|
|
1191
|
-
});
|
|
1192
|
-
const parsed = parseJsonOutput(result.stdout);
|
|
1193
|
-
return {
|
|
1194
|
-
args: [...args],
|
|
1195
|
-
projectedArgs: projected.args,
|
|
1196
|
-
entrypoint,
|
|
1197
|
-
...result,
|
|
1198
|
-
...parsed,
|
|
1199
|
-
streamingStdout,
|
|
1200
|
-
droppedSpecKeys: projected.droppedSpecKeys
|
|
1201
|
-
};
|
|
1202
|
-
} catch (error) {
|
|
1203
|
-
return setupFailure(args, error);
|
|
1362
|
+
for await (const line of readline) {
|
|
1363
|
+
output.write(resetShellPromptStyle());
|
|
1364
|
+
const action = await session.handleLine(line);
|
|
1365
|
+
if (action === "exit") {
|
|
1366
|
+
readline.close();
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
output.write("\n");
|
|
1370
|
+
readline.setPrompt(session.prompt());
|
|
1371
|
+
readline.prompt();
|
|
1372
|
+
}
|
|
1204
1373
|
} finally {
|
|
1205
|
-
|
|
1374
|
+
output.write(resetShellPromptStyle());
|
|
1375
|
+
readline.close();
|
|
1206
1376
|
}
|
|
1207
1377
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return result;
|
|
1378
|
+
function ciEnabled(value) {
|
|
1379
|
+
if (!value) return false;
|
|
1380
|
+
return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
1381
|
+
}
|
|
1382
|
+
function shouldStartInteractiveShell(options) {
|
|
1383
|
+
const env = options.env ?? process.env;
|
|
1384
|
+
return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
|
|
1216
1385
|
}
|
|
1217
1386
|
|
|
1218
|
-
// src/
|
|
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
|
-
|
|
1387
|
+
// src/agent.ts
|
|
1388
|
+
var accent2 = chalk2.hex("#8B5CF6");
|
|
1389
|
+
var successMark2 = () => chalk2.green("\u2713");
|
|
1390
|
+
var errorMark2 = () => chalk2.red("\u2717");
|
|
1391
|
+
function isRecord(value) {
|
|
1392
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1393
|
+
}
|
|
1394
|
+
function stringValue2(value) {
|
|
1395
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1396
|
+
}
|
|
1397
|
+
function helpText2() {
|
|
1398
|
+
return [
|
|
1399
|
+
accent2.bold("Agent commands"),
|
|
1400
|
+
"",
|
|
1401
|
+
` ${accent2("/new".padEnd(22))} Start a new conversation.`,
|
|
1402
|
+
` ${accent2("/threads".padEnd(22))} List recent conversations.`,
|
|
1403
|
+
` ${accent2("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
|
|
1404
|
+
` ${accent2("/approve [id]".padEnd(22))} Approve a proposed action.`,
|
|
1405
|
+
` ${accent2("/reject [id]".padEnd(22))} Reject a proposed action.`,
|
|
1406
|
+
` ${accent2("/status".padEnd(22))} Show the active conversation.`,
|
|
1407
|
+
` ${accent2("/clear".padEnd(22))} Clear the terminal.`,
|
|
1408
|
+
` ${accent2("/exit".padEnd(22))} Exit the agent.`,
|
|
1409
|
+
"",
|
|
1410
|
+
chalk2.dim(
|
|
1411
|
+
"Everything else is sent to the Tuned Tensor agent; known TT commands still run directly in the shell."
|
|
1412
|
+
),
|
|
1413
|
+
""
|
|
1414
|
+
].join("\n");
|
|
1415
|
+
}
|
|
1416
|
+
function formatThread(thread, activeId) {
|
|
1417
|
+
const active = thread.id === activeId ? accent2("\u25CF") : chalk2.dim("\u25CB");
|
|
1418
|
+
const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
|
|
1419
|
+
const date = new Date(time);
|
|
1420
|
+
const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
|
|
1421
|
+
month: "short",
|
|
1422
|
+
day: "numeric",
|
|
1423
|
+
hour: "2-digit",
|
|
1424
|
+
minute: "2-digit"
|
|
1425
|
+
});
|
|
1426
|
+
return `${active} ${chalk2.dim(thread.id.slice(0, 8))} ${thread.title} ${chalk2.dim(dateLabel)}`;
|
|
1427
|
+
}
|
|
1428
|
+
function actionFromPayload(payload) {
|
|
1429
|
+
const value = isRecord(payload.action) ? payload.action : isRecord(payload.approval) ? payload.approval : payload;
|
|
1430
|
+
const id = stringValue2(value.id);
|
|
1431
|
+
const title = stringValue2(value.title);
|
|
1432
|
+
if (!id || !title) return null;
|
|
1433
|
+
return {
|
|
1434
|
+
id,
|
|
1435
|
+
title,
|
|
1436
|
+
summary: stringValue2(value.summary) ?? "",
|
|
1437
|
+
risk: stringValue2(value.risk) ?? "unknown",
|
|
1438
|
+
status: stringValue2(value.status) ?? "proposed",
|
|
1439
|
+
operation: stringValue2(value.operation) ?? void 0,
|
|
1440
|
+
thread_id: stringValue2(value.thread_id) ?? void 0,
|
|
1441
|
+
turn_id: stringValue2(value.turn_id) ?? void 0,
|
|
1442
|
+
arguments: value.arguments,
|
|
1443
|
+
preview: value.preview,
|
|
1444
|
+
method: stringValue2(value.method) ?? void 0,
|
|
1445
|
+
path: stringValue2(value.path) ?? void 0
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function formatJson(value) {
|
|
1449
|
+
try {
|
|
1450
|
+
return JSON.stringify(value, null, 2);
|
|
1451
|
+
} catch {
|
|
1452
|
+
return String(value);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
var TunedTensorAgentSession = class {
|
|
1456
|
+
constructor(options) {
|
|
1457
|
+
this.options = options;
|
|
1458
|
+
this.thread = options.thread ?? null;
|
|
1459
|
+
}
|
|
1460
|
+
thread;
|
|
1461
|
+
pendingActions = /* @__PURE__ */ new Map();
|
|
1462
|
+
activeRequest = null;
|
|
1463
|
+
responseStarted = false;
|
|
1464
|
+
reasoningActive = false;
|
|
1465
|
+
lineOpen = false;
|
|
1466
|
+
get busy() {
|
|
1467
|
+
return this.activeRequest !== null;
|
|
1468
|
+
}
|
|
1469
|
+
snapshot() {
|
|
1470
|
+
return {
|
|
1471
|
+
thread: this.thread,
|
|
1472
|
+
pendingActions: [...this.pendingActions.values()]
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
interrupt() {
|
|
1476
|
+
if (!this.activeRequest) return false;
|
|
1477
|
+
this.activeRequest.abort("user-stop");
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
endOpenLine() {
|
|
1481
|
+
if (this.lineOpen) this.options.io.write("\n");
|
|
1482
|
+
this.lineOpen = false;
|
|
1483
|
+
this.reasoningActive = false;
|
|
1484
|
+
}
|
|
1485
|
+
renderAction(action) {
|
|
1486
|
+
this.endOpenLine();
|
|
1487
|
+
this.options.io.write(
|
|
1488
|
+
`
|
|
1489
|
+
${chalk2.yellow.bold("Approval required")} ${chalk2.dim(action.id.slice(0, 8))}
|
|
1490
|
+
`
|
|
1491
|
+
);
|
|
1492
|
+
this.options.io.write(`${chalk2.bold(action.title)} ${chalk2.yellow(`[${action.risk} risk]`)}
|
|
1493
|
+
`);
|
|
1494
|
+
if (action.summary) this.options.io.write(`${action.summary}
|
|
1495
|
+
`);
|
|
1496
|
+
const request2 = [action.method, action.path].filter(Boolean).join(" ");
|
|
1497
|
+
const technical = {
|
|
1498
|
+
operation: action.operation,
|
|
1499
|
+
...request2 ? { request: request2 } : {},
|
|
1500
|
+
arguments: action.arguments ?? null,
|
|
1501
|
+
preview: action.preview ?? null
|
|
1502
|
+
};
|
|
1503
|
+
this.options.io.write(`${chalk2.bold("Proposed action")}
|
|
1504
|
+
`);
|
|
1505
|
+
this.options.io.write(`${formatJson(technical)}
|
|
1506
|
+
`);
|
|
1507
|
+
this.options.io.write(
|
|
1508
|
+
chalk2.dim("Use /approve or /reject. The action will not run without approval.") + "\n"
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
renderEvent(event) {
|
|
1512
|
+
const payload = event.payload;
|
|
1513
|
+
if (event.type === "text_delta") {
|
|
1514
|
+
const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
|
|
1515
|
+
if (!delta) return;
|
|
1516
|
+
const followsReasoning = this.reasoningActive;
|
|
1517
|
+
if (followsReasoning) this.endOpenLine();
|
|
1518
|
+
if (!this.responseStarted) {
|
|
1519
|
+
this.options.io.write(`
|
|
1520
|
+
${accent2.bold("tt")} `);
|
|
1521
|
+
this.responseStarted = true;
|
|
1522
|
+
this.lineOpen = true;
|
|
1523
|
+
} else if (followsReasoning) {
|
|
1524
|
+
this.options.io.write("\n");
|
|
1525
|
+
}
|
|
1526
|
+
this.options.io.write(delta);
|
|
1527
|
+
this.lineOpen = !delta.endsWith("\n");
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
if (event.type === "reasoning_delta") {
|
|
1531
|
+
const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
|
|
1532
|
+
if (!delta) return;
|
|
1533
|
+
if (!this.reasoningActive) {
|
|
1534
|
+
this.endOpenLine();
|
|
1535
|
+
this.options.io.write("\n");
|
|
1536
|
+
this.reasoningActive = true;
|
|
1537
|
+
}
|
|
1538
|
+
this.options.io.write(chalk2.dim.italic(delta));
|
|
1539
|
+
this.lineOpen = !delta.endsWith("\n");
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
if (event.type === "tool_call") {
|
|
1543
|
+
this.endOpenLine();
|
|
1544
|
+
const name = stringValue2(payload.name) ?? stringValue2(isRecord(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
1545
|
+
this.options.io.write(chalk2.dim(` \u25CB ${name}
|
|
1546
|
+
`));
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (event.type === "tool_result") {
|
|
1550
|
+
this.endOpenLine();
|
|
1551
|
+
const failed = payload.status === "error" || Boolean(payload.error);
|
|
1552
|
+
this.options.io.write(
|
|
1553
|
+
chalk2.dim(` ${failed ? chalk2.red("\u2717") : chalk2.green("\u2713")} Tool ${failed ? "failed" : "complete"}
|
|
1554
|
+
`)
|
|
1555
|
+
);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
if (event.type === "approval_required") {
|
|
1559
|
+
const action = actionFromPayload(payload);
|
|
1560
|
+
if (!action) return;
|
|
1561
|
+
this.pendingActions.set(action.id, action);
|
|
1562
|
+
this.renderAction(action);
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (event.type === "action_started") {
|
|
1566
|
+
this.endOpenLine();
|
|
1567
|
+
this.options.io.write(chalk2.dim(" \u25CB Running approved action\u2026\n"));
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
if (event.type === "action_result") {
|
|
1571
|
+
this.endOpenLine();
|
|
1572
|
+
const failed = payload.status === "failed" || Boolean(payload.error);
|
|
1573
|
+
this.options.io.write(
|
|
1574
|
+
`${failed ? errorMark2() : successMark2()} Approved action ${failed ? "failed" : "completed"}.
|
|
1575
|
+
`
|
|
1576
|
+
);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if (event.type === "error") {
|
|
1580
|
+
this.endOpenLine();
|
|
1581
|
+
const message = stringValue2(payload.message) ?? stringValue2(payload.error) ?? "The agent could not complete this request.";
|
|
1582
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1583
|
+
`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async ensureThread() {
|
|
1587
|
+
if (!this.thread) this.thread = await this.options.client.createThread();
|
|
1588
|
+
return this.thread;
|
|
1589
|
+
}
|
|
1590
|
+
async runStream(operation) {
|
|
1591
|
+
if (this.activeRequest) {
|
|
1592
|
+
this.options.io.writeError(`${errorMark2()} A response is already running.
|
|
1593
|
+
`);
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
const controller = new AbortController();
|
|
1597
|
+
this.activeRequest = controller;
|
|
1598
|
+
this.responseStarted = false;
|
|
1599
|
+
this.reasoningActive = false;
|
|
1600
|
+
this.lineOpen = false;
|
|
1601
|
+
try {
|
|
1602
|
+
const result = await operation(controller.signal);
|
|
1603
|
+
this.endOpenLine();
|
|
1604
|
+
for (const action of result.actions) {
|
|
1605
|
+
this.pendingActions.set(action.id, action);
|
|
1606
|
+
}
|
|
1607
|
+
return result;
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
this.endOpenLine();
|
|
1610
|
+
if (controller.signal.aborted) {
|
|
1611
|
+
this.options.io.write(chalk2.dim("Response stopped.\n"));
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
throw error;
|
|
1615
|
+
} finally {
|
|
1616
|
+
this.activeRequest = null;
|
|
1617
|
+
this.responseStarted = false;
|
|
1618
|
+
this.reasoningActive = false;
|
|
1619
|
+
this.lineOpen = false;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async send(prompt) {
|
|
1623
|
+
const normalized = prompt.trim();
|
|
1624
|
+
if (!normalized) return null;
|
|
1625
|
+
const thread = await this.ensureThread();
|
|
1626
|
+
return await this.runStream(
|
|
1627
|
+
async (signal) => await this.options.client.runTurn(
|
|
1628
|
+
thread.id,
|
|
1629
|
+
normalized,
|
|
1630
|
+
(event) => this.renderEvent(event),
|
|
1631
|
+
signal
|
|
1632
|
+
)
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
resolvePendingAction(idOrPrefix) {
|
|
1636
|
+
const candidates = [...this.pendingActions.values()].filter(
|
|
1637
|
+
(action) => idOrPrefix ? action.id.startsWith(idOrPrefix) : true
|
|
1638
|
+
);
|
|
1639
|
+
if (candidates.length === 0) {
|
|
1640
|
+
throw new Error(
|
|
1641
|
+
idOrPrefix ? `No pending action matches ${idOrPrefix}.` : "There is no pending action."
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
if (candidates.length > 1) {
|
|
1645
|
+
throw new Error("More than one action is pending; include its ID or prefix.");
|
|
1646
|
+
}
|
|
1647
|
+
return candidates[0];
|
|
1648
|
+
}
|
|
1649
|
+
async approve(idOrPrefix) {
|
|
1650
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1651
|
+
const result = await this.runStream(
|
|
1652
|
+
async (signal) => await this.options.client.approveAction(
|
|
1653
|
+
action.id,
|
|
1654
|
+
(event) => this.renderEvent(event),
|
|
1655
|
+
signal
|
|
1656
|
+
)
|
|
1657
|
+
);
|
|
1658
|
+
if (result) this.pendingActions.delete(action.id);
|
|
1659
|
+
}
|
|
1660
|
+
async reject(idOrPrefix) {
|
|
1661
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1662
|
+
await this.options.client.rejectAction(action.id);
|
|
1663
|
+
this.pendingActions.delete(action.id);
|
|
1664
|
+
this.options.io.write(`${successMark2()} Action rejected.
|
|
1665
|
+
`);
|
|
1666
|
+
}
|
|
1667
|
+
async resolveThread(idOrPrefix) {
|
|
1668
|
+
const id = idOrPrefix.trim();
|
|
1669
|
+
if (!id) throw new Error("Usage: /resume <conversation-id>");
|
|
1670
|
+
if (id.length >= 32) return await this.options.client.getThread(id);
|
|
1671
|
+
const matches = (await this.options.client.listThreads()).filter((thread) => thread.id.startsWith(id));
|
|
1672
|
+
if (matches.length === 0) throw new Error(`No conversation matches ${id}.`);
|
|
1673
|
+
if (matches.length > 1) {
|
|
1674
|
+
throw new Error(`Conversation prefix ${id} is ambiguous.`);
|
|
1675
|
+
}
|
|
1676
|
+
return await this.options.client.getThread(matches[0].id);
|
|
1677
|
+
}
|
|
1678
|
+
async handleCommand(input) {
|
|
1679
|
+
const tokens = tokenizeShellInput(input);
|
|
1680
|
+
const command = tokens[0].slice(1).toLowerCase();
|
|
1681
|
+
const args = tokens.slice(1);
|
|
1682
|
+
switch (command) {
|
|
1683
|
+
case "help":
|
|
1684
|
+
this.options.io.write(helpText2());
|
|
1685
|
+
return "continue";
|
|
1686
|
+
case "new":
|
|
1687
|
+
if (args.length > 0) throw new Error("/new does not accept arguments.");
|
|
1688
|
+
this.thread = null;
|
|
1689
|
+
this.pendingActions.clear();
|
|
1690
|
+
this.options.io.write(`${successMark2()} New conversation ready.
|
|
1691
|
+
`);
|
|
1692
|
+
return "continue";
|
|
1693
|
+
case "threads": {
|
|
1694
|
+
if (args.length > 0) throw new Error("/threads does not accept arguments.");
|
|
1695
|
+
const threads = await this.options.client.listThreads();
|
|
1696
|
+
if (threads.length === 0) {
|
|
1697
|
+
this.options.io.write(chalk2.dim("No conversations yet.\n"));
|
|
1698
|
+
return "continue";
|
|
1699
|
+
}
|
|
1700
|
+
this.options.io.write(
|
|
1701
|
+
`${threads.slice(0, 20).map((thread) => formatThread(thread, this.thread?.id)).join("\n")}
|
|
1702
|
+
`
|
|
1703
|
+
);
|
|
1704
|
+
return "continue";
|
|
1705
|
+
}
|
|
1706
|
+
case "resume":
|
|
1707
|
+
if (args.length !== 1) throw new Error("Usage: /resume <conversation-id>");
|
|
1708
|
+
{
|
|
1709
|
+
const detail = await this.resolveThread(args[0]);
|
|
1710
|
+
this.thread = detail.thread;
|
|
1711
|
+
this.pendingActions.clear();
|
|
1712
|
+
for (const action of detail.actions) {
|
|
1713
|
+
if (action.status === "proposed") {
|
|
1714
|
+
this.pendingActions.set(action.id, action);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
this.options.io.write(
|
|
1719
|
+
`${successMark2()} Resumed ${this.thread.title} (${this.thread.id.slice(0, 8)}).
|
|
1720
|
+
`
|
|
1721
|
+
);
|
|
1722
|
+
return "continue";
|
|
1723
|
+
case "approve":
|
|
1724
|
+
if (args.length > 1) throw new Error("Usage: /approve [action-id]");
|
|
1725
|
+
await this.approve(args[0]);
|
|
1726
|
+
return "continue";
|
|
1727
|
+
case "reject":
|
|
1728
|
+
if (args.length > 1) throw new Error("Usage: /reject [action-id]");
|
|
1729
|
+
await this.reject(args[0]);
|
|
1730
|
+
return "continue";
|
|
1731
|
+
case "status":
|
|
1732
|
+
if (args.length > 0) throw new Error("/status does not accept arguments.");
|
|
1733
|
+
this.options.io.write(
|
|
1734
|
+
this.thread ? `${formatThread(this.thread, this.thread.id)}
|
|
1735
|
+
` : chalk2.dim("New conversation; send a message to create it.\n")
|
|
1736
|
+
);
|
|
1737
|
+
this.options.io.write(
|
|
1738
|
+
chalk2.dim(`${this.pendingActions.size} pending approval(s).
|
|
1739
|
+
`)
|
|
1740
|
+
);
|
|
1741
|
+
return "continue";
|
|
1742
|
+
case "clear":
|
|
1743
|
+
if (args.length > 0) throw new Error("/clear does not accept arguments.");
|
|
1744
|
+
this.options.io.clear();
|
|
1745
|
+
return "continue";
|
|
1746
|
+
case "exit":
|
|
1747
|
+
if (args.length > 0) throw new Error("/exit does not accept arguments.");
|
|
1748
|
+
return "exit";
|
|
1749
|
+
default:
|
|
1750
|
+
throw new Error(`Unknown agent command: /${command}. Use /help.`);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
async handleLine(input) {
|
|
1754
|
+
try {
|
|
1755
|
+
const normalized = input.trim();
|
|
1756
|
+
if (!normalized) return "continue";
|
|
1757
|
+
if (/^(exit|quit)$/i.test(normalized)) return "exit";
|
|
1758
|
+
if (normalized.startsWith("/")) return await this.handleCommand(normalized);
|
|
1759
|
+
await this.send(normalized);
|
|
1760
|
+
return "continue";
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1763
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1764
|
+
`);
|
|
1765
|
+
return "continue";
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
// src/local-runner.ts
|
|
1771
|
+
import { constants as osConstants } from "os";
|
|
1772
|
+
import { randomUUID } from "crypto";
|
|
1773
|
+
import { createRequire } from "module";
|
|
1774
|
+
import { spawn } from "child_process";
|
|
1775
|
+
import {
|
|
1776
|
+
readFile as readFile2,
|
|
1777
|
+
rm,
|
|
1778
|
+
stat as stat3,
|
|
1779
|
+
writeFile
|
|
1780
|
+
} from "fs/promises";
|
|
1781
|
+
import {
|
|
1782
|
+
basename as basename3,
|
|
1783
|
+
dirname as dirname2,
|
|
1784
|
+
isAbsolute as isAbsolute3,
|
|
1785
|
+
join as join3,
|
|
1786
|
+
resolve as resolve3
|
|
1787
|
+
} from "path";
|
|
1788
|
+
|
|
1789
|
+
// src/base-models.ts
|
|
1790
|
+
var SUPPORTED_BASE_MODELS = [
|
|
1791
|
+
"google/gemma-4-E2B-it",
|
|
1792
|
+
"google/gemma-4-E4B-it",
|
|
1793
|
+
"Qwen/Qwen3.5-2B",
|
|
1794
|
+
"Qwen/Qwen3-VL-2B-Instruct",
|
|
1795
|
+
"Qwen/Qwen3.5-4B",
|
|
1796
|
+
"meta-llama/Llama-3.2-3B-Instruct",
|
|
1797
|
+
"microsoft/Phi-4-mini-instruct",
|
|
1798
|
+
"ibm-granite/granite-3.3-2b-instruct",
|
|
1799
|
+
"bigcode/starcoder2-3b"
|
|
1286
1800
|
];
|
|
1287
|
-
var
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1801
|
+
var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
|
|
1802
|
+
for (const model of SUPPORTED_BASE_MODELS) {
|
|
1803
|
+
BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
|
|
1804
|
+
}
|
|
1805
|
+
for (const [alias, model] of [
|
|
1806
|
+
["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
|
|
1807
|
+
["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
|
|
1808
|
+
["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
|
|
1809
|
+
["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
|
|
1810
|
+
["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
|
|
1811
|
+
["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
|
|
1812
|
+
["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
|
|
1813
|
+
["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
|
|
1814
|
+
["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
|
|
1815
|
+
["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1816
|
+
["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1817
|
+
["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1818
|
+
["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
|
|
1819
|
+
["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
|
|
1820
|
+
["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
|
|
1821
|
+
["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1822
|
+
["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1823
|
+
["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1824
|
+
["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1825
|
+
["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1826
|
+
["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1827
|
+
["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1828
|
+
["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
|
|
1829
|
+
["starcoder2-3b", "bigcode/starcoder2-3b"]
|
|
1830
|
+
]) {
|
|
1831
|
+
BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
|
|
1832
|
+
}
|
|
1833
|
+
var UnsupportedBaseModelError = class extends Error {
|
|
1834
|
+
constructor(model) {
|
|
1835
|
+
const supported = SUPPORTED_BASE_MODELS.join(", ");
|
|
1836
|
+
super(
|
|
1837
|
+
`Unsupported base_model "${String(model)}". Supported base models: ${supported}`
|
|
1838
|
+
);
|
|
1839
|
+
this.name = "UnsupportedBaseModelError";
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
function canonicalizeBaseModel(model) {
|
|
1843
|
+
if (typeof model !== "string" || !model.trim()) {
|
|
1844
|
+
throw new UnsupportedBaseModelError(model);
|
|
1845
|
+
}
|
|
1846
|
+
const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
|
|
1847
|
+
if (!canonical) {
|
|
1848
|
+
throw new UnsupportedBaseModelError(model);
|
|
1849
|
+
}
|
|
1850
|
+
return canonical;
|
|
1851
|
+
}
|
|
1852
|
+
function canonicalizeSpecBaseModel(body) {
|
|
1853
|
+
if ("base_model" in body && body.base_model !== void 0) {
|
|
1854
|
+
return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
|
|
1855
|
+
}
|
|
1856
|
+
return body;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// src/project-spec.ts
|
|
1860
|
+
var CLOUD_SPEC_KEYS = [
|
|
1861
|
+
"name",
|
|
1862
|
+
"description",
|
|
1863
|
+
"base_model",
|
|
1864
|
+
"system_prompt",
|
|
1865
|
+
"guidelines",
|
|
1866
|
+
"constraints",
|
|
1867
|
+
"examples",
|
|
1868
|
+
"eval_cases"
|
|
1297
1869
|
];
|
|
1298
|
-
|
|
1299
|
-
|
|
1870
|
+
var LOCAL_ONLY_SPEC_KEYS = [
|
|
1871
|
+
"hyperparameters",
|
|
1872
|
+
"dataset_prebuilt"
|
|
1873
|
+
];
|
|
1874
|
+
var LOCAL_SPEC_KEYS = [
|
|
1875
|
+
"id",
|
|
1876
|
+
"name",
|
|
1877
|
+
"description",
|
|
1878
|
+
"system_prompt",
|
|
1879
|
+
"guidelines",
|
|
1880
|
+
"constraints",
|
|
1881
|
+
"base_model",
|
|
1882
|
+
"examples",
|
|
1883
|
+
...LOCAL_ONLY_SPEC_KEYS
|
|
1884
|
+
];
|
|
1885
|
+
var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
|
|
1886
|
+
var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
|
|
1887
|
+
var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
|
|
1888
|
+
var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
|
|
1889
|
+
...CLOUD_SPEC_KEYS,
|
|
1890
|
+
...LOCAL_SPEC_KEYS
|
|
1891
|
+
]);
|
|
1892
|
+
function projectSpec(raw, supportedKeys) {
|
|
1893
|
+
const body = {};
|
|
1894
|
+
const droppedKeys = [];
|
|
1895
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1896
|
+
if (supportedKeys.has(key)) {
|
|
1897
|
+
body[key] = value;
|
|
1898
|
+
} else {
|
|
1899
|
+
droppedKeys.push(key);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return {
|
|
1903
|
+
body: canonicalizeSpecBaseModel(body),
|
|
1904
|
+
droppedKeys
|
|
1905
|
+
};
|
|
1300
1906
|
}
|
|
1301
|
-
function
|
|
1302
|
-
return
|
|
1907
|
+
function projectCloudSpec(raw) {
|
|
1908
|
+
return projectSpec(raw, CLOUD_SPEC_KEY_SET);
|
|
1303
1909
|
}
|
|
1304
|
-
function
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1910
|
+
function projectLocalSpec(raw) {
|
|
1911
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET);
|
|
1912
|
+
}
|
|
1913
|
+
function hasLocalOnlySpecFields(raw) {
|
|
1914
|
+
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
1915
|
+
}
|
|
1916
|
+
function unknownProjectSpecKeys(raw) {
|
|
1917
|
+
return Object.keys(raw).filter(
|
|
1918
|
+
(key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// src/output.ts
|
|
1923
|
+
import chalk3 from "chalk";
|
|
1924
|
+
import Table from "cli-table3";
|
|
1925
|
+
var jsonMode = false;
|
|
1926
|
+
function setJsonMode(enabled) {
|
|
1927
|
+
jsonMode = enabled;
|
|
1928
|
+
}
|
|
1929
|
+
function isJsonMode() {
|
|
1930
|
+
return jsonMode;
|
|
1931
|
+
}
|
|
1932
|
+
function printJson(data) {
|
|
1933
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1934
|
+
}
|
|
1935
|
+
function printTable(headers, rows2, meta) {
|
|
1936
|
+
const table = new Table({
|
|
1937
|
+
head: headers.map((h) => chalk3.bold.cyan(h)),
|
|
1938
|
+
style: { head: [], border: [] }
|
|
1309
1939
|
});
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1940
|
+
for (const row of rows2) {
|
|
1941
|
+
table.push(row);
|
|
1942
|
+
}
|
|
1943
|
+
console.log(table.toString());
|
|
1944
|
+
if (meta) {
|
|
1945
|
+
const totalPages = Math.ceil(meta.total / meta.per_page);
|
|
1946
|
+
console.log(
|
|
1947
|
+
chalk3.dim(
|
|
1948
|
+
`
|
|
1949
|
+
Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
1950
|
+
)
|
|
1951
|
+
);
|
|
1315
1952
|
}
|
|
1316
|
-
return groups;
|
|
1317
1953
|
}
|
|
1318
|
-
function
|
|
1319
|
-
const
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
1954
|
+
function printDetail(fields) {
|
|
1955
|
+
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
1956
|
+
for (const [label, value] of fields) {
|
|
1957
|
+
const paddedLabel = label.padEnd(maxLabel);
|
|
1958
|
+
console.log(`${chalk3.bold.cyan(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
|
|
1324
1959
|
}
|
|
1325
|
-
const mode = override[1];
|
|
1326
|
-
const prefix = `${leadingWhitespace}${mode} `;
|
|
1327
|
-
return {
|
|
1328
|
-
prefix,
|
|
1329
|
-
mode,
|
|
1330
|
-
fragment: content.slice(override[0].length)
|
|
1331
|
-
};
|
|
1332
1960
|
}
|
|
1333
|
-
function
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
1961
|
+
function printSuccess(message) {
|
|
1962
|
+
console.log(chalk3.green("\u2713") + " " + message);
|
|
1963
|
+
}
|
|
1964
|
+
function printWarning(message) {
|
|
1965
|
+
console.log(chalk3.yellow("!") + " " + message);
|
|
1966
|
+
}
|
|
1967
|
+
function printError(message) {
|
|
1968
|
+
console.error(chalk3.red("\u2717") + " " + message);
|
|
1969
|
+
}
|
|
1970
|
+
function reportError(err) {
|
|
1971
|
+
const isApi = err instanceof ApiError;
|
|
1972
|
+
const message = err?.message || String(err);
|
|
1973
|
+
if (jsonMode) {
|
|
1974
|
+
printJson({
|
|
1975
|
+
error: {
|
|
1976
|
+
status: isApi ? err.status : null,
|
|
1977
|
+
code: isApi ? err.code : "CLI_ERROR",
|
|
1978
|
+
message
|
|
1979
|
+
}
|
|
1353
1980
|
});
|
|
1354
|
-
|
|
1981
|
+
} else if (isApi) {
|
|
1982
|
+
printError(`[${err.status}] ${message}`);
|
|
1983
|
+
} else {
|
|
1984
|
+
printError(message);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
function formatDate(iso) {
|
|
1988
|
+
if (!iso) return "\u2014";
|
|
1989
|
+
return new Date(iso).toLocaleString();
|
|
1990
|
+
}
|
|
1991
|
+
function formatStatus(status2) {
|
|
1992
|
+
const map = {
|
|
1993
|
+
completed: chalk3.green,
|
|
1994
|
+
running: chalk3.blue,
|
|
1995
|
+
training: chalk3.blue,
|
|
1996
|
+
evaluating: chalk3.blue,
|
|
1997
|
+
preparing: chalk3.yellow,
|
|
1998
|
+
pending: chalk3.yellow,
|
|
1999
|
+
uploading: chalk3.yellow,
|
|
2000
|
+
failed: chalk3.red,
|
|
2001
|
+
cancelled: chalk3.dim,
|
|
2002
|
+
validated: chalk3.green,
|
|
2003
|
+
invalid: chalk3.red
|
|
1355
2004
|
};
|
|
2005
|
+
const colorFn = map[status2] || chalk3.white;
|
|
2006
|
+
return colorFn(status2);
|
|
2007
|
+
}
|
|
2008
|
+
function truncate(str, max) {
|
|
2009
|
+
if (str.length <= max) return str;
|
|
2010
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
2011
|
+
}
|
|
2012
|
+
function shortId(id) {
|
|
2013
|
+
return id.slice(0, 8);
|
|
1356
2014
|
}
|
|
1357
2015
|
|
|
1358
|
-
// src/
|
|
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 };
|
|
2016
|
+
// src/local-output.ts
|
|
2017
|
+
function isRecord2(value) {
|
|
2018
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2019
|
+
}
|
|
2020
|
+
function text(value) {
|
|
2021
|
+
if (typeof value === "string") return value;
|
|
2022
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
2023
|
+
return String(value);
|
|
1378
2024
|
}
|
|
2025
|
+
return void 0;
|
|
1379
2026
|
}
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
|
|
2027
|
+
function number(value) {
|
|
2028
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2029
|
+
}
|
|
2030
|
+
function record2(value) {
|
|
2031
|
+
return isRecord2(value) ? value : void 0;
|
|
2032
|
+
}
|
|
2033
|
+
function rows(value) {
|
|
2034
|
+
return Array.isArray(value) ? value.filter(isRecord2) : [];
|
|
2035
|
+
}
|
|
2036
|
+
function displayId(value) {
|
|
2037
|
+
const id = text(value);
|
|
2038
|
+
return id ? shortId(id) : "\u2014";
|
|
2039
|
+
}
|
|
2040
|
+
function displayNumber(value, digits = 4) {
|
|
2041
|
+
const parsed = number(value);
|
|
2042
|
+
if (parsed === void 0) return void 0;
|
|
2043
|
+
return parsed.toFixed(digits).replace(/\.?0+$/, "");
|
|
2044
|
+
}
|
|
2045
|
+
function displayBytes(value) {
|
|
2046
|
+
const bytes = number(value);
|
|
2047
|
+
if (bytes === void 0 || bytes < 0) return void 0;
|
|
2048
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
2049
|
+
let amount = bytes;
|
|
2050
|
+
let unit = 0;
|
|
2051
|
+
while (amount >= 1024 && unit < units.length - 1) {
|
|
2052
|
+
amount /= 1024;
|
|
2053
|
+
unit += 1;
|
|
2054
|
+
}
|
|
2055
|
+
const precision = amount >= 10 || unit === 0 ? 0 : 1;
|
|
2056
|
+
return `${amount.toFixed(precision)} ${units[unit]}`;
|
|
2057
|
+
}
|
|
2058
|
+
function displayCommand(value) {
|
|
2059
|
+
if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
|
|
2060
|
+
return void 0;
|
|
2061
|
+
}
|
|
2062
|
+
return value.map(
|
|
2063
|
+
(part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
|
|
2064
|
+
).join(" ");
|
|
2065
|
+
}
|
|
2066
|
+
function status(value) {
|
|
2067
|
+
const parsed = text(value) ?? "unknown";
|
|
2068
|
+
return formatStatus(parsed);
|
|
2069
|
+
}
|
|
2070
|
+
function gateFrom(value) {
|
|
2071
|
+
return record2(record2(value)?.general_regression);
|
|
2072
|
+
}
|
|
2073
|
+
function renderGate(value) {
|
|
2074
|
+
const gate = gateFrom(value);
|
|
2075
|
+
if (!gate) {
|
|
2076
|
+
printWarning(
|
|
2077
|
+
"General regression was not run; this model cannot be activated yet."
|
|
2078
|
+
);
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
if (gate.passed === true) {
|
|
2082
|
+
printSuccess("General regression gate passed");
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
printWarning(
|
|
2086
|
+
"The local run completed, but the general regression gate failed."
|
|
2087
|
+
);
|
|
2088
|
+
const failures = Array.isArray(gate.failures) ? gate.failures.filter(
|
|
2089
|
+
(failure) => typeof failure === "string"
|
|
2090
|
+
) : [];
|
|
2091
|
+
for (const failure of failures) printWarning(failure);
|
|
2092
|
+
}
|
|
2093
|
+
function renderDoctor(value) {
|
|
2094
|
+
const checks = rows(value.checks);
|
|
2095
|
+
printTable(
|
|
2096
|
+
["Check", "Status", "Details"],
|
|
2097
|
+
checks.map((check) => [
|
|
2098
|
+
text(check.name) ?? "unknown",
|
|
2099
|
+
check.ok === true ? formatStatus("completed") : formatStatus("failed"),
|
|
2100
|
+
truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
|
|
2101
|
+
])
|
|
2102
|
+
);
|
|
2103
|
+
if (value.ok === true) {
|
|
2104
|
+
printSuccess("Local environment is ready");
|
|
2105
|
+
} else {
|
|
2106
|
+
printError("Local environment needs attention");
|
|
2107
|
+
}
|
|
2108
|
+
if (value.config_path) {
|
|
2109
|
+
printDetail([["Config", text(value.config_path)]]);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function renderInit(value) {
|
|
2113
|
+
printSuccess("Created local Tuned Tensor project");
|
|
2114
|
+
printDetail([
|
|
2115
|
+
["Path", text(value.path)],
|
|
2116
|
+
["Name", text(value.name)],
|
|
2117
|
+
["ID", text(value.id)],
|
|
2118
|
+
["Base model", text(value.base_model)],
|
|
2119
|
+
["Config", text(value.config_path)]
|
|
2120
|
+
]);
|
|
1383
2121
|
}
|
|
1384
|
-
function
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
2122
|
+
function renderValidate(value) {
|
|
2123
|
+
printSuccess("Local behavior spec is valid");
|
|
2124
|
+
printDetail([
|
|
2125
|
+
["Input", text(value.input_path)],
|
|
2126
|
+
["Config", text(value.config_path)],
|
|
2127
|
+
["Spec", text(value.behavior_spec_id)],
|
|
2128
|
+
["Base model", text(value.base_model)],
|
|
2129
|
+
["Dataset", text(value.dataset_format)],
|
|
2130
|
+
["Artifacts", text(value.artifact_root)],
|
|
2131
|
+
["Store", text(value.store_root)],
|
|
2132
|
+
["Dry run", text(value.dry_run)]
|
|
2133
|
+
]);
|
|
1388
2134
|
}
|
|
1389
|
-
function
|
|
1390
|
-
|
|
2135
|
+
function renderRun(value) {
|
|
2136
|
+
const runStatus = text(value.status) ?? "unknown";
|
|
2137
|
+
if (runStatus === "completed") {
|
|
2138
|
+
printSuccess("Local run completed");
|
|
2139
|
+
} else {
|
|
2140
|
+
printWarning(`Local run finished with status ${runStatus}`);
|
|
2141
|
+
}
|
|
2142
|
+
const comparison = record2(value.comparison);
|
|
2143
|
+
printDetail([
|
|
2144
|
+
["Run", text(value.run_id)],
|
|
2145
|
+
["Status", status(runStatus)],
|
|
2146
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2147
|
+
["Report", text(value.report_path)],
|
|
2148
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2149
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2150
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
|
|
2151
|
+
]);
|
|
2152
|
+
renderGate(value);
|
|
1391
2153
|
}
|
|
1392
|
-
function
|
|
1393
|
-
const
|
|
1394
|
-
|
|
2154
|
+
function renderRunsList(value) {
|
|
2155
|
+
const runRows = rows(value);
|
|
2156
|
+
if (runRows.length === 0) {
|
|
2157
|
+
printWarning("No local runs found.");
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
printTable(
|
|
2161
|
+
["Run", "Spec", "Status", "Stage", "Model", "Updated"],
|
|
2162
|
+
runRows.map((run) => [
|
|
2163
|
+
displayId(run.id),
|
|
2164
|
+
truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
|
|
2165
|
+
status(run.status),
|
|
2166
|
+
text(run.current_stage) ?? "\u2014",
|
|
2167
|
+
displayId(run.model_id),
|
|
2168
|
+
formatDate(text(run.updated_at))
|
|
2169
|
+
])
|
|
2170
|
+
);
|
|
1395
2171
|
}
|
|
1396
|
-
function
|
|
1397
|
-
|
|
1398
|
-
if (
|
|
1399
|
-
|
|
1400
|
-
|
|
2172
|
+
function renderRunEvents(value) {
|
|
2173
|
+
const eventRows = rows(value);
|
|
2174
|
+
if (eventRows.length === 0) {
|
|
2175
|
+
printWarning("No local run events found.");
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
printTable(
|
|
2179
|
+
["Time", "Stage", "Status", "Message"],
|
|
2180
|
+
eventRows.map((event) => [
|
|
2181
|
+
formatDate(text(event.occurred_at)),
|
|
2182
|
+
text(event.stage) ?? "\u2014",
|
|
2183
|
+
status(event.status),
|
|
2184
|
+
truncate(text(event.message) ?? "\u2014", 80)
|
|
2185
|
+
])
|
|
2186
|
+
);
|
|
1401
2187
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
|
|
2188
|
+
function renderRunRecord(value) {
|
|
2189
|
+
printDetail([
|
|
2190
|
+
["Run", text(value.id) ?? text(value.run_id)],
|
|
2191
|
+
["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
|
|
2192
|
+
["Status", status(value.status)],
|
|
2193
|
+
["Stage", text(value.current_stage)],
|
|
2194
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2195
|
+
["Base model", text(value.base_model)],
|
|
2196
|
+
["Report", text(value.report_path)],
|
|
2197
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2198
|
+
["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
|
|
2199
|
+
]);
|
|
1405
2200
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
2201
|
+
function renderRunReport(value) {
|
|
2202
|
+
const baseline = record2(value.baseline);
|
|
2203
|
+
const candidate = record2(value.candidate);
|
|
2204
|
+
const comparison = record2(value.comparison);
|
|
2205
|
+
const metadata = record2(value.run_metadata);
|
|
2206
|
+
printDetail([
|
|
2207
|
+
["Run", text(value.run_id)],
|
|
2208
|
+
["Status", status(value.status)],
|
|
2209
|
+
["Base model", text(value.base_model)],
|
|
2210
|
+
["Tuned model", text(value.fine_tuned_model_id)],
|
|
2211
|
+
["Training examples", text(metadata?.training_example_count)],
|
|
2212
|
+
["Evaluation examples", text(metadata?.eval_examples_used)],
|
|
2213
|
+
["Baseline score", displayNumber(baseline?.avg_score)],
|
|
2214
|
+
["Candidate score", displayNumber(candidate?.avg_score)],
|
|
2215
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2216
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
|
|
2217
|
+
["Regressions", text(comparison?.regressions)],
|
|
2218
|
+
["Improvements", text(comparison?.improvements)]
|
|
2219
|
+
]);
|
|
2220
|
+
renderGate(value);
|
|
1412
2221
|
}
|
|
1413
|
-
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
2222
|
+
function renderRunComparison(value) {
|
|
2223
|
+
const runA = record2(value.run_a);
|
|
2224
|
+
const runB = record2(value.run_b);
|
|
2225
|
+
const shared = record2(value.shared);
|
|
2226
|
+
const sharedA = record2(shared?.run_a);
|
|
2227
|
+
const sharedB = record2(shared?.run_b);
|
|
2228
|
+
printTable(
|
|
2229
|
+
["Run", "Candidate score", "Token F1", "Examples"],
|
|
2230
|
+
[
|
|
2231
|
+
[
|
|
2232
|
+
displayId(runA?.run_id),
|
|
2233
|
+
displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
|
|
2234
|
+
displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
|
|
2235
|
+
text(shared?.examples) ?? "0"
|
|
2236
|
+
],
|
|
2237
|
+
[
|
|
2238
|
+
displayId(runB?.run_id),
|
|
2239
|
+
displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
|
|
2240
|
+
displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
|
|
2241
|
+
text(shared?.examples) ?? "0"
|
|
2242
|
+
]
|
|
2243
|
+
]
|
|
2244
|
+
);
|
|
2245
|
+
const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
|
|
2246
|
+
for (const note of notes) printWarning(note);
|
|
1419
2247
|
}
|
|
1420
|
-
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
} catch {
|
|
1426
|
-
return void 0;
|
|
2248
|
+
function renderModelsList(value) {
|
|
2249
|
+
const modelRows = rows(value);
|
|
2250
|
+
if (modelRows.length === 0) {
|
|
2251
|
+
printWarning("No local models found.");
|
|
2252
|
+
return;
|
|
1427
2253
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
return state;
|
|
1439
|
-
}));
|
|
1440
|
-
return states.filter((state) => Boolean(state)).sort((left, right) => {
|
|
1441
|
-
const leftUpdated = left.updatedAt ?? "";
|
|
1442
|
-
const rightUpdated = right.updatedAt ?? "";
|
|
1443
|
-
if (leftUpdated !== rightUpdated) {
|
|
1444
|
-
return rightUpdated.localeCompare(leftUpdated);
|
|
1445
|
-
}
|
|
1446
|
-
return right.id.localeCompare(left.id);
|
|
1447
|
-
})[0];
|
|
2254
|
+
printTable(
|
|
2255
|
+
["Model", "Base model", "Run", "Provider", "Created"],
|
|
2256
|
+
modelRows.map((model) => [
|
|
2257
|
+
displayId(model.id),
|
|
2258
|
+
truncate(text(model.base_model) ?? "\u2014", 32),
|
|
2259
|
+
displayId(model.run_id),
|
|
2260
|
+
text(model.provider) ?? "\u2014",
|
|
2261
|
+
formatDate(text(model.created_at))
|
|
2262
|
+
])
|
|
2263
|
+
);
|
|
1448
2264
|
}
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
spec = {
|
|
1464
|
-
path: specPath,
|
|
1465
|
-
id: stringField(specJson.value, "id"),
|
|
1466
|
-
name: stringField(specJson.value, "name"),
|
|
1467
|
-
baseModel: stringField(specJson.value, "base_model"),
|
|
1468
|
-
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
1469
|
-
parseError: false
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
const localConfigPath = join3(cwd, "local-runner.json");
|
|
1474
|
-
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
1475
|
-
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
1476
|
-
if (localConfigJson.invalid) {
|
|
1477
|
-
warnings.push("local-runner.json could not be parsed.");
|
|
1478
|
-
}
|
|
1479
|
-
const localConfigDirectory = hasAdjacentLocalConfig ? dirname2(localConfigPath) : cwd;
|
|
1480
|
-
const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
|
|
1481
|
-
const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
|
|
1482
|
-
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve2(cwd, ".tt-local", "artifacts");
|
|
1483
|
-
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join3(homeDirectory, ".tuned-tensor-local");
|
|
1484
|
-
const cloudPath = cloudConfigPath(env, homeDirectory);
|
|
1485
|
-
const cloudConfigJson = await readJsonObject(cloudPath);
|
|
1486
|
-
if (cloudConfigJson.invalid) {
|
|
1487
|
-
warnings.push("The Tuned Tensor cloud config could not be parsed.");
|
|
1488
|
-
}
|
|
1489
|
-
const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
|
|
1490
|
-
const storedApiKey = stringField(cloudConfigJson.value, "api_key");
|
|
1491
|
-
const apiKey = environmentApiKey || storedApiKey;
|
|
1492
|
-
const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
|
|
1493
|
-
const environmentTarget = targetFromEnvironment(env);
|
|
1494
|
-
if (env.TT_TARGET && !environmentTarget) {
|
|
1495
|
-
warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
|
|
1496
|
-
}
|
|
1497
|
-
const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
|
|
1498
|
-
const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
|
|
1499
|
-
const [activeModelId, latestRun] = await Promise.all([
|
|
1500
|
-
readActiveModelId(storeRoot),
|
|
1501
|
-
readLatestRun(storeRoot)
|
|
1502
|
-
]);
|
|
1503
|
-
return {
|
|
1504
|
-
cwd,
|
|
1505
|
-
projectName: basename3(cwd) || cwd,
|
|
1506
|
-
inferredTarget,
|
|
1507
|
-
targetSource,
|
|
1508
|
-
spec,
|
|
1509
|
-
cloud: {
|
|
1510
|
-
authenticated: Boolean(apiKey),
|
|
1511
|
-
keyPrefix: redactApiKey(apiKey),
|
|
1512
|
-
baseUrl,
|
|
1513
|
-
configPath: cloudPath,
|
|
1514
|
-
configFound: cloudConfigJson.found
|
|
1515
|
-
},
|
|
1516
|
-
local: {
|
|
1517
|
-
configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
|
|
1518
|
-
artifactRoot,
|
|
1519
|
-
storeRoot,
|
|
1520
|
-
activeModelId,
|
|
1521
|
-
latestRun
|
|
1522
|
-
},
|
|
1523
|
-
warnings
|
|
1524
|
-
};
|
|
2265
|
+
function renderModelRecord(value) {
|
|
2266
|
+
const model = record2(value.model) ?? value;
|
|
2267
|
+
const artifact = record2(value.artifact);
|
|
2268
|
+
const integrity = record2(value.integrity);
|
|
2269
|
+
printDetail([
|
|
2270
|
+
["Model", text(model.id) ?? text(value.active)],
|
|
2271
|
+
["Base model", text(model.base_model)],
|
|
2272
|
+
["Run", text(model.run_id)],
|
|
2273
|
+
["Provider", text(model.provider)],
|
|
2274
|
+
["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
|
|
2275
|
+
["Manifest", text(value.manifest_path)],
|
|
2276
|
+
["Verified files", text(integrity?.checked)],
|
|
2277
|
+
["Created", formatDate(text(model.created_at))]
|
|
2278
|
+
]);
|
|
1525
2279
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
2280
|
+
function renderModelPrefetch(value) {
|
|
2281
|
+
printSuccess(
|
|
2282
|
+
value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
|
|
2283
|
+
);
|
|
2284
|
+
printDetail([
|
|
2285
|
+
["Base model", text(value.base_model)],
|
|
2286
|
+
["Revision", text(value.snapshot_revision)],
|
|
2287
|
+
["Snapshot", text(value.snapshot_path)],
|
|
2288
|
+
["Files", text(value.file_count)],
|
|
2289
|
+
["Size", displayBytes(value.size_bytes)],
|
|
2290
|
+
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
2291
|
+
]);
|
|
1528
2292
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
return "default";
|
|
2293
|
+
function renderActiveModel(value) {
|
|
2294
|
+
const active = text(value.active) ?? "base";
|
|
2295
|
+
const pointer = record2(value.pointer);
|
|
2296
|
+
if (pointer?.action === "activate") {
|
|
2297
|
+
printSuccess(`Activated local model ${active}`);
|
|
2298
|
+
} else if (pointer?.action === "rollback") {
|
|
2299
|
+
printSuccess(`Rolled back to ${active}`);
|
|
1537
2300
|
}
|
|
2301
|
+
printDetail([
|
|
2302
|
+
["Active", active],
|
|
2303
|
+
["Run", text(pointer?.run_id)],
|
|
2304
|
+
["Previous", text(pointer?.previous_model_id) ?? "base"],
|
|
2305
|
+
["Changed", formatDate(text(pointer?.activated_at))],
|
|
2306
|
+
["Manifest", text(value.manifest_path)]
|
|
2307
|
+
]);
|
|
1538
2308
|
}
|
|
1539
|
-
function
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
1549
|
-
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
1550
|
-
`Cloud endpoint ${context.cloud.baseUrl}`,
|
|
1551
|
-
`Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
|
|
1552
|
-
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1553
|
-
`Artifact root ${context.local.artifactRoot}`,
|
|
1554
|
-
`Store root ${context.local.storeRoot}`
|
|
1555
|
-
];
|
|
1556
|
-
for (const warning of context.warnings) lines.push(`Warning ${warning}`);
|
|
1557
|
-
return lines;
|
|
1558
|
-
}
|
|
1559
|
-
function formatShellStatus(context, selectedTarget) {
|
|
1560
|
-
const lines = [
|
|
1561
|
-
`Workflow ${selectedTarget}`,
|
|
1562
|
-
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
|
|
1563
|
-
];
|
|
1564
|
-
if (selectedTarget === "cloud") {
|
|
1565
|
-
lines.push(
|
|
1566
|
-
`Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
|
|
1567
|
-
`Endpoint ${context.cloud.baseUrl}`,
|
|
1568
|
-
"Remote status not queried"
|
|
1569
|
-
);
|
|
1570
|
-
} else {
|
|
1571
|
-
lines.push(
|
|
1572
|
-
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1573
|
-
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
1574
|
-
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
1575
|
-
"Host checks not run (use doctor)"
|
|
1576
|
-
);
|
|
1577
|
-
}
|
|
1578
|
-
return lines;
|
|
2309
|
+
function renderServePlan(value) {
|
|
2310
|
+
printSuccess("Local serving plan is valid");
|
|
2311
|
+
printDetail([
|
|
2312
|
+
["Model", text(value.model_id) ?? text(value.base_model)],
|
|
2313
|
+
["URL", text(value.url)],
|
|
2314
|
+
["Artifact", text(value.artifact_path)],
|
|
2315
|
+
["Manifest", text(value.manifest_path)],
|
|
2316
|
+
["Command", displayCommand(value.command)]
|
|
2317
|
+
]);
|
|
1579
2318
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
2319
|
+
function renderKnownJson(args, value) {
|
|
2320
|
+
const command = args[0];
|
|
2321
|
+
const subcommand = args[1];
|
|
2322
|
+
if (command === "doctor" && isRecord2(value)) {
|
|
2323
|
+
renderDoctor(value);
|
|
2324
|
+
return true;
|
|
1586
2325
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
`Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
|
|
1591
|
-
);
|
|
1592
|
-
}
|
|
1593
|
-
function tokenizeShellInput(input) {
|
|
1594
|
-
if (input.includes("\0")) {
|
|
1595
|
-
throw new ShellParseError("Command input must not contain NUL bytes.");
|
|
2326
|
+
if (command === "init" && isRecord2(value)) {
|
|
2327
|
+
renderInit(value);
|
|
2328
|
+
return true;
|
|
1596
2329
|
}
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
2330
|
+
if (command === "validate" && isRecord2(value)) {
|
|
2331
|
+
renderValidate(value);
|
|
2332
|
+
return true;
|
|
1599
2333
|
}
|
|
1600
|
-
if (
|
|
1601
|
-
|
|
2334
|
+
if (command === "run" && isRecord2(value)) {
|
|
2335
|
+
renderRun(value);
|
|
2336
|
+
return true;
|
|
1602
2337
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
const finishToken = () => {
|
|
1608
|
-
if (!tokenStarted) return;
|
|
1609
|
-
tokens.push(token);
|
|
1610
|
-
token = "";
|
|
1611
|
-
tokenStarted = false;
|
|
1612
|
-
};
|
|
1613
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
1614
|
-
const character = input[index];
|
|
1615
|
-
if (quote === "single") {
|
|
1616
|
-
if (character === "'") {
|
|
1617
|
-
quote = "none";
|
|
1618
|
-
} else {
|
|
1619
|
-
token += character;
|
|
1620
|
-
}
|
|
1621
|
-
continue;
|
|
1622
|
-
}
|
|
1623
|
-
if (quote === "double") {
|
|
1624
|
-
if (character === '"') {
|
|
1625
|
-
quote = "none";
|
|
1626
|
-
continue;
|
|
1627
|
-
}
|
|
1628
|
-
if (character === "\\") {
|
|
1629
|
-
const escaped = input[index + 1];
|
|
1630
|
-
if (escaped === void 0) {
|
|
1631
|
-
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1632
|
-
}
|
|
1633
|
-
token += escaped;
|
|
1634
|
-
tokenStarted = true;
|
|
1635
|
-
index += 1;
|
|
1636
|
-
continue;
|
|
1637
|
-
}
|
|
1638
|
-
if (character === "`") throw operatorError("`\u2026`");
|
|
1639
|
-
if (character === "$" && input[index + 1] === "(") {
|
|
1640
|
-
throw operatorError("$(\u2026)");
|
|
1641
|
-
}
|
|
1642
|
-
token += character;
|
|
1643
|
-
tokenStarted = true;
|
|
1644
|
-
continue;
|
|
1645
|
-
}
|
|
1646
|
-
if (/\s/.test(character)) {
|
|
1647
|
-
finishToken();
|
|
1648
|
-
continue;
|
|
1649
|
-
}
|
|
1650
|
-
if (character === "'") {
|
|
1651
|
-
quote = "single";
|
|
1652
|
-
tokenStarted = true;
|
|
1653
|
-
continue;
|
|
1654
|
-
}
|
|
1655
|
-
if (character === '"') {
|
|
1656
|
-
quote = "double";
|
|
1657
|
-
tokenStarted = true;
|
|
1658
|
-
continue;
|
|
1659
|
-
}
|
|
1660
|
-
if (character === "\\") {
|
|
1661
|
-
const escaped = input[index + 1];
|
|
1662
|
-
if (escaped === void 0) {
|
|
1663
|
-
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1664
|
-
}
|
|
1665
|
-
token += escaped;
|
|
1666
|
-
tokenStarted = true;
|
|
1667
|
-
index += 1;
|
|
1668
|
-
continue;
|
|
2338
|
+
if (command === "runs") {
|
|
2339
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2340
|
+
renderRunsList(value);
|
|
2341
|
+
return true;
|
|
1669
2342
|
}
|
|
1670
|
-
if (
|
|
1671
|
-
|
|
1672
|
-
|
|
2343
|
+
if (subcommand === "events" && Array.isArray(value)) {
|
|
2344
|
+
renderRunEvents(value);
|
|
2345
|
+
return true;
|
|
1673
2346
|
}
|
|
1674
|
-
if (
|
|
1675
|
-
|
|
1676
|
-
|
|
2347
|
+
if (subcommand === "get" && isRecord2(value)) {
|
|
2348
|
+
renderRunRecord(value);
|
|
2349
|
+
return true;
|
|
1677
2350
|
}
|
|
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`.");
|
|
2351
|
+
if (subcommand === "report" && isRecord2(value)) {
|
|
2352
|
+
renderRunReport(value);
|
|
2353
|
+
return true;
|
|
1696
2354
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
const override = args[0];
|
|
1702
|
-
if (override === "cloud" || override === "local") {
|
|
1703
|
-
if (args.length === 1) {
|
|
1704
|
-
throw new ShellParseError(`Add a ${override} command, or use \`/mode ${override}\` to switch workflows.`);
|
|
2355
|
+
if (subcommand === "compare" && isRecord2(value)) {
|
|
2356
|
+
renderRunComparison(value);
|
|
2357
|
+
return true;
|
|
1705
2358
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
);
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
for (let j = 1; j <= right.length; j += 1) {
|
|
1729
|
-
const above = previous[j];
|
|
1730
|
-
previous[j] = Math.min(
|
|
1731
|
-
previous[j] + 1,
|
|
1732
|
-
previous[j - 1] + 1,
|
|
1733
|
-
diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
1734
|
-
);
|
|
1735
|
-
diagonal = above;
|
|
2359
|
+
}
|
|
2360
|
+
if (command === "models") {
|
|
2361
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2362
|
+
renderModelsList(value);
|
|
2363
|
+
return true;
|
|
2364
|
+
}
|
|
2365
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord2(value)) {
|
|
2366
|
+
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
2367
|
+
renderModelRecord(value);
|
|
2368
|
+
return true;
|
|
2369
|
+
}
|
|
2370
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord2(value)) {
|
|
2371
|
+
renderModelPrefetch(value);
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord2(value)) {
|
|
2375
|
+
renderActiveModel(value);
|
|
2376
|
+
return true;
|
|
2377
|
+
}
|
|
2378
|
+
if (subcommand === "serve" && isRecord2(value)) {
|
|
2379
|
+
renderServePlan(value);
|
|
2380
|
+
return true;
|
|
1736
2381
|
}
|
|
1737
2382
|
}
|
|
1738
|
-
|
|
2383
|
+
if (command === "serve" && isRecord2(value)) {
|
|
2384
|
+
renderServePlan(value);
|
|
2385
|
+
return true;
|
|
2386
|
+
}
|
|
2387
|
+
return false;
|
|
1739
2388
|
}
|
|
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);
|
|
2389
|
+
function lastNonEmptyLine(value) {
|
|
2390
|
+
return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
|
|
1745
2391
|
}
|
|
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
|
-
);
|
|
2392
|
+
function adaptLocalCliText(value) {
|
|
2393
|
+
return value.replace(
|
|
2394
|
+
/(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
|
|
2395
|
+
"tt local"
|
|
2396
|
+
).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
|
|
1757
2397
|
}
|
|
1758
|
-
function
|
|
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
|
-
}
|
|
2398
|
+
function localCliErrorEnvelope(payload) {
|
|
2399
|
+
const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
|
|
1775
2400
|
return {
|
|
1776
|
-
|
|
1777
|
-
|
|
2401
|
+
error: {
|
|
2402
|
+
status: null,
|
|
2403
|
+
code: "LOCAL_CLI_ERROR",
|
|
2404
|
+
message: adaptLocalCliText(rawMessage),
|
|
2405
|
+
exit_code: payload.exitCode,
|
|
2406
|
+
signal: payload.signal
|
|
2407
|
+
}
|
|
1778
2408
|
};
|
|
1779
2409
|
}
|
|
1780
|
-
function
|
|
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.`);
|
|
2410
|
+
function localCliTextEnvelope(payload) {
|
|
2411
|
+
const output = adaptLocalCliText(payload.stdout).trim();
|
|
2412
|
+
if (payload.args[0] === "info") {
|
|
2413
|
+
const lines = output.split(/\r?\n/);
|
|
2414
|
+
const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
|
|
2415
|
+
const fields = Object.fromEntries(
|
|
2416
|
+
lines.slice(1).flatMap((line) => {
|
|
2417
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
2418
|
+
return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
|
|
2419
|
+
})
|
|
2420
|
+
);
|
|
2421
|
+
if (heading) {
|
|
2422
|
+
return {
|
|
2423
|
+
data: {
|
|
2424
|
+
name: heading[1],
|
|
2425
|
+
description: heading[2],
|
|
2426
|
+
...fields
|
|
2427
|
+
}
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
1802
2430
|
}
|
|
2431
|
+
return { data: { output } };
|
|
1803
2432
|
}
|
|
1804
|
-
function
|
|
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
|
-
}
|
|
2433
|
+
function renderLocalOutput(payload, options = {}) {
|
|
2434
|
+
if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
|
|
2435
|
+
return;
|
|
1824
2436
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
2437
|
+
const output = options.stdout ?? process.stdout;
|
|
2438
|
+
if (options.jsonMode) {
|
|
2439
|
+
if (payload.hasJson) {
|
|
2440
|
+
output.write(payload.stdout);
|
|
2441
|
+
return;
|
|
1830
2442
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2443
|
+
if (payload.exitCode !== 0 || payload.errorMessage) {
|
|
2444
|
+
output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
|
|
2445
|
+
`);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
|
|
2449
|
+
`);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (payload.droppedSpecKeys.length > 0) {
|
|
2453
|
+
printWarning(
|
|
2454
|
+
`Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
|
|
2458
|
+
if (payload.hasJson) {
|
|
2459
|
+
printJson(payload.json);
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
|
|
2463
|
+
if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
|
|
2464
|
+
printError(
|
|
2465
|
+
payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
|
|
2466
|
+
);
|
|
1835
2467
|
}
|
|
1836
|
-
return `${lines.join("\n")}
|
|
1837
|
-
`;
|
|
1838
|
-
}
|
|
1839
|
-
function activeModelLabel(snapshot) {
|
|
1840
|
-
return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
|
|
1841
|
-
}
|
|
1842
|
-
function logoRows() {
|
|
1843
|
-
const muted = chalk2.dim("\u2588\u2588");
|
|
1844
|
-
return [
|
|
1845
|
-
`${chalk2.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
|
|
1846
|
-
`${muted} ${accent("\u2588\u2588")} ${muted}`,
|
|
1847
|
-
`${muted} ${muted} ${chalk2.hex("#7C3AED")("\u2588\u2588")}`
|
|
1848
|
-
];
|
|
1849
2468
|
}
|
|
1850
|
-
function renderShellBanner(snapshot) {
|
|
1851
|
-
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1852
|
-
const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk2.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
|
|
1853
|
-
const textRows = [
|
|
1854
|
-
heading,
|
|
1855
|
-
`${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
|
|
1856
|
-
chalk2.dim("/help for commands \xB7 Tab completes \xB7 /mode cloud|local switches")
|
|
1857
|
-
];
|
|
1858
|
-
const logo = logoRows();
|
|
1859
|
-
const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
|
|
1860
|
-
return `${lines.join("\n")}
|
|
1861
2469
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2470
|
+
// src/local-runner.ts
|
|
2471
|
+
var localRequire = createRequire(import.meta.url);
|
|
2472
|
+
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
2473
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
2474
|
+
"--config",
|
|
2475
|
+
"--name",
|
|
2476
|
+
"--model",
|
|
2477
|
+
"--output",
|
|
2478
|
+
"--profile",
|
|
2479
|
+
"--host",
|
|
2480
|
+
"--port",
|
|
2481
|
+
"--device",
|
|
2482
|
+
"--max-tokens",
|
|
2483
|
+
"--temperature",
|
|
2484
|
+
"--top-p",
|
|
2485
|
+
"--max-concurrent-requests",
|
|
2486
|
+
"--spec",
|
|
2487
|
+
"--api-key-env"
|
|
2488
|
+
]);
|
|
2489
|
+
function isOptionWithInlineValue(value) {
|
|
2490
|
+
const equals = value.indexOf("=");
|
|
2491
|
+
return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
|
|
1866
2492
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2493
|
+
function positionalIndices(args, start) {
|
|
2494
|
+
const indices = [];
|
|
2495
|
+
let optionsEnded = false;
|
|
2496
|
+
for (let index = start; index < args.length; index += 1) {
|
|
2497
|
+
const value = args[index];
|
|
2498
|
+
if (!optionsEnded && value === "--") {
|
|
2499
|
+
optionsEnded = true;
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
if (!optionsEnded && value.startsWith("-")) {
|
|
2503
|
+
if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2506
|
+
indices.push(index);
|
|
1878
2507
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2508
|
+
return indices;
|
|
2509
|
+
}
|
|
2510
|
+
function optionValueArgument(args, name) {
|
|
2511
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2512
|
+
const value = args[index];
|
|
2513
|
+
if (value === name) {
|
|
2514
|
+
const path = args[index + 1];
|
|
2515
|
+
if (!path) return null;
|
|
2516
|
+
return {
|
|
2517
|
+
path,
|
|
2518
|
+
replace(projectedPath) {
|
|
2519
|
+
args[index + 1] = projectedPath;
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
if (value.startsWith(`${name}=`)) {
|
|
2524
|
+
const path = value.slice(name.length + 1);
|
|
2525
|
+
if (!path) return null;
|
|
2526
|
+
return {
|
|
2527
|
+
path,
|
|
2528
|
+
replace(projectedPath) {
|
|
2529
|
+
args[index] = `${name}=${projectedPath}`;
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
1896
2533
|
}
|
|
1897
|
-
|
|
2534
|
+
return null;
|
|
2535
|
+
}
|
|
2536
|
+
function positionalSpecArgument(args, start, defaultPath) {
|
|
2537
|
+
const positional = positionalIndices(args, start)[0];
|
|
2538
|
+
if (positional !== void 0) {
|
|
1898
2539
|
return {
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
version: this.version
|
|
2540
|
+
path: args[positional],
|
|
2541
|
+
replace(projectedPath) {
|
|
2542
|
+
args[positional] = projectedPath;
|
|
2543
|
+
}
|
|
1904
2544
|
};
|
|
1905
2545
|
}
|
|
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;
|
|
2546
|
+
return {
|
|
2547
|
+
path: defaultPath,
|
|
2548
|
+
replace(projectedPath) {
|
|
2549
|
+
args.splice(start, 0, projectedPath);
|
|
1918
2550
|
}
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
function selectedSpecArgument(args, cwd) {
|
|
2554
|
+
const command = args[0];
|
|
2555
|
+
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
2556
|
+
return positionalSpecArgument(args, 1, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1919
2557
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
`);
|
|
2558
|
+
if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
|
|
2559
|
+
return positionalSpecArgument(args, 2, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1923
2560
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2561
|
+
const serves = command === "serve" || command === "models" && args[1] === "serve";
|
|
2562
|
+
if (!serves) return null;
|
|
2563
|
+
const explicit = optionValueArgument(args, "--spec");
|
|
2564
|
+
if (explicit) return explicit;
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
async function pathIsFile(path) {
|
|
2568
|
+
return (await stat3(path).catch(() => null))?.isFile() ?? false;
|
|
2569
|
+
}
|
|
2570
|
+
function resolveFromCwd(path, cwd) {
|
|
2571
|
+
if (isAbsolute3(path)) return path;
|
|
2572
|
+
return resolve3(cwd, path);
|
|
2573
|
+
}
|
|
2574
|
+
async function projectSpecArguments(originalArgs, cwd) {
|
|
2575
|
+
const args = [...originalArgs];
|
|
2576
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
2577
|
+
return {
|
|
2578
|
+
args,
|
|
2579
|
+
droppedSpecKeys: [],
|
|
2580
|
+
pathReplacements: [],
|
|
2581
|
+
cleanup: async () => {
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
1935
2584
|
}
|
|
1936
|
-
|
|
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";
|
|
2585
|
+
const selected = selectedSpecArgument(args, cwd);
|
|
2586
|
+
if (!selected) {
|
|
2587
|
+
return {
|
|
2588
|
+
args,
|
|
2589
|
+
droppedSpecKeys: [],
|
|
2590
|
+
pathReplacements: [],
|
|
2591
|
+
cleanup: async () => {
|
|
1970
2592
|
}
|
|
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";
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
const sourcePath = resolveFromCwd(selected.path, cwd);
|
|
2596
|
+
if (!await pathIsFile(sourcePath)) {
|
|
2597
|
+
return {
|
|
2598
|
+
args,
|
|
2599
|
+
droppedSpecKeys: [],
|
|
2600
|
+
pathReplacements: [],
|
|
2601
|
+
cleanup: async () => {
|
|
1992
2602
|
}
|
|
1993
|
-
|
|
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";
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
const raw = JSON.parse(await readFile2(sourcePath, "utf8"));
|
|
2606
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2607
|
+
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
2608
|
+
}
|
|
2609
|
+
const body = raw;
|
|
2610
|
+
const runRequestKeys = [
|
|
2611
|
+
"run_id",
|
|
2612
|
+
"behavior_spec_id",
|
|
2613
|
+
"run_number",
|
|
2614
|
+
"spec_snapshot"
|
|
2615
|
+
].filter((key) => key in body);
|
|
2616
|
+
if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
|
|
2617
|
+
return {
|
|
2618
|
+
args,
|
|
2619
|
+
droppedSpecKeys: [],
|
|
2620
|
+
pathReplacements: [],
|
|
2621
|
+
cleanup: async () => {
|
|
2016
2622
|
}
|
|
2017
|
-
|
|
2018
|
-
assertNoArgs("exit", command.args);
|
|
2019
|
-
return "exit";
|
|
2020
|
-
}
|
|
2623
|
+
};
|
|
2021
2624
|
}
|
|
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
|
-
}
|
|
2625
|
+
const projected = projectLocalSpec(body);
|
|
2626
|
+
if (projected.droppedKeys.length === 0) {
|
|
2627
|
+
return {
|
|
2628
|
+
args,
|
|
2629
|
+
droppedSpecKeys: [],
|
|
2630
|
+
pathReplacements: [],
|
|
2631
|
+
cleanup: async () => {
|
|
2632
|
+
}
|
|
2633
|
+
};
|
|
2041
2634
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2635
|
+
const temporaryPath = join3(
|
|
2636
|
+
dirname2(sourcePath),
|
|
2637
|
+
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
|
|
2638
|
+
);
|
|
2639
|
+
await writeFile(
|
|
2640
|
+
temporaryPath,
|
|
2641
|
+
`${JSON.stringify(projected.body, null, 2)}
|
|
2642
|
+
`,
|
|
2643
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
2644
|
+
);
|
|
2645
|
+
selected.replace(temporaryPath);
|
|
2646
|
+
return {
|
|
2647
|
+
args,
|
|
2648
|
+
droppedSpecKeys: projected.droppedKeys,
|
|
2649
|
+
pathReplacements: [{
|
|
2650
|
+
sourcePath,
|
|
2651
|
+
projectedPath: temporaryPath
|
|
2652
|
+
}],
|
|
2653
|
+
cleanup: () => rm(temporaryPath, { force: true })
|
|
2654
|
+
};
|
|
2045
2655
|
}
|
|
2046
|
-
function
|
|
2047
|
-
|
|
2656
|
+
function resolveLocalCliEntrypoint() {
|
|
2657
|
+
try {
|
|
2658
|
+
return localRequire.resolve("@tuned-tensor/local");
|
|
2659
|
+
} catch (error) {
|
|
2660
|
+
throw new Error(
|
|
2661
|
+
"TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
|
|
2662
|
+
{ cause: error }
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2048
2665
|
}
|
|
2049
|
-
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (restoreRawMode) control.input.setRawMode(false);
|
|
2056
|
-
signals.on("SIGINT", keepParentAlive);
|
|
2666
|
+
function localCommandStreamsStdout(args) {
|
|
2667
|
+
const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
|
|
2668
|
+
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
2669
|
+
}
|
|
2670
|
+
function parseJsonOutput(stdout2) {
|
|
2671
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
2057
2672
|
try {
|
|
2058
|
-
return
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
if (restoreRawMode) control.input.setRawMode(true);
|
|
2062
|
-
control.resumeReadline();
|
|
2673
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
2674
|
+
} catch {
|
|
2675
|
+
return { hasJson: false, json: void 0 };
|
|
2063
2676
|
}
|
|
2064
2677
|
}
|
|
2065
|
-
function
|
|
2066
|
-
return
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
},
|
|
2070
|
-
writeError(text2) {
|
|
2071
|
-
error.write(text2);
|
|
2072
|
-
},
|
|
2073
|
-
clear() {
|
|
2074
|
-
output.write("\x1B[2J\x1B[H");
|
|
2075
|
-
}
|
|
2076
|
-
};
|
|
2678
|
+
function signalExitCode(signal) {
|
|
2679
|
+
if (!signal) return 1;
|
|
2680
|
+
const signalNumber = osConstants.signals[signal];
|
|
2681
|
+
return typeof signalNumber === "number" ? 128 + signalNumber : 1;
|
|
2077
2682
|
}
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2683
|
+
function writeStream(stream, chunk) {
|
|
2684
|
+
stream.write(chunk);
|
|
2685
|
+
}
|
|
2686
|
+
function normalizeProjectedPaths(value, replacements) {
|
|
2687
|
+
let normalized = value;
|
|
2688
|
+
for (const replacement of replacements) {
|
|
2689
|
+
const encodedProjectedPath = JSON.stringify(
|
|
2690
|
+
replacement.projectedPath
|
|
2691
|
+
).slice(1, -1);
|
|
2692
|
+
const encodedSourcePath = JSON.stringify(
|
|
2693
|
+
replacement.sourcePath
|
|
2694
|
+
).slice(1, -1);
|
|
2695
|
+
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
2084
2696
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2697
|
+
return normalized;
|
|
2698
|
+
}
|
|
2699
|
+
async function spawnLocalCli(args) {
|
|
2700
|
+
const output = args.stdout ?? process.stdout;
|
|
2701
|
+
const errors = args.stderr ?? process.stderr;
|
|
2702
|
+
const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
|
|
2703
|
+
const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
|
|
2704
|
+
const child = spawn(
|
|
2705
|
+
args.nodeExecutable,
|
|
2706
|
+
[args.entrypoint, ...args.commandArgs],
|
|
2088
2707
|
{
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2708
|
+
cwd: args.cwd,
|
|
2709
|
+
env: args.env,
|
|
2710
|
+
stdio: [
|
|
2711
|
+
pipeInput ? "pipe" : "inherit",
|
|
2712
|
+
args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
2713
|
+
"pipe"
|
|
2714
|
+
],
|
|
2715
|
+
detached: process.platform !== "win32"
|
|
2716
|
+
}
|
|
2094
2717
|
);
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2718
|
+
if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
|
|
2719
|
+
let stdout2 = "";
|
|
2720
|
+
let stderr = "";
|
|
2721
|
+
child.stdout?.setEncoding("utf8");
|
|
2722
|
+
child.stderr?.setEncoding("utf8");
|
|
2723
|
+
child.stdout?.on("data", (chunk) => {
|
|
2724
|
+
const normalized = normalizeProjectedPaths(
|
|
2725
|
+
chunk,
|
|
2726
|
+
args.pathReplacements
|
|
2727
|
+
);
|
|
2728
|
+
if (args.streamingStdout) writeStream(output, normalized);
|
|
2729
|
+
else stdout2 += normalized;
|
|
2101
2730
|
});
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2731
|
+
child.stderr?.on("data", (chunk) => {
|
|
2732
|
+
const normalized = normalizeProjectedPaths(
|
|
2733
|
+
chunk,
|
|
2734
|
+
args.pathReplacements
|
|
2735
|
+
);
|
|
2736
|
+
stderr += normalized;
|
|
2737
|
+
writeStream(
|
|
2738
|
+
errors,
|
|
2739
|
+
args.jsonMode ? normalized : adaptLocalCliText(normalized)
|
|
2740
|
+
);
|
|
2110
2741
|
});
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2742
|
+
return new Promise((resolveChild) => {
|
|
2743
|
+
let spawnError;
|
|
2744
|
+
const forwardSignal = (signal) => {
|
|
2745
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
2746
|
+
if (child.pid && process.platform !== "win32") {
|
|
2747
|
+
try {
|
|
2748
|
+
process.kill(-child.pid, childSignal);
|
|
2749
|
+
return;
|
|
2750
|
+
} catch {
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
child.kill(childSignal);
|
|
2754
|
+
};
|
|
2755
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
2756
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
2757
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
2758
|
+
const cleanup = () => {
|
|
2759
|
+
process.off("SIGINT", onSigint);
|
|
2760
|
+
process.off("SIGTERM", onSigterm);
|
|
2761
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
2762
|
+
};
|
|
2763
|
+
process.on("SIGINT", onSigint);
|
|
2764
|
+
process.on("SIGTERM", onSigterm);
|
|
2765
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
2766
|
+
child.once("error", (error) => {
|
|
2767
|
+
spawnError = error;
|
|
2768
|
+
});
|
|
2769
|
+
child.once("close", (code, signal) => {
|
|
2770
|
+
cleanup();
|
|
2771
|
+
resolveChild({
|
|
2772
|
+
exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
|
|
2773
|
+
signal,
|
|
2774
|
+
stdout: stdout2,
|
|
2775
|
+
stderr,
|
|
2776
|
+
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
2777
|
+
});
|
|
2778
|
+
});
|
|
2115
2779
|
});
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2780
|
+
}
|
|
2781
|
+
function setupFailure(originalArgs, error) {
|
|
2782
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2783
|
+
return {
|
|
2784
|
+
args: [...originalArgs],
|
|
2785
|
+
projectedArgs: [...originalArgs],
|
|
2786
|
+
entrypoint: null,
|
|
2787
|
+
exitCode: 1,
|
|
2788
|
+
signal: null,
|
|
2789
|
+
stdout: "",
|
|
2790
|
+
stderr: "",
|
|
2791
|
+
hasJson: false,
|
|
2792
|
+
json: void 0,
|
|
2793
|
+
streamingStdout: false,
|
|
2794
|
+
droppedSpecKeys: [],
|
|
2795
|
+
errorMessage: message
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
async function runLocalCommand(args, options = {}) {
|
|
2799
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
2800
|
+
let projected;
|
|
2119
2801
|
try {
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2802
|
+
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
2803
|
+
projected = await projectSpecArguments(args, cwd);
|
|
2804
|
+
const streamingStdout = localCommandStreamsStdout(projected.args);
|
|
2805
|
+
const result = await spawnLocalCli({
|
|
2806
|
+
entrypoint,
|
|
2807
|
+
commandArgs: projected.args,
|
|
2808
|
+
cwd,
|
|
2809
|
+
env: options.env ?? process.env,
|
|
2810
|
+
nodeExecutable: options.nodeExecutable ?? process.execPath,
|
|
2811
|
+
streamingStdout,
|
|
2812
|
+
jsonMode: options.jsonMode ?? false,
|
|
2813
|
+
stdin: options.stdin,
|
|
2814
|
+
stdout: options.stdout,
|
|
2815
|
+
stderr: options.stderr,
|
|
2816
|
+
pathReplacements: projected.pathReplacements
|
|
2817
|
+
});
|
|
2818
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
2819
|
+
return {
|
|
2820
|
+
args: [...args],
|
|
2821
|
+
projectedArgs: projected.args,
|
|
2822
|
+
entrypoint,
|
|
2823
|
+
...result,
|
|
2824
|
+
...parsed,
|
|
2825
|
+
streamingStdout,
|
|
2826
|
+
droppedSpecKeys: projected.droppedSpecKeys
|
|
2827
|
+
};
|
|
2828
|
+
} catch (error) {
|
|
2829
|
+
return setupFailure(args, error);
|
|
2129
2830
|
} finally {
|
|
2130
|
-
|
|
2831
|
+
await projected?.cleanup().catch(() => void 0);
|
|
2131
2832
|
}
|
|
2132
2833
|
}
|
|
2133
|
-
function
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2834
|
+
async function executeLocalCommand(args, options = {}) {
|
|
2835
|
+
const result = await runLocalCommand(args, options);
|
|
2836
|
+
renderLocalOutput(result, {
|
|
2837
|
+
jsonMode: options.jsonMode,
|
|
2838
|
+
stdout: options.stdout
|
|
2839
|
+
});
|
|
2840
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
2841
|
+
return result;
|
|
2140
2842
|
}
|
|
2141
2843
|
|
|
2142
2844
|
// src/commands/auth.ts
|
|
2143
2845
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2144
2846
|
import { stdin, stdout } from "process";
|
|
2145
2847
|
import { Writable } from "stream";
|
|
2146
|
-
import
|
|
2848
|
+
import chalk4 from "chalk";
|
|
2147
2849
|
async function promptForApiKey(input = stdin, output = stdout) {
|
|
2148
2850
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2149
2851
|
throw new Error(
|
|
@@ -2185,9 +2887,9 @@ function registerAuthCommands(parent) {
|
|
|
2185
2887
|
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
2186
2888
|
console.log();
|
|
2187
2889
|
console.log(
|
|
2188
|
-
` To get your API key, go to ${
|
|
2890
|
+
` To get your API key, go to ${chalk4.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
2189
2891
|
);
|
|
2190
|
-
console.log(` ${
|
|
2892
|
+
console.log(` ${chalk4.cyan.underline(settingsUrl)}`);
|
|
2191
2893
|
console.log();
|
|
2192
2894
|
apiKey = await promptForApiKey();
|
|
2193
2895
|
}
|
|
@@ -3074,18 +3776,18 @@ function findControlChar(value) {
|
|
|
3074
3776
|
function formatCodepoint(code) {
|
|
3075
3777
|
return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
3076
3778
|
}
|
|
3077
|
-
function detectRowFormat(
|
|
3078
|
-
const input =
|
|
3079
|
-
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof
|
|
3779
|
+
function detectRowFormat(record3) {
|
|
3780
|
+
const input = record3.input;
|
|
3781
|
+
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record3.output === "string") {
|
|
3080
3782
|
return "document_ocr_jsonl";
|
|
3081
3783
|
}
|
|
3082
|
-
if (typeof
|
|
3784
|
+
if (typeof record3.input === "string" || typeof record3.output === "string" || !("input" in record3) || !("output" in record3)) {
|
|
3083
3785
|
return "jsonl";
|
|
3084
3786
|
}
|
|
3085
3787
|
return null;
|
|
3086
3788
|
}
|
|
3087
|
-
function validateDocumentOcrRow(
|
|
3088
|
-
const input =
|
|
3789
|
+
function validateDocumentOcrRow(record3, rowNumber, errors) {
|
|
3790
|
+
const input = record3.input;
|
|
3089
3791
|
const assets = input.assets;
|
|
3090
3792
|
if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
|
|
3091
3793
|
errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
|
|
@@ -3097,10 +3799,10 @@ function validateDocumentOcrRow(record2, rowNumber, errors) {
|
|
|
3097
3799
|
);
|
|
3098
3800
|
}
|
|
3099
3801
|
}
|
|
3100
|
-
if (typeof
|
|
3802
|
+
if (typeof record3.output !== "string" || record3.output.trim().length === 0) {
|
|
3101
3803
|
errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
|
|
3102
3804
|
} else {
|
|
3103
|
-
const hit = findControlChar(
|
|
3805
|
+
const hit = findControlChar(record3.output);
|
|
3104
3806
|
if (hit) {
|
|
3105
3807
|
errors.push(
|
|
3106
3808
|
`Row ${rowNumber}: OCR output contains invisible control character ${formatCodepoint(hit.codepoint)} (${hit.name}) at offset ${hit.offset} \u2014 strip or escape before uploading; some training paths split on it`
|
|
@@ -3169,14 +3871,14 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3169
3871
|
errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
|
|
3170
3872
|
continue;
|
|
3171
3873
|
}
|
|
3172
|
-
const
|
|
3173
|
-
if ("messages" in
|
|
3874
|
+
const record3 = row;
|
|
3875
|
+
if ("messages" in record3 && !("input" in record3) && !("output" in record3)) {
|
|
3174
3876
|
errors.push(
|
|
3175
3877
|
`Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
|
|
3176
3878
|
);
|
|
3177
3879
|
continue;
|
|
3178
3880
|
}
|
|
3179
|
-
const rowFormat = detectRowFormat(
|
|
3881
|
+
const rowFormat = detectRowFormat(record3);
|
|
3180
3882
|
if (!rowFormat) {
|
|
3181
3883
|
errors.push(
|
|
3182
3884
|
`Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
|
|
@@ -3193,17 +3895,17 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3193
3895
|
continue;
|
|
3194
3896
|
}
|
|
3195
3897
|
if (rowFormat === "document_ocr_jsonl") {
|
|
3196
|
-
validateDocumentOcrRow(
|
|
3898
|
+
validateDocumentOcrRow(record3, rowNumber, errors);
|
|
3197
3899
|
continue;
|
|
3198
3900
|
}
|
|
3199
|
-
if (typeof
|
|
3901
|
+
if (typeof record3.input !== "string") {
|
|
3200
3902
|
errors.push(`Row ${rowNumber}: missing string "input" field`);
|
|
3201
3903
|
}
|
|
3202
|
-
if (typeof
|
|
3904
|
+
if (typeof record3.output !== "string") {
|
|
3203
3905
|
errors.push(`Row ${rowNumber}: missing string "output" field`);
|
|
3204
3906
|
}
|
|
3205
3907
|
for (const field of ["input", "output"]) {
|
|
3206
|
-
const value =
|
|
3908
|
+
const value = record3[field];
|
|
3207
3909
|
if (typeof value !== "string") continue;
|
|
3208
3910
|
const hit = findControlChar(value);
|
|
3209
3911
|
if (hit) {
|
|
@@ -4206,12 +4908,12 @@ var ManagedModelServer = class {
|
|
|
4206
4908
|
clearTimeout(this.idleTimer);
|
|
4207
4909
|
this.idleTimer = null;
|
|
4208
4910
|
}
|
|
4209
|
-
writeLog(
|
|
4911
|
+
writeLog(record3) {
|
|
4210
4912
|
if (this.logRecordOverride) {
|
|
4211
|
-
this.logRecordOverride(
|
|
4913
|
+
this.logRecordOverride(record3);
|
|
4212
4914
|
return;
|
|
4213
4915
|
}
|
|
4214
|
-
const line = `${JSON.stringify(
|
|
4916
|
+
const line = `${JSON.stringify(record3)}
|
|
4215
4917
|
`;
|
|
4216
4918
|
if (this.options.logFile) {
|
|
4217
4919
|
mkdirSync2(dirname3(this.options.logFile), { recursive: true });
|
|
@@ -5651,7 +6353,7 @@ function registerModelsCommands(parent) {
|
|
|
5651
6353
|
}
|
|
5652
6354
|
|
|
5653
6355
|
// src/commands/balance.ts
|
|
5654
|
-
import
|
|
6356
|
+
import chalk5 from "chalk";
|
|
5655
6357
|
var KIND_LABELS = {
|
|
5656
6358
|
signup_bonus: "Signup bonus",
|
|
5657
6359
|
topup: "Top-up",
|
|
@@ -5666,8 +6368,8 @@ function formatCents3(cents) {
|
|
|
5666
6368
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
5667
6369
|
}
|
|
5668
6370
|
function formatSigned(cents) {
|
|
5669
|
-
if (cents > 0) return
|
|
5670
|
-
if (cents < 0) return
|
|
6371
|
+
if (cents > 0) return chalk5.green(`+${formatCents3(cents)}`);
|
|
6372
|
+
if (cents < 0) return chalk5.red(formatCents3(cents));
|
|
5671
6373
|
return formatCents3(cents);
|
|
5672
6374
|
}
|
|
5673
6375
|
function registerBalanceCommands(parent) {
|
|
@@ -5682,7 +6384,7 @@ function registerBalanceCommands(parent) {
|
|
|
5682
6384
|
return printJson({ balance, transactions });
|
|
5683
6385
|
}
|
|
5684
6386
|
const lowBalance = balance.balance_cents < 100;
|
|
5685
|
-
const balanceLine = lowBalance ?
|
|
6387
|
+
const balanceLine = lowBalance ? chalk5.red(formatCents3(balance.balance_cents)) + chalk5.dim(" (low)") : chalk5.bold.green(formatCents3(balance.balance_cents));
|
|
5686
6388
|
printDetail([
|
|
5687
6389
|
["Credits", balanceLine],
|
|
5688
6390
|
[
|
|
@@ -5697,7 +6399,7 @@ function registerBalanceCommands(parent) {
|
|
|
5697
6399
|
]);
|
|
5698
6400
|
if (transactions && transactions.length > 0) {
|
|
5699
6401
|
console.log(`
|
|
5700
|
-
${
|
|
6402
|
+
${chalk5.bold("Recent transactions")}`);
|
|
5701
6403
|
printTable(
|
|
5702
6404
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
5703
6405
|
transactions.map((row) => [
|
|
@@ -5705,19 +6407,19 @@ ${chalk4.bold("Recent transactions")}`);
|
|
|
5705
6407
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
5706
6408
|
formatSigned(row.amount_cents),
|
|
5707
6409
|
formatCents3(row.balance_after_cents),
|
|
5708
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
6410
|
+
row.reference_id ? shortId(row.reference_id) : chalk5.dim("\u2014")
|
|
5709
6411
|
])
|
|
5710
6412
|
);
|
|
5711
6413
|
} else {
|
|
5712
6414
|
console.log(
|
|
5713
6415
|
`
|
|
5714
|
-
${
|
|
6416
|
+
${chalk5.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
5715
6417
|
);
|
|
5716
6418
|
}
|
|
5717
6419
|
if (lowBalance) {
|
|
5718
6420
|
console.log(
|
|
5719
6421
|
`
|
|
5720
|
-
${formatStatus("preparing")} ${
|
|
6422
|
+
${formatStatus("preparing")} ${chalk5.yellow(
|
|
5721
6423
|
"Run `tt topup` to add more credits before starting a run."
|
|
5722
6424
|
)}`
|
|
5723
6425
|
);
|
|
@@ -5726,7 +6428,7 @@ ${formatStatus("preparing")} ${chalk4.yellow(
|
|
|
5726
6428
|
}
|
|
5727
6429
|
|
|
5728
6430
|
// src/commands/topup.ts
|
|
5729
|
-
import
|
|
6431
|
+
import chalk6 from "chalk";
|
|
5730
6432
|
import open from "open";
|
|
5731
6433
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
5732
6434
|
var MIN_USD = 5;
|
|
@@ -5765,9 +6467,9 @@ function registerTopupCommands(parent) {
|
|
|
5765
6467
|
"amount is required in --json mode (use --amount <usd>)"
|
|
5766
6468
|
);
|
|
5767
6469
|
}
|
|
5768
|
-
console.log(
|
|
6470
|
+
console.log(chalk6.bold("Add credits via Stripe Checkout"));
|
|
5769
6471
|
console.log(
|
|
5770
|
-
|
|
6472
|
+
chalk6.dim(
|
|
5771
6473
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
5772
6474
|
)
|
|
5773
6475
|
);
|
|
@@ -5794,8 +6496,8 @@ function registerTopupCommands(parent) {
|
|
|
5794
6496
|
}
|
|
5795
6497
|
const url = data.checkout_url;
|
|
5796
6498
|
console.log(
|
|
5797
|
-
`${
|
|
5798
|
-
` +
|
|
6499
|
+
`${chalk6.bold("Checkout URL:")} ${chalk6.cyan(url)}
|
|
6500
|
+
` + chalk6.dim("Complete the payment to credit your account.")
|
|
5799
6501
|
);
|
|
5800
6502
|
if (options.open !== false) {
|
|
5801
6503
|
try {
|
|
@@ -5803,7 +6505,7 @@ function registerTopupCommands(parent) {
|
|
|
5803
6505
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
5804
6506
|
} catch {
|
|
5805
6507
|
console.log(
|
|
5806
|
-
|
|
6508
|
+
chalk6.yellow(
|
|
5807
6509
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
5808
6510
|
)
|
|
5809
6511
|
);
|
|
@@ -5813,7 +6515,7 @@ function registerTopupCommands(parent) {
|
|
|
5813
6515
|
}
|
|
5814
6516
|
|
|
5815
6517
|
// src/commands/eval.ts
|
|
5816
|
-
import
|
|
6518
|
+
import chalk7 from "chalk";
|
|
5817
6519
|
|
|
5818
6520
|
// src/eval/rules.ts
|
|
5819
6521
|
function validateSpec(spec) {
|
|
@@ -5978,7 +6680,7 @@ function registerEvalCommand(parent) {
|
|
|
5978
6680
|
const spec = loadSpec(cmdOpts.file);
|
|
5979
6681
|
const validation = validateSpec(spec);
|
|
5980
6682
|
if (!isJsonMode()) {
|
|
5981
|
-
console.log(
|
|
6683
|
+
console.log(chalk7.dim(`
|
|
5982
6684
|
Spec: ${cmdOpts.file}
|
|
5983
6685
|
`));
|
|
5984
6686
|
}
|
|
@@ -5993,10 +6695,10 @@ Spec: ${cmdOpts.file}
|
|
|
5993
6695
|
});
|
|
5994
6696
|
}
|
|
5995
6697
|
function printValidation(checks) {
|
|
5996
|
-
console.log(
|
|
6698
|
+
console.log(chalk7.bold("Spec Validation"));
|
|
5997
6699
|
for (const check of checks) {
|
|
5998
|
-
const icon = check.passed ?
|
|
5999
|
-
const msg = check.message ?
|
|
6700
|
+
const icon = check.passed ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
6701
|
+
const msg = check.message ? chalk7.dim(` \u2014 ${check.message}`) : "";
|
|
6000
6702
|
console.log(` ${icon} ${check.name}${msg}`);
|
|
6001
6703
|
}
|
|
6002
6704
|
}
|
|
@@ -6152,17 +6854,28 @@ function createProgram(version, runtime = {}) {
|
|
|
6152
6854
|
const openShell = async () => {
|
|
6153
6855
|
const root = program.opts();
|
|
6154
6856
|
const shellEnvironment = childEnvironment(root, env);
|
|
6857
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6858
|
+
const error = runtime.stderr ?? process.stderr;
|
|
6859
|
+
const agent = createShellAgent({
|
|
6860
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
6861
|
+
apiKey: root.apiKey ?? shellEnvironment.TUNED_TENSOR_API_KEY,
|
|
6862
|
+
baseUrl: root.baseUrl ?? shellEnvironment.TUNED_TENSOR_URL
|
|
6863
|
+
}),
|
|
6864
|
+
output,
|
|
6865
|
+
error
|
|
6866
|
+
});
|
|
6155
6867
|
await launchShell({
|
|
6156
6868
|
runner: shellRunner,
|
|
6869
|
+
agent,
|
|
6157
6870
|
input: runtime.stdin ?? process.stdin,
|
|
6158
|
-
output
|
|
6159
|
-
error
|
|
6871
|
+
output,
|
|
6872
|
+
error,
|
|
6160
6873
|
cwd,
|
|
6161
6874
|
env: shellEnvironment,
|
|
6162
6875
|
version
|
|
6163
6876
|
});
|
|
6164
6877
|
};
|
|
6165
|
-
program.name("tt").description("Tuned Tensor \u2014
|
|
6878
|
+
program.name("tt").description("Tuned Tensor \u2014 converse, train, and inspect from one terminal").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
|
|
6166
6879
|
"-u, --base-url <url>",
|
|
6167
6880
|
"API base URL (default: https://tunedtensor.com)"
|
|
6168
6881
|
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
|
|
@@ -6170,7 +6883,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6170
6883
|
if (root.json) setJsonMode(true);
|
|
6171
6884
|
if (root.color === false) {
|
|
6172
6885
|
process.env.FORCE_COLOR = "0";
|
|
6173
|
-
|
|
6886
|
+
chalk8.level = 0;
|
|
6174
6887
|
}
|
|
6175
6888
|
});
|
|
6176
6889
|
registerAuthCommands(program);
|
|
@@ -6203,12 +6916,12 @@ function createProgram(version, runtime = {}) {
|
|
|
6203
6916
|
`);
|
|
6204
6917
|
return;
|
|
6205
6918
|
}
|
|
6206
|
-
output.write(`${
|
|
6919
|
+
output.write(`${chalk8.bold.cyan("Tuned Tensor status")}
|
|
6207
6920
|
`);
|
|
6208
6921
|
output.write(`${formatShellStatus(context, target).join("\n")}
|
|
6209
6922
|
|
|
6210
6923
|
`);
|
|
6211
|
-
output.write(`${
|
|
6924
|
+
output.write(`${chalk8.bold("Context")}
|
|
6212
6925
|
`);
|
|
6213
6926
|
output.write(
|
|
6214
6927
|
`${formatShellContext(
|
|
@@ -6222,7 +6935,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6222
6935
|
program.command("local").description("Run the local CUDA training and model workflow").helpOption(false).allowUnknownOption().argument("[args...]", "TT Local command and arguments").action(async (args) => {
|
|
6223
6936
|
const root = program.opts();
|
|
6224
6937
|
const passthrough = extractPassthroughOptions(args, root);
|
|
6225
|
-
if (!passthrough.color)
|
|
6938
|
+
if (!passthrough.color) chalk8.level = 0;
|
|
6226
6939
|
const result = await invokeLocal(
|
|
6227
6940
|
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6228
6941
|
{
|
|
@@ -6262,7 +6975,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6262
6975
|
"after",
|
|
6263
6976
|
`
|
|
6264
6977
|
Workflows:
|
|
6265
|
-
tt Open the
|
|
6978
|
+
tt Open the conversational terminal (TTY only)
|
|
6266
6979
|
tt status Inspect cloud/local project context
|
|
6267
6980
|
tt cloud <command> Run a hosted command explicitly
|
|
6268
6981
|
tt local <command> Run a local GPU command
|
|
@@ -6275,6 +6988,22 @@ Examples:
|
|
|
6275
6988
|
);
|
|
6276
6989
|
return program;
|
|
6277
6990
|
}
|
|
6991
|
+
function createShellAgent(options) {
|
|
6992
|
+
return new TunedTensorAgentSession({
|
|
6993
|
+
client: options.client,
|
|
6994
|
+
io: {
|
|
6995
|
+
write(text2) {
|
|
6996
|
+
options.output.write(text2);
|
|
6997
|
+
},
|
|
6998
|
+
writeError(text2) {
|
|
6999
|
+
options.error.write(text2);
|
|
7000
|
+
},
|
|
7001
|
+
clear() {
|
|
7002
|
+
options.output.write("\x1B[2J\x1B[H");
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
});
|
|
7006
|
+
}
|
|
6278
7007
|
async function runCli(version, runtime = {}) {
|
|
6279
7008
|
const argv = runtime.argv ?? process.argv;
|
|
6280
7009
|
const env = runtime.env ?? process.env;
|
|
@@ -6286,6 +7015,16 @@ async function runCli(version, runtime = {}) {
|
|
|
6286
7015
|
env
|
|
6287
7016
|
})) {
|
|
6288
7017
|
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
7018
|
+
const output = runtime.stdout ?? process.stdout;
|
|
7019
|
+
const error = runtime.stderr ?? process.stderr;
|
|
7020
|
+
const agent = createShellAgent({
|
|
7021
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
7022
|
+
apiKey: env.TUNED_TENSOR_API_KEY,
|
|
7023
|
+
baseUrl: env.TUNED_TENSOR_URL
|
|
7024
|
+
}),
|
|
7025
|
+
output,
|
|
7026
|
+
error
|
|
7027
|
+
});
|
|
6289
7028
|
await (runtime.startShell ?? startInteractiveShell)({
|
|
6290
7029
|
runner: async (request2) => await invokeSelf(
|
|
6291
7030
|
request2.target === "local" ? ["local", ...request2.args] : request2.args,
|
|
@@ -6295,9 +7034,10 @@ async function runCli(version, runtime = {}) {
|
|
|
6295
7034
|
entrypoint: argv[1]
|
|
6296
7035
|
}
|
|
6297
7036
|
),
|
|
7037
|
+
agent,
|
|
6298
7038
|
input: runtime.stdin ?? process.stdin,
|
|
6299
|
-
output
|
|
6300
|
-
error
|
|
7039
|
+
output,
|
|
7040
|
+
error,
|
|
6301
7041
|
cwd: runtime.cwd ?? process.cwd(),
|
|
6302
7042
|
env,
|
|
6303
7043
|
version
|
|
@@ -6309,7 +7049,7 @@ async function runCli(version, runtime = {}) {
|
|
|
6309
7049
|
}
|
|
6310
7050
|
|
|
6311
7051
|
// src/index.ts
|
|
6312
|
-
runCli("0.
|
|
7052
|
+
runCli("0.8.0").catch((err) => {
|
|
6313
7053
|
reportError(err);
|
|
6314
7054
|
process.exit(1);
|
|
6315
7055
|
});
|