@tuned-tensor/cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -12
- package/dist/index.js +2534 -1736
- 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,1739 +170,2622 @@ function del(path, opts) {
|
|
|
295
170
|
return request("DELETE", path, { opts });
|
|
296
171
|
}
|
|
297
172
|
|
|
298
|
-
// src/
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
function
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const paddedLabel = label.padEnd(maxLabel);
|
|
332
|
-
console.log(`${chalk.bold.cyan(paddedLabel)} ${value ?? chalk.dim("\u2014")}`);
|
|
173
|
+
// src/agent-client.ts
|
|
174
|
+
function record(value) {
|
|
175
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
176
|
+
}
|
|
177
|
+
function stringValue(value) {
|
|
178
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
179
|
+
}
|
|
180
|
+
function dataEnvelope(value) {
|
|
181
|
+
const outer = record(value);
|
|
182
|
+
return outer && "data" in outer ? outer.data : value;
|
|
183
|
+
}
|
|
184
|
+
function normalizeEvent(block) {
|
|
185
|
+
let eventName = null;
|
|
186
|
+
const dataLines = [];
|
|
187
|
+
for (const rawLine of block.split(/\r?\n/)) {
|
|
188
|
+
if (!rawLine || rawLine.startsWith(":")) continue;
|
|
189
|
+
const separator = rawLine.indexOf(":");
|
|
190
|
+
const field = separator === -1 ? rawLine : rawLine.slice(0, separator);
|
|
191
|
+
const rawValue = separator === -1 ? "" : rawLine.slice(separator + 1);
|
|
192
|
+
const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
|
|
193
|
+
if (field === "event") eventName = value;
|
|
194
|
+
if (field === "data") dataLines.push(value);
|
|
195
|
+
}
|
|
196
|
+
const rawData = dataLines.join("\n");
|
|
197
|
+
if (!eventName && !rawData) return null;
|
|
198
|
+
if (rawData === "[DONE]") return { type: "final", payload: {} };
|
|
199
|
+
let decoded = rawData;
|
|
200
|
+
if (rawData) {
|
|
201
|
+
try {
|
|
202
|
+
decoded = JSON.parse(rawData);
|
|
203
|
+
} catch {
|
|
204
|
+
decoded = rawData;
|
|
205
|
+
}
|
|
333
206
|
}
|
|
207
|
+
const decodedRecord = record(decoded);
|
|
208
|
+
const inferredType = stringValue(decodedRecord?.type) ?? stringValue(decodedRecord?.event);
|
|
209
|
+
const nestedData = record(decodedRecord?.data);
|
|
210
|
+
const payload = decodedRecord && nestedData && inferredType ? nestedData : decodedRecord ?? (rawData ? { text: rawData } : {});
|
|
211
|
+
const type = (eventName ?? inferredType ?? "message").toLowerCase().replaceAll("-", "_");
|
|
212
|
+
return { type, payload };
|
|
334
213
|
}
|
|
335
|
-
function
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
214
|
+
function createAgentEventDecoder(onEvent) {
|
|
215
|
+
let buffer = "";
|
|
216
|
+
const drain = (flush) => {
|
|
217
|
+
while (true) {
|
|
218
|
+
const boundary = buffer.match(/\r?\n\r?\n/);
|
|
219
|
+
if (!boundary || boundary.index === void 0) break;
|
|
220
|
+
const block = buffer.slice(0, boundary.index);
|
|
221
|
+
buffer = buffer.slice(boundary.index + boundary[0].length);
|
|
222
|
+
const event = normalizeEvent(block);
|
|
223
|
+
if (event) onEvent(event);
|
|
224
|
+
}
|
|
225
|
+
if (flush && buffer) {
|
|
226
|
+
const event = normalizeEvent(buffer);
|
|
227
|
+
buffer = "";
|
|
228
|
+
if (event) onEvent(event);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
push(chunk) {
|
|
233
|
+
buffer += chunk;
|
|
234
|
+
drain(false);
|
|
235
|
+
},
|
|
236
|
+
finish() {
|
|
237
|
+
drain(true);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
340
240
|
}
|
|
341
|
-
function
|
|
342
|
-
|
|
241
|
+
async function consumeAgentEventStream(response, onEvent) {
|
|
242
|
+
if (!response.body) throw new Error("The agent returned an empty response.");
|
|
243
|
+
const reader = response.body.getReader();
|
|
244
|
+
const decoder = new TextDecoder();
|
|
245
|
+
const events = createAgentEventDecoder(onEvent);
|
|
246
|
+
while (true) {
|
|
247
|
+
const { value, done } = await reader.read();
|
|
248
|
+
events.push(decoder.decode(value, { stream: !done }));
|
|
249
|
+
if (done) break;
|
|
250
|
+
}
|
|
251
|
+
events.finish();
|
|
252
|
+
}
|
|
253
|
+
function normalizeAction(value) {
|
|
254
|
+
const input = record(value);
|
|
255
|
+
const id = stringValue(input?.id);
|
|
256
|
+
const title = stringValue(input?.title);
|
|
257
|
+
if (!input || !id || !title) return null;
|
|
258
|
+
return {
|
|
259
|
+
id,
|
|
260
|
+
title,
|
|
261
|
+
summary: stringValue(input.summary) ?? "",
|
|
262
|
+
risk: stringValue(input.risk) ?? "unknown",
|
|
263
|
+
status: stringValue(input.status) ?? void 0,
|
|
264
|
+
operation: stringValue(input.operation) ?? void 0,
|
|
265
|
+
thread_id: stringValue(input.thread_id) ?? void 0,
|
|
266
|
+
turn_id: stringValue(input.turn_id) ?? void 0,
|
|
267
|
+
arguments: input.arguments,
|
|
268
|
+
preview: input.preview,
|
|
269
|
+
method: stringValue(input.method) ?? void 0,
|
|
270
|
+
path: stringValue(input.path) ?? void 0
|
|
271
|
+
};
|
|
343
272
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
273
|
+
async function streamResult(response, fallbackThreadId, onEvent) {
|
|
274
|
+
let threadId = fallbackThreadId;
|
|
275
|
+
let turnId = null;
|
|
276
|
+
let status2 = null;
|
|
277
|
+
let assistantText = "";
|
|
278
|
+
let sawFinal = false;
|
|
279
|
+
const actions = [];
|
|
280
|
+
await consumeAgentEventStream(response, (event) => {
|
|
281
|
+
const payload = event.payload;
|
|
282
|
+
if (event.type === "turn_started") {
|
|
283
|
+
threadId = stringValue(payload.thread_id) ?? stringValue(payload.threadId) ?? threadId;
|
|
284
|
+
turnId = stringValue(payload.turn_id) ?? stringValue(payload.turnId) ?? turnId;
|
|
285
|
+
} else if (event.type === "text_delta") {
|
|
286
|
+
assistantText += stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content) ?? "";
|
|
287
|
+
} else if (event.type === "approval_required") {
|
|
288
|
+
const action = normalizeAction(payload.action ?? payload.approval ?? payload);
|
|
289
|
+
if (action && !actions.some((candidate) => candidate.id === action.id)) {
|
|
290
|
+
actions.push(action);
|
|
353
291
|
}
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
292
|
+
} else if (event.type === "final") {
|
|
293
|
+
sawFinal = true;
|
|
294
|
+
status2 = stringValue(payload.status) ?? status2;
|
|
295
|
+
threadId = stringValue(payload.thread_id) ?? stringValue(payload.threadId) ?? threadId;
|
|
296
|
+
turnId = stringValue(payload.turn_id) ?? stringValue(payload.turnId) ?? turnId;
|
|
297
|
+
}
|
|
298
|
+
onEvent(event);
|
|
299
|
+
});
|
|
300
|
+
if (!sawFinal) {
|
|
301
|
+
throw new Error("The agent response ended before its final status was confirmed.");
|
|
359
302
|
}
|
|
303
|
+
return { threadId, turnId, status: status2, response: assistantText, actions };
|
|
360
304
|
}
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
305
|
+
function createAgentClient(opts) {
|
|
306
|
+
return {
|
|
307
|
+
async createThread() {
|
|
308
|
+
const response = await post("/agent/threads", {}, opts);
|
|
309
|
+
return dataEnvelope(response);
|
|
310
|
+
},
|
|
311
|
+
async listThreads() {
|
|
312
|
+
const response = await get("/agent/threads", void 0, opts);
|
|
313
|
+
return dataEnvelope(response);
|
|
314
|
+
},
|
|
315
|
+
async getThread(id) {
|
|
316
|
+
const response = await get(`/agent/threads/${encodeURIComponent(id)}`, void 0, opts);
|
|
317
|
+
const detail = dataEnvelope(response);
|
|
318
|
+
return {
|
|
319
|
+
thread: detail.thread,
|
|
320
|
+
actions: (detail.actions ?? []).map((action) => normalizeAction(action)).filter((action) => action !== null)
|
|
321
|
+
};
|
|
322
|
+
},
|
|
323
|
+
async runTurn(threadId, prompt, onEvent, signal) {
|
|
324
|
+
const streamOpts = { ...opts, signal };
|
|
325
|
+
const response = await postStream(
|
|
326
|
+
`/agent/threads/${encodeURIComponent(threadId)}/turns`,
|
|
327
|
+
{ prompt },
|
|
328
|
+
streamOpts
|
|
329
|
+
);
|
|
330
|
+
return await streamResult(response, threadId, onEvent);
|
|
331
|
+
},
|
|
332
|
+
async approveAction(actionId, onEvent, signal) {
|
|
333
|
+
const streamOpts = { ...opts, signal };
|
|
334
|
+
const response = await postStream(
|
|
335
|
+
`/agent/actions/${encodeURIComponent(actionId)}/approve`,
|
|
336
|
+
{},
|
|
337
|
+
streamOpts
|
|
338
|
+
);
|
|
339
|
+
return await streamResult(response, "", onEvent);
|
|
340
|
+
},
|
|
341
|
+
async rejectAction(actionId) {
|
|
342
|
+
await post(`/agent/actions/${encodeURIComponent(actionId)}/reject`, {}, opts);
|
|
343
|
+
}
|
|
378
344
|
};
|
|
379
|
-
const colorFn = map[status2] || chalk.white;
|
|
380
|
-
return colorFn(status2);
|
|
381
|
-
}
|
|
382
|
-
function truncate(str, max) {
|
|
383
|
-
if (str.length <= max) return str;
|
|
384
|
-
return str.slice(0, max - 1) + "\u2026";
|
|
385
|
-
}
|
|
386
|
-
function shortId(id) {
|
|
387
|
-
return id.slice(0, 8);
|
|
388
345
|
}
|
|
389
346
|
|
|
390
|
-
// src/
|
|
391
|
-
|
|
392
|
-
|
|
347
|
+
// src/agent.ts
|
|
348
|
+
import chalk2 from "chalk";
|
|
349
|
+
|
|
350
|
+
// src/shell.ts
|
|
351
|
+
import chalk from "chalk";
|
|
352
|
+
import { stat as stat2 } from "fs/promises";
|
|
353
|
+
import { homedir as homedir3 } from "os";
|
|
354
|
+
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
355
|
+
import { createInterface } from "readline";
|
|
356
|
+
|
|
357
|
+
// src/command-catalog.ts
|
|
358
|
+
var CLOUD = ["cloud"];
|
|
359
|
+
var LOCAL = ["local"];
|
|
360
|
+
var BOTH = ["cloud", "local"];
|
|
361
|
+
var COMMAND_CATALOG = [
|
|
362
|
+
{ path: "init", description: "Create a behaviour-spec project.", group: "Workflow", modes: BOTH },
|
|
363
|
+
{ path: "eval", description: "Validate a managed-service behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
364
|
+
{ path: "push", description: "Create or update the cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
365
|
+
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
366
|
+
{ path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
|
|
367
|
+
{ path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
|
|
368
|
+
{ path: "runs list", description: "List runs.", group: "Inspect", modes: BOTH },
|
|
369
|
+
{ path: "runs get", description: "Show a run.", group: "Inspect", modes: BOTH },
|
|
370
|
+
{ path: "runs report", description: "Show a run report.", group: "Inspect", modes: BOTH },
|
|
371
|
+
{ path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
|
|
372
|
+
{ path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
|
|
373
|
+
{ path: "runs estimate", description: "Estimate managed run cost and duration.", group: "Workflow", modes: CLOUD },
|
|
374
|
+
{ path: "runs start", description: "Start a managed fine-tuning run.", group: "Workflow", modes: CLOUD },
|
|
375
|
+
{ path: "runs watch", description: "Watch a managed run.", group: "Inspect", modes: CLOUD },
|
|
376
|
+
{ path: "runs diagnose", description: "Show managed run diagnostics.", group: "Inspect", modes: CLOUD },
|
|
377
|
+
{ path: "runs cancel", description: "Cancel a managed run.", group: "Workflow", modes: CLOUD },
|
|
378
|
+
{ path: "models list", description: "List models.", group: "Inspect", modes: BOTH },
|
|
379
|
+
{ path: "models get", description: "Show a model.", group: "Inspect", modes: BOTH },
|
|
380
|
+
{ path: "models base", description: "List managed-service base models.", group: "Inspect", modes: CLOUD },
|
|
381
|
+
{ path: "models download", description: "Download a managed model artifact.", group: "Serving", modes: CLOUD },
|
|
382
|
+
{ path: "models export", description: "Export a managed model to GGUF or Ollama.", group: "Serving", modes: CLOUD },
|
|
383
|
+
{ path: "models setup-runtime", description: "Install the managed model-serving runtime.", group: "Serving", modes: CLOUD },
|
|
384
|
+
{ path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: BOTH },
|
|
385
|
+
{ path: "models delete", description: "Delete a managed model.", group: "Workflow", modes: CLOUD },
|
|
386
|
+
{ path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
|
|
387
|
+
{ path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
388
|
+
{ path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
389
|
+
{ path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
|
|
390
|
+
{ path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
|
|
391
|
+
{ path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
|
|
392
|
+
{ path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
|
|
393
|
+
{ path: "specs list", description: "List cloud behaviour specs.", group: "Inspect", modes: CLOUD },
|
|
394
|
+
{ path: "specs get", description: "Show a cloud behaviour spec.", group: "Inspect", modes: CLOUD },
|
|
395
|
+
{ path: "specs create", description: "Create a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
396
|
+
{ path: "specs update", description: "Update a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
397
|
+
{ path: "specs delete", description: "Delete a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
398
|
+
{ path: "datasets list", description: "List cloud datasets.", group: "Data", modes: CLOUD },
|
|
399
|
+
{ path: "datasets get", description: "Show a cloud dataset.", group: "Data", modes: CLOUD },
|
|
400
|
+
{ path: "datasets upload", description: "Upload a cloud dataset.", group: "Data", modes: CLOUD },
|
|
401
|
+
{ path: "datasets delete", description: "Delete a cloud dataset.", group: "Data", modes: CLOUD },
|
|
402
|
+
{ path: "label upload", description: "Start a cloud teacher-labeling job.", group: "Data", modes: CLOUD },
|
|
403
|
+
{ path: "label watch", description: "Watch a cloud labeling job.", group: "Data", modes: CLOUD },
|
|
404
|
+
{ path: "label list", description: "List cloud labeling jobs.", group: "Data", modes: CLOUD },
|
|
405
|
+
{ path: "label status", description: "Show cloud labeling-job status.", group: "Data", modes: CLOUD },
|
|
406
|
+
{ path: "label rows", description: "Review labeled rows.", group: "Data", modes: CLOUD },
|
|
407
|
+
{ path: "label accept", description: "Accept labeled rows.", group: "Data", modes: CLOUD },
|
|
408
|
+
{ path: "label reject", description: "Reject labeled rows.", group: "Data", modes: CLOUD },
|
|
409
|
+
{ path: "label edit", description: "Edit a labeled row.", group: "Data", modes: CLOUD },
|
|
410
|
+
{ path: "label promote", description: "Promote reviewed rows to a dataset.", group: "Data", modes: CLOUD },
|
|
411
|
+
{ path: "label cancel", description: "Cancel a labeling job.", group: "Data", modes: CLOUD },
|
|
412
|
+
{ path: "auth login", description: "Store a cloud API key.", group: "Account", modes: CLOUD },
|
|
413
|
+
{ path: "auth logout", description: "Remove stored cloud credentials.", group: "Account", modes: CLOUD },
|
|
414
|
+
{ path: "auth status", description: "Show cloud authentication status.", group: "Account", modes: CLOUD },
|
|
415
|
+
{ path: "balance", description: "Show cloud credit balance.", group: "Account", modes: CLOUD },
|
|
416
|
+
{ path: "topup", description: "Open a cloud credit checkout.", group: "Account", modes: CLOUD },
|
|
417
|
+
{ path: "info", description: "Show TT Local package information.", group: "Inspect", modes: LOCAL }
|
|
418
|
+
];
|
|
419
|
+
var SLASH_COMMANDS = [
|
|
420
|
+
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
421
|
+
{ path: "/status", description: "Show lightweight workflow status." },
|
|
422
|
+
{ path: "/context", description: "Show the current project and backend context." },
|
|
423
|
+
{ path: "/mode cloud", description: "Use the managed cloud workflow." },
|
|
424
|
+
{ path: "/mode local", description: "Use the local GPU workflow." },
|
|
425
|
+
{ path: "/model", description: "Show the active model; /model <id> activates a local model." },
|
|
426
|
+
{ path: "/cd", description: "Change the shell's working directory." },
|
|
427
|
+
{ path: "/clear", description: "Clear the terminal." },
|
|
428
|
+
{ path: "/exit", description: "Exit the TT shell." }
|
|
429
|
+
];
|
|
430
|
+
function catalogForMode(mode) {
|
|
431
|
+
return COMMAND_CATALOG.filter((command) => command.modes.includes(mode));
|
|
393
432
|
}
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
433
|
+
function commandPathsForMode(mode) {
|
|
434
|
+
return [...new Set(catalogForMode(mode).map((command) => command.path))].sort((left, right) => left.localeCompare(right));
|
|
435
|
+
}
|
|
436
|
+
function groupedCatalog(mode, query) {
|
|
437
|
+
const normalizedQuery = query?.trim().toLowerCase();
|
|
438
|
+
const matches = catalogForMode(mode).filter((command) => {
|
|
439
|
+
if (!normalizedQuery) return true;
|
|
440
|
+
return command.path.toLowerCase().includes(normalizedQuery) || command.description.toLowerCase().includes(normalizedQuery) || command.group.toLowerCase().includes(normalizedQuery);
|
|
441
|
+
});
|
|
442
|
+
const groups = /* @__PURE__ */ new Map();
|
|
443
|
+
for (const command of matches) {
|
|
444
|
+
const group = groups.get(command.group) ?? [];
|
|
445
|
+
group.push(command);
|
|
446
|
+
groups.set(command.group, group);
|
|
398
447
|
}
|
|
399
|
-
return
|
|
448
|
+
return groups;
|
|
400
449
|
}
|
|
401
|
-
function
|
|
402
|
-
|
|
450
|
+
function completionTarget(line, activeMode) {
|
|
451
|
+
const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
|
|
452
|
+
const content = line.slice(leadingWhitespace.length);
|
|
453
|
+
const override = content.match(/^(cloud|local)(?:\s+|$)/);
|
|
454
|
+
if (!override) {
|
|
455
|
+
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
456
|
+
}
|
|
457
|
+
const mode = override[1];
|
|
458
|
+
const prefix = `${leadingWhitespace}${mode} `;
|
|
459
|
+
return {
|
|
460
|
+
prefix,
|
|
461
|
+
mode,
|
|
462
|
+
fragment: content.slice(override[0].length)
|
|
463
|
+
};
|
|
403
464
|
}
|
|
404
|
-
function
|
|
405
|
-
return
|
|
465
|
+
function createCommandCompleter(getMode) {
|
|
466
|
+
return (line) => {
|
|
467
|
+
const trimmed = line.trimStart();
|
|
468
|
+
if (trimmed.startsWith("/")) {
|
|
469
|
+
const candidates = [
|
|
470
|
+
...SLASH_COMMANDS.map((command) => command.path),
|
|
471
|
+
"/new",
|
|
472
|
+
"/threads",
|
|
473
|
+
"/resume ",
|
|
474
|
+
"/approve ",
|
|
475
|
+
"/reject "
|
|
476
|
+
];
|
|
477
|
+
const matches2 = candidates.filter((candidate) => candidate.startsWith(trimmed));
|
|
478
|
+
return [matches2.length > 0 ? matches2 : candidates, line];
|
|
479
|
+
}
|
|
480
|
+
if (trimmed.startsWith("?")) {
|
|
481
|
+
return [["?"], line];
|
|
482
|
+
}
|
|
483
|
+
const { prefix, mode, fragment } = completionTarget(line, getMode());
|
|
484
|
+
const paths = commandPathsForMode(mode);
|
|
485
|
+
const targetCandidates = paths.map((path) => `${prefix}${path}`);
|
|
486
|
+
if (!prefix.trim()) targetCandidates.unshift("cloud ", "local ");
|
|
487
|
+
const normalizedLine = line.trimStart().toLowerCase();
|
|
488
|
+
const normalizedFragment = fragment.toLowerCase();
|
|
489
|
+
const matches = targetCandidates.filter((candidate) => {
|
|
490
|
+
if (prefix.trim()) return candidate.toLowerCase().startsWith(`${prefix.toLowerCase()}${normalizedFragment}`);
|
|
491
|
+
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
492
|
+
});
|
|
493
|
+
return [matches.length > 0 ? matches : targetCandidates, line];
|
|
494
|
+
};
|
|
406
495
|
}
|
|
407
|
-
|
|
408
|
-
|
|
496
|
+
|
|
497
|
+
// src/shell-context.ts
|
|
498
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
499
|
+
import { homedir as homedir2 } from "os";
|
|
500
|
+
import { basename as basename2, dirname, isAbsolute, join as join2, resolve } from "path";
|
|
501
|
+
async function readJsonObject(path) {
|
|
502
|
+
try {
|
|
503
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
504
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
505
|
+
return { found: true, invalid: true };
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
found: true,
|
|
509
|
+
value: parsed,
|
|
510
|
+
invalid: false
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
if (error.code === "ENOENT") {
|
|
514
|
+
return { found: false, invalid: false };
|
|
515
|
+
}
|
|
516
|
+
return { found: true, invalid: true };
|
|
517
|
+
}
|
|
409
518
|
}
|
|
410
|
-
function
|
|
411
|
-
const
|
|
412
|
-
return
|
|
519
|
+
function stringField(value, key) {
|
|
520
|
+
const field = value?.[key];
|
|
521
|
+
return typeof field === "string" && field.trim() ? field : void 0;
|
|
413
522
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
if (
|
|
417
|
-
return
|
|
523
|
+
function expandPath(value, baseDirectory, homeDirectory) {
|
|
524
|
+
if (value === "~") return homeDirectory;
|
|
525
|
+
if (value.startsWith("~/")) return resolve(homeDirectory, value.slice(2));
|
|
526
|
+
return isAbsolute(value) ? resolve(value) : resolve(baseDirectory, value);
|
|
418
527
|
}
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
if (bytes === void 0 || bytes < 0) return void 0;
|
|
422
|
-
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
423
|
-
let amount = bytes;
|
|
424
|
-
let unit = 0;
|
|
425
|
-
while (amount >= 1024 && unit < units.length - 1) {
|
|
426
|
-
amount /= 1024;
|
|
427
|
-
unit += 1;
|
|
428
|
-
}
|
|
429
|
-
const precision = amount >= 10 || unit === 0 ? 0 : 1;
|
|
430
|
-
return `${amount.toFixed(precision)} ${units[unit]}`;
|
|
528
|
+
function environmentHome(env) {
|
|
529
|
+
return env.HOME ? resolve(env.HOME) : homedir2();
|
|
431
530
|
}
|
|
432
|
-
function
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
return value.map(
|
|
437
|
-
(part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
|
|
438
|
-
).join(" ");
|
|
531
|
+
function cloudConfigPath(env, homeDirectory) {
|
|
532
|
+
const configHome = env.XDG_CONFIG_HOME ? resolve(env.XDG_CONFIG_HOME) : join2(homeDirectory, ".config");
|
|
533
|
+
return join2(configHome, "tuned-tensor", "config.json");
|
|
439
534
|
}
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
535
|
+
function redactApiKey(key) {
|
|
536
|
+
if (!key) return void 0;
|
|
537
|
+
if (key.length <= 4) return "\u2026";
|
|
538
|
+
const prefixLength = key.length > 8 ? 8 : Math.max(1, Math.floor(key.length / 2));
|
|
539
|
+
return `${key.slice(0, prefixLength)}\u2026`;
|
|
443
540
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
541
|
+
function targetFromEnvironment(env) {
|
|
542
|
+
const value = env.TT_TARGET?.trim().toLowerCase();
|
|
543
|
+
return value === "cloud" || value === "local" ? value : void 0;
|
|
446
544
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (gate.passed === true) {
|
|
456
|
-
printSuccess("General regression gate passed");
|
|
457
|
-
return;
|
|
545
|
+
async function adjacentFile(path) {
|
|
546
|
+
try {
|
|
547
|
+
return (await stat(path)).isFile();
|
|
548
|
+
} catch {
|
|
549
|
+
return false;
|
|
458
550
|
}
|
|
459
|
-
printWarning(
|
|
460
|
-
"The local run completed, but the general regression gate failed."
|
|
461
|
-
);
|
|
462
|
-
const failures = Array.isArray(gate.failures) ? gate.failures.filter(
|
|
463
|
-
(failure) => typeof failure === "string"
|
|
464
|
-
) : [];
|
|
465
|
-
for (const failure of failures) printWarning(failure);
|
|
466
551
|
}
|
|
467
|
-
function
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
check.ok === true ? formatStatus("completed") : formatStatus("failed"),
|
|
474
|
-
truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
|
|
475
|
-
])
|
|
476
|
-
);
|
|
477
|
-
if (value.ok === true) {
|
|
478
|
-
printSuccess("Local environment is ready");
|
|
479
|
-
} else {
|
|
480
|
-
printError("Local environment needs attention");
|
|
481
|
-
}
|
|
482
|
-
if (value.config_path) {
|
|
483
|
-
printDetail([["Config", text(value.config_path)]]);
|
|
484
|
-
}
|
|
552
|
+
async function readActiveModelId(storeRoot) {
|
|
553
|
+
const result = await readJsonObject(join2(storeRoot, "active-model.json"));
|
|
554
|
+
if (!result.found || result.invalid) return void 0;
|
|
555
|
+
const modelId = result.value?.model_id;
|
|
556
|
+
if (modelId === null) return "base";
|
|
557
|
+
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
485
558
|
}
|
|
486
|
-
function
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
559
|
+
async function readLatestRun(storeRoot) {
|
|
560
|
+
const runsRoot = join2(storeRoot, "runs");
|
|
561
|
+
let names;
|
|
562
|
+
try {
|
|
563
|
+
names = (await readdir(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
564
|
+
} catch {
|
|
565
|
+
return void 0;
|
|
566
|
+
}
|
|
567
|
+
const states = await Promise.all(names.map(async (name) => {
|
|
568
|
+
const result = await readJsonObject(join2(runsRoot, name, "state.json"));
|
|
569
|
+
if (!result.found || result.invalid || !result.value) return void 0;
|
|
570
|
+
const id = stringField(result.value, "id") ?? name;
|
|
571
|
+
const state = {
|
|
572
|
+
id,
|
|
573
|
+
status: stringField(result.value, "status"),
|
|
574
|
+
specName: stringField(result.value, "spec_name"),
|
|
575
|
+
updatedAt: stringField(result.value, "updated_at")
|
|
576
|
+
};
|
|
577
|
+
return state;
|
|
578
|
+
}));
|
|
579
|
+
return states.filter((state) => Boolean(state)).sort((left, right) => {
|
|
580
|
+
const leftUpdated = left.updatedAt ?? "";
|
|
581
|
+
const rightUpdated = right.updatedAt ?? "";
|
|
582
|
+
if (leftUpdated !== rightUpdated) {
|
|
583
|
+
return rightUpdated.localeCompare(leftUpdated);
|
|
584
|
+
}
|
|
585
|
+
return right.id.localeCompare(left.id);
|
|
586
|
+
})[0];
|
|
495
587
|
}
|
|
496
|
-
function
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
588
|
+
async function discoverShellContext(options = {}) {
|
|
589
|
+
const env = options.env ?? process.env;
|
|
590
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
591
|
+
const homeDirectory = environmentHome(env);
|
|
592
|
+
const warnings = [];
|
|
593
|
+
const specPath = join2(cwd, "tunedtensor.json");
|
|
594
|
+
const specJson = await readJsonObject(specPath);
|
|
595
|
+
let spec;
|
|
596
|
+
if (specJson.found) {
|
|
597
|
+
if (specJson.invalid || !specJson.value) {
|
|
598
|
+
spec = { path: specPath, parseError: true };
|
|
599
|
+
warnings.push("tunedtensor.json could not be parsed.");
|
|
600
|
+
} else {
|
|
601
|
+
const examples = specJson.value.examples;
|
|
602
|
+
spec = {
|
|
603
|
+
path: specPath,
|
|
604
|
+
id: stringField(specJson.value, "id"),
|
|
605
|
+
name: stringField(specJson.value, "name"),
|
|
606
|
+
baseModel: stringField(specJson.value, "base_model"),
|
|
607
|
+
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
608
|
+
parseError: false
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const localConfigPath = join2(cwd, "local-runner.json");
|
|
613
|
+
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
614
|
+
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
615
|
+
if (localConfigJson.invalid) {
|
|
616
|
+
warnings.push("local-runner.json could not be parsed.");
|
|
617
|
+
}
|
|
618
|
+
const localConfigDirectory = hasAdjacentLocalConfig ? dirname(localConfigPath) : cwd;
|
|
619
|
+
const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
|
|
620
|
+
const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
|
|
621
|
+
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve(cwd, ".tt-local", "artifacts");
|
|
622
|
+
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join2(homeDirectory, ".tuned-tensor-local");
|
|
623
|
+
const cloudPath = cloudConfigPath(env, homeDirectory);
|
|
624
|
+
const cloudConfigJson = await readJsonObject(cloudPath);
|
|
625
|
+
if (cloudConfigJson.invalid) {
|
|
626
|
+
warnings.push("The Tuned Tensor cloud config could not be parsed.");
|
|
627
|
+
}
|
|
628
|
+
const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
|
|
629
|
+
const storedApiKey = stringField(cloudConfigJson.value, "api_key");
|
|
630
|
+
const apiKey = environmentApiKey || storedApiKey;
|
|
631
|
+
const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
|
|
632
|
+
const environmentTarget = targetFromEnvironment(env);
|
|
633
|
+
if (env.TT_TARGET && !environmentTarget) {
|
|
634
|
+
warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
|
|
635
|
+
}
|
|
636
|
+
const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
|
|
637
|
+
const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
|
|
638
|
+
const [activeModelId, latestRun] = await Promise.all([
|
|
639
|
+
readActiveModelId(storeRoot),
|
|
640
|
+
readLatestRun(storeRoot)
|
|
507
641
|
]);
|
|
642
|
+
return {
|
|
643
|
+
cwd,
|
|
644
|
+
projectName: basename2(cwd) || cwd,
|
|
645
|
+
inferredTarget,
|
|
646
|
+
targetSource,
|
|
647
|
+
spec,
|
|
648
|
+
cloud: {
|
|
649
|
+
authenticated: Boolean(apiKey),
|
|
650
|
+
keyPrefix: redactApiKey(apiKey),
|
|
651
|
+
baseUrl,
|
|
652
|
+
configPath: cloudPath,
|
|
653
|
+
configFound: cloudConfigJson.found
|
|
654
|
+
},
|
|
655
|
+
local: {
|
|
656
|
+
configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
|
|
657
|
+
artifactRoot,
|
|
658
|
+
storeRoot,
|
|
659
|
+
activeModelId,
|
|
660
|
+
latestRun
|
|
661
|
+
},
|
|
662
|
+
warnings
|
|
663
|
+
};
|
|
508
664
|
}
|
|
509
|
-
function
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
665
|
+
function valueOrDash(value) {
|
|
666
|
+
return value === void 0 || value === "" ? "\u2014" : String(value);
|
|
667
|
+
}
|
|
668
|
+
function targetSourceLabel(source) {
|
|
669
|
+
switch (source) {
|
|
670
|
+
case "environment":
|
|
671
|
+
return "TT_TARGET";
|
|
672
|
+
case "adjacent-config":
|
|
673
|
+
return "adjacent local-runner.json";
|
|
674
|
+
default:
|
|
675
|
+
return "default";
|
|
515
676
|
}
|
|
516
|
-
const comparison = record(value.comparison);
|
|
517
|
-
printDetail([
|
|
518
|
-
["Run", text(value.run_id)],
|
|
519
|
-
["Status", status(runStatus)],
|
|
520
|
-
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
521
|
-
["Report", text(value.report_path)],
|
|
522
|
-
["Artifacts", text(value.artifact_dir)],
|
|
523
|
-
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
524
|
-
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
|
|
525
|
-
]);
|
|
526
|
-
renderGate(value);
|
|
527
677
|
}
|
|
528
|
-
function
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
678
|
+
function formatShellContext(context, selectedTarget, targetSource) {
|
|
679
|
+
const specLabel = context.spec ? context.spec.parseError ? `${context.spec.path} (invalid JSON)` : context.spec.path : "not found";
|
|
680
|
+
const sourceLabel = targetSource === "session" ? "session" : targetSource === "command-option" ? "--target" : targetSourceLabel(targetSource);
|
|
681
|
+
const lines = [
|
|
682
|
+
`Target ${selectedTarget} (${sourceLabel})`,
|
|
683
|
+
`Project ${context.projectName}`,
|
|
684
|
+
`Directory ${context.cwd}`,
|
|
685
|
+
`Spec ${specLabel}`,
|
|
686
|
+
`Spec ID ${valueOrDash(context.spec?.id)}`,
|
|
687
|
+
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
688
|
+
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
689
|
+
`Cloud endpoint ${context.cloud.baseUrl}`,
|
|
690
|
+
`Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
|
|
691
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
692
|
+
`Artifact root ${context.local.artifactRoot}`,
|
|
693
|
+
`Store root ${context.local.storeRoot}`
|
|
694
|
+
];
|
|
695
|
+
for (const warning of context.warnings) lines.push(`Warning ${warning}`);
|
|
696
|
+
return lines;
|
|
697
|
+
}
|
|
698
|
+
function formatShellStatus(context, selectedTarget) {
|
|
699
|
+
const lines = [
|
|
700
|
+
`Workflow ${selectedTarget}`,
|
|
701
|
+
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
|
|
702
|
+
];
|
|
703
|
+
if (selectedTarget === "cloud") {
|
|
704
|
+
lines.push(
|
|
705
|
+
`Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
|
|
706
|
+
`Endpoint ${context.cloud.baseUrl}`,
|
|
707
|
+
"Remote status not queried"
|
|
708
|
+
);
|
|
709
|
+
} else {
|
|
710
|
+
lines.push(
|
|
711
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
712
|
+
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
713
|
+
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
714
|
+
"Host checks not run (use doctor)"
|
|
715
|
+
);
|
|
533
716
|
}
|
|
534
|
-
|
|
535
|
-
["Run", "Spec", "Status", "Stage", "Model", "Updated"],
|
|
536
|
-
runRows.map((run) => [
|
|
537
|
-
displayId(run.id),
|
|
538
|
-
truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
|
|
539
|
-
status(run.status),
|
|
540
|
-
text(run.current_stage) ?? "\u2014",
|
|
541
|
-
displayId(run.model_id),
|
|
542
|
-
formatDate(text(run.updated_at))
|
|
543
|
-
])
|
|
544
|
-
);
|
|
717
|
+
return lines;
|
|
545
718
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
719
|
+
|
|
720
|
+
// src/shell.ts
|
|
721
|
+
var AGENT_SLASH_COMMANDS = /* @__PURE__ */ new Set([
|
|
722
|
+
"new",
|
|
723
|
+
"threads",
|
|
724
|
+
"resume",
|
|
725
|
+
"approve",
|
|
726
|
+
"reject"
|
|
727
|
+
]);
|
|
728
|
+
var ShellParseError = class extends Error {
|
|
729
|
+
constructor(message) {
|
|
730
|
+
super(message);
|
|
731
|
+
this.name = "ShellParseError";
|
|
551
732
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
text(event.stage) ?? "\u2014",
|
|
557
|
-
status(event.status),
|
|
558
|
-
truncate(text(event.message) ?? "\u2014", 80)
|
|
559
|
-
])
|
|
733
|
+
};
|
|
734
|
+
function operatorError(operator) {
|
|
735
|
+
return new ShellParseError(
|
|
736
|
+
`Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
|
|
560
737
|
);
|
|
561
738
|
}
|
|
562
|
-
function
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
|
|
566
|
-
["Status", status(value.status)],
|
|
567
|
-
["Stage", text(value.current_stage)],
|
|
568
|
-
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
569
|
-
["Base model", text(value.base_model)],
|
|
570
|
-
["Report", text(value.report_path)],
|
|
571
|
-
["Artifacts", text(value.artifact_dir)],
|
|
572
|
-
["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
|
|
573
|
-
]);
|
|
574
|
-
}
|
|
575
|
-
function renderRunReport(value) {
|
|
576
|
-
const baseline = record(value.baseline);
|
|
577
|
-
const candidate = record(value.candidate);
|
|
578
|
-
const comparison = record(value.comparison);
|
|
579
|
-
const metadata = record(value.run_metadata);
|
|
580
|
-
printDetail([
|
|
581
|
-
["Run", text(value.run_id)],
|
|
582
|
-
["Status", status(value.status)],
|
|
583
|
-
["Base model", text(value.base_model)],
|
|
584
|
-
["Tuned model", text(value.fine_tuned_model_id)],
|
|
585
|
-
["Training examples", text(metadata?.training_example_count)],
|
|
586
|
-
["Evaluation examples", text(metadata?.eval_examples_used)],
|
|
587
|
-
["Baseline score", displayNumber(baseline?.avg_score)],
|
|
588
|
-
["Candidate score", displayNumber(candidate?.avg_score)],
|
|
589
|
-
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
590
|
-
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
|
|
591
|
-
["Regressions", text(comparison?.regressions)],
|
|
592
|
-
["Improvements", text(comparison?.improvements)]
|
|
593
|
-
]);
|
|
594
|
-
renderGate(value);
|
|
595
|
-
}
|
|
596
|
-
function renderRunComparison(value) {
|
|
597
|
-
const runA = record(value.run_a);
|
|
598
|
-
const runB = record(value.run_b);
|
|
599
|
-
const shared = record(value.shared);
|
|
600
|
-
const sharedA = record(shared?.run_a);
|
|
601
|
-
const sharedB = record(shared?.run_b);
|
|
602
|
-
printTable(
|
|
603
|
-
["Run", "Candidate score", "Token F1", "Examples"],
|
|
604
|
-
[
|
|
605
|
-
[
|
|
606
|
-
displayId(runA?.run_id),
|
|
607
|
-
displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
|
|
608
|
-
displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
|
|
609
|
-
text(shared?.examples) ?? "0"
|
|
610
|
-
],
|
|
611
|
-
[
|
|
612
|
-
displayId(runB?.run_id),
|
|
613
|
-
displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
|
|
614
|
-
displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
|
|
615
|
-
text(shared?.examples) ?? "0"
|
|
616
|
-
]
|
|
617
|
-
]
|
|
618
|
-
);
|
|
619
|
-
const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
|
|
620
|
-
for (const note of notes) printWarning(note);
|
|
621
|
-
}
|
|
622
|
-
function renderModelsList(value) {
|
|
623
|
-
const modelRows = rows(value);
|
|
624
|
-
if (modelRows.length === 0) {
|
|
625
|
-
printWarning("No local models found.");
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
printTable(
|
|
629
|
-
["Model", "Base model", "Run", "Provider", "Created"],
|
|
630
|
-
modelRows.map((model) => [
|
|
631
|
-
displayId(model.id),
|
|
632
|
-
truncate(text(model.base_model) ?? "\u2014", 32),
|
|
633
|
-
displayId(model.run_id),
|
|
634
|
-
text(model.provider) ?? "\u2014",
|
|
635
|
-
formatDate(text(model.created_at))
|
|
636
|
-
])
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
function renderModelRecord(value) {
|
|
640
|
-
const model = record(value.model) ?? value;
|
|
641
|
-
const artifact = record(value.artifact);
|
|
642
|
-
const integrity = record(value.integrity);
|
|
643
|
-
printDetail([
|
|
644
|
-
["Model", text(model.id) ?? text(value.active)],
|
|
645
|
-
["Base model", text(model.base_model)],
|
|
646
|
-
["Run", text(model.run_id)],
|
|
647
|
-
["Provider", text(model.provider)],
|
|
648
|
-
["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
|
|
649
|
-
["Manifest", text(value.manifest_path)],
|
|
650
|
-
["Verified files", text(integrity?.checked)],
|
|
651
|
-
["Created", formatDate(text(model.created_at))]
|
|
652
|
-
]);
|
|
653
|
-
}
|
|
654
|
-
function renderModelPrefetch(value) {
|
|
655
|
-
printSuccess(
|
|
656
|
-
value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
|
|
657
|
-
);
|
|
658
|
-
printDetail([
|
|
659
|
-
["Base model", text(value.base_model)],
|
|
660
|
-
["Revision", text(value.snapshot_revision)],
|
|
661
|
-
["Snapshot", text(value.snapshot_path)],
|
|
662
|
-
["Files", text(value.file_count)],
|
|
663
|
-
["Size", displayBytes(value.size_bytes)],
|
|
664
|
-
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
665
|
-
]);
|
|
666
|
-
}
|
|
667
|
-
function renderActiveModel(value) {
|
|
668
|
-
const active = text(value.active) ?? "base";
|
|
669
|
-
const pointer = record(value.pointer);
|
|
670
|
-
if (pointer?.action === "activate") {
|
|
671
|
-
printSuccess(`Activated local model ${active}`);
|
|
672
|
-
} else if (pointer?.action === "rollback") {
|
|
673
|
-
printSuccess(`Rolled back to ${active}`);
|
|
674
|
-
}
|
|
675
|
-
printDetail([
|
|
676
|
-
["Active", active],
|
|
677
|
-
["Run", text(pointer?.run_id)],
|
|
678
|
-
["Previous", text(pointer?.previous_model_id) ?? "base"],
|
|
679
|
-
["Changed", formatDate(text(pointer?.activated_at))],
|
|
680
|
-
["Manifest", text(value.manifest_path)]
|
|
681
|
-
]);
|
|
682
|
-
}
|
|
683
|
-
function renderServePlan(value) {
|
|
684
|
-
printSuccess("Local serving plan is valid");
|
|
685
|
-
printDetail([
|
|
686
|
-
["Model", text(value.model_id) ?? text(value.base_model)],
|
|
687
|
-
["URL", text(value.url)],
|
|
688
|
-
["Artifact", text(value.artifact_path)],
|
|
689
|
-
["Manifest", text(value.manifest_path)],
|
|
690
|
-
["Command", displayCommand(value.command)]
|
|
691
|
-
]);
|
|
692
|
-
}
|
|
693
|
-
function renderKnownJson(args, value) {
|
|
694
|
-
const command = args[0];
|
|
695
|
-
const subcommand = args[1];
|
|
696
|
-
if (command === "doctor" && isRecord(value)) {
|
|
697
|
-
renderDoctor(value);
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
if (command === "init" && isRecord(value)) {
|
|
701
|
-
renderInit(value);
|
|
702
|
-
return true;
|
|
739
|
+
function tokenizeShellInput(input) {
|
|
740
|
+
if (input.includes("\0")) {
|
|
741
|
+
throw new ShellParseError("Command input must not contain NUL bytes.");
|
|
703
742
|
}
|
|
704
|
-
if (
|
|
705
|
-
|
|
706
|
-
return true;
|
|
743
|
+
if (/[\r\n]/.test(input)) {
|
|
744
|
+
throw new ShellParseError("Enter one command at a time.");
|
|
707
745
|
}
|
|
708
|
-
if (
|
|
709
|
-
|
|
710
|
-
return true;
|
|
746
|
+
if (input.trimStart().startsWith("!")) {
|
|
747
|
+
throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
|
|
711
748
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (
|
|
718
|
-
|
|
719
|
-
|
|
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;
|
|
720
768
|
}
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
|
|
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;
|
|
724
791
|
}
|
|
725
|
-
if (
|
|
726
|
-
|
|
727
|
-
|
|
792
|
+
if (/\s/.test(character)) {
|
|
793
|
+
finishToken();
|
|
794
|
+
continue;
|
|
728
795
|
}
|
|
729
|
-
if (
|
|
730
|
-
|
|
731
|
-
|
|
796
|
+
if (character === "'") {
|
|
797
|
+
quote = "single";
|
|
798
|
+
tokenStarted = true;
|
|
799
|
+
continue;
|
|
732
800
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return true;
|
|
801
|
+
if (character === '"') {
|
|
802
|
+
quote = "double";
|
|
803
|
+
tokenStarted = true;
|
|
804
|
+
continue;
|
|
738
805
|
}
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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;
|
|
743
815
|
}
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
816
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
817
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
818
|
+
throw operatorError("$(\u2026)");
|
|
747
819
|
}
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
|
|
820
|
+
if (character === "|" || character === ">" || character === "<" || character === ";" || character === "&") {
|
|
821
|
+
const doubled = input[index + 1] === character && (character === "|" || character === "&");
|
|
822
|
+
throw operatorError(doubled ? character.repeat(2) : character);
|
|
751
823
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
824
|
+
token += character;
|
|
825
|
+
tokenStarted = true;
|
|
826
|
+
}
|
|
827
|
+
if (quote !== "none") {
|
|
828
|
+
throw new ShellParseError(
|
|
829
|
+
`Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
finishToken();
|
|
833
|
+
return tokens;
|
|
834
|
+
}
|
|
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`.");
|
|
755
842
|
}
|
|
756
843
|
}
|
|
757
|
-
if (
|
|
758
|
-
|
|
759
|
-
return true;
|
|
844
|
+
if (args[0]?.startsWith("/")) {
|
|
845
|
+
throw new ShellParseError("Slash commands are session commands and cannot be routed to a workflow.");
|
|
760
846
|
}
|
|
761
|
-
|
|
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 };
|
|
762
855
|
}
|
|
763
|
-
function
|
|
764
|
-
|
|
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
|
+
});
|
|
765
866
|
}
|
|
766
|
-
function
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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);
|
|
771
874
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
name: heading[1],
|
|
799
|
-
description: heading[2],
|
|
800
|
-
...fields
|
|
801
|
-
}
|
|
802
|
-
};
|
|
875
|
+
var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
876
|
+
"help",
|
|
877
|
+
"status",
|
|
878
|
+
"context",
|
|
879
|
+
"mode",
|
|
880
|
+
"model",
|
|
881
|
+
"clear",
|
|
882
|
+
"cd",
|
|
883
|
+
"exit"
|
|
884
|
+
]);
|
|
885
|
+
var WORKFLOW_ROOT_COMMANDS = new Set(
|
|
886
|
+
COMMAND_CATALOG.map((command) => command.path.split(" ", 1)[0])
|
|
887
|
+
);
|
|
888
|
+
function editDistance(left, right) {
|
|
889
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
890
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
891
|
+
let diagonal = previous[0];
|
|
892
|
+
previous[0] = i;
|
|
893
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
894
|
+
const above = previous[j];
|
|
895
|
+
previous[j] = Math.min(
|
|
896
|
+
previous[j] + 1,
|
|
897
|
+
previous[j - 1] + 1,
|
|
898
|
+
diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
899
|
+
);
|
|
900
|
+
diagonal = above;
|
|
803
901
|
}
|
|
804
902
|
}
|
|
805
|
-
return
|
|
903
|
+
return previous[right.length];
|
|
806
904
|
}
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (payload.exitCode !== 0 || payload.errorMessage) {
|
|
818
|
-
output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
|
|
819
|
-
`);
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
|
|
823
|
-
`);
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
if (payload.droppedSpecKeys.length > 0) {
|
|
827
|
-
printWarning(
|
|
828
|
-
`Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
|
|
905
|
+
function suggestSlashCommands(rawName) {
|
|
906
|
+
const names = [...SLASH_NAMES].sort();
|
|
907
|
+
const prefixMatches = names.filter((name) => name.startsWith(rawName));
|
|
908
|
+
if (prefixMatches.length > 0) return prefixMatches;
|
|
909
|
+
return names.filter((name) => editDistance(name, rawName) <= 2);
|
|
910
|
+
}
|
|
911
|
+
function unknownSlashCommandError(rawName) {
|
|
912
|
+
if (WORKFLOW_ROOT_COMMANDS.has(rawName)) {
|
|
913
|
+
return new ShellParseError(
|
|
914
|
+
`Unknown session command: /${rawName}. Workflow commands need no slash \u2014 try "${rawName} --help".`
|
|
829
915
|
);
|
|
830
916
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
917
|
+
const suggestions = suggestSlashCommands(rawName);
|
|
918
|
+
const hint = suggestions.length > 0 ? ` Did you mean ${suggestions.map((name) => `/${name}`).join(" or ")}?` : "";
|
|
919
|
+
return new ShellParseError(
|
|
920
|
+
`Unknown session command: /${rawName}.${hint} Use /help to see available commands.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
function parseSlashCommand(input) {
|
|
924
|
+
const trimmed = input.trim();
|
|
925
|
+
if (!trimmed) return null;
|
|
926
|
+
if (trimmed === "?") return { name: "help", args: [] };
|
|
927
|
+
if (trimmed.startsWith("? ")) {
|
|
928
|
+
return {
|
|
929
|
+
name: "help",
|
|
930
|
+
args: tokenizeShellInput(trimmed.slice(1))
|
|
931
|
+
};
|
|
835
932
|
}
|
|
836
|
-
if (
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
933
|
+
if (!trimmed.startsWith("/")) return null;
|
|
934
|
+
if (trimmed === "/") return { name: "palette", args: [] };
|
|
935
|
+
const tokens = tokenizeShellInput(trimmed);
|
|
936
|
+
const rawName = tokens[0].slice(1).toLowerCase();
|
|
937
|
+
if (!SLASH_NAMES.has(rawName)) {
|
|
938
|
+
throw unknownSlashCommandError(rawName);
|
|
841
939
|
}
|
|
940
|
+
return {
|
|
941
|
+
name: rawName,
|
|
942
|
+
args: tokens.slice(1)
|
|
943
|
+
};
|
|
842
944
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
var localRequire = createRequire(import.meta.url);
|
|
846
|
-
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
847
|
-
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
848
|
-
"--config",
|
|
849
|
-
"--name",
|
|
850
|
-
"--model",
|
|
851
|
-
"--output",
|
|
852
|
-
"--profile",
|
|
853
|
-
"--host",
|
|
854
|
-
"--port",
|
|
855
|
-
"--device",
|
|
856
|
-
"--max-tokens",
|
|
857
|
-
"--temperature",
|
|
858
|
-
"--top-p",
|
|
859
|
-
"--max-concurrent-requests",
|
|
860
|
-
"--spec",
|
|
861
|
-
"--api-key-env"
|
|
862
|
-
]);
|
|
863
|
-
function isOptionWithInlineValue(value) {
|
|
864
|
-
const equals = value.indexOf("=");
|
|
865
|
-
return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
|
|
945
|
+
function errorMessage(error) {
|
|
946
|
+
return error instanceof Error ? error.message : String(error);
|
|
866
947
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
indices.push(index);
|
|
881
|
-
}
|
|
882
|
-
return indices;
|
|
948
|
+
var successMark = () => chalk.green("\u2713");
|
|
949
|
+
var errorMark = () => chalk.red("\u2717");
|
|
950
|
+
var accent = chalk.hex("#8B5CF6");
|
|
951
|
+
var DETAIL_LABEL_WIDTH = 15;
|
|
952
|
+
function styleDetailLines(lines) {
|
|
953
|
+
return lines.map((line) => {
|
|
954
|
+
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
955
|
+
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
956
|
+
if (!label.trim()) return line;
|
|
957
|
+
const color = label.startsWith("Warning") ? chalk.yellow : accent.bold;
|
|
958
|
+
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
959
|
+
});
|
|
883
960
|
}
|
|
884
|
-
function
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
return {
|
|
891
|
-
path,
|
|
892
|
-
replace(projectedPath) {
|
|
893
|
-
args[index + 1] = projectedPath;
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
if (value.startsWith(`${name}=`)) {
|
|
898
|
-
const path = value.slice(name.length + 1);
|
|
899
|
-
if (!path) return null;
|
|
900
|
-
return {
|
|
901
|
-
path,
|
|
902
|
-
replace(projectedPath) {
|
|
903
|
-
args[index] = `${name}=${projectedPath}`;
|
|
904
|
-
}
|
|
905
|
-
};
|
|
906
|
-
}
|
|
961
|
+
function detailLine(label, value) {
|
|
962
|
+
return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
|
|
963
|
+
}
|
|
964
|
+
function assertNoArgs(command, args) {
|
|
965
|
+
if (args.length > 0) {
|
|
966
|
+
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
907
967
|
}
|
|
908
|
-
return null;
|
|
909
968
|
}
|
|
910
|
-
function
|
|
911
|
-
const
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
969
|
+
function expandDirectory(value, cwd, env) {
|
|
970
|
+
const home = env.HOME ? resolve2(env.HOME) : homedir3();
|
|
971
|
+
if (value === "~") return home;
|
|
972
|
+
if (value.startsWith("~/")) return resolve2(home, value.slice(2));
|
|
973
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(cwd, value);
|
|
974
|
+
}
|
|
975
|
+
function helpText(mode, query, palette = false) {
|
|
976
|
+
const groups = groupedCatalog(mode, query);
|
|
977
|
+
const lines = [];
|
|
978
|
+
lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
|
|
979
|
+
if (groups.size === 0) {
|
|
980
|
+
lines.push(chalk.dim(" No matching commands."));
|
|
981
|
+
} else {
|
|
982
|
+
for (const [group, commands] of groups) {
|
|
983
|
+
lines.push("");
|
|
984
|
+
lines.push(chalk.bold(group));
|
|
985
|
+
for (const command of commands) {
|
|
986
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
917
987
|
}
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
return {
|
|
921
|
-
path: defaultPath,
|
|
922
|
-
replace(projectedPath) {
|
|
923
|
-
args.splice(start, 0, projectedPath);
|
|
924
988
|
}
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
function selectedSpecArgument(args, cwd) {
|
|
928
|
-
const command = args[0];
|
|
929
|
-
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
930
|
-
return positionalSpecArgument(args, 1, join2(cwd, DEFAULT_SPEC_NAME));
|
|
931
989
|
}
|
|
932
|
-
if (
|
|
933
|
-
|
|
990
|
+
if (!query) {
|
|
991
|
+
lines.push("");
|
|
992
|
+
lines.push(chalk.bold("Conversation"));
|
|
993
|
+
lines.push(` ${accent("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
|
|
994
|
+
lines.push(` ${accent("/new".padEnd(22))} Start a new agent conversation.`);
|
|
995
|
+
lines.push(` ${accent("/threads".padEnd(22))} List recent conversations.`);
|
|
996
|
+
lines.push(` ${accent("/resume <id>".padEnd(22))} Resume a conversation.`);
|
|
997
|
+
lines.push(` ${accent("/approve [id]".padEnd(22))} Approve a proposed action.`);
|
|
998
|
+
lines.push(` ${accent("/reject [id]".padEnd(22))} Reject a proposed action.`);
|
|
999
|
+
lines.push("");
|
|
1000
|
+
lines.push(chalk.bold("Session"));
|
|
1001
|
+
for (const command of SLASH_COMMANDS) {
|
|
1002
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
1003
|
+
}
|
|
1004
|
+
lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
|
|
1005
|
+
lines.push(chalk.dim(
|
|
1006
|
+
"\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
|
|
1007
|
+
));
|
|
934
1008
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const explicit = optionValueArgument(args, "--spec");
|
|
938
|
-
if (explicit) return explicit;
|
|
939
|
-
return null;
|
|
1009
|
+
return `${lines.join("\n")}
|
|
1010
|
+
`;
|
|
940
1011
|
}
|
|
941
|
-
|
|
942
|
-
return
|
|
1012
|
+
function activeModelLabel(snapshot) {
|
|
1013
|
+
return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
|
|
943
1014
|
}
|
|
944
|
-
function
|
|
945
|
-
|
|
946
|
-
return
|
|
1015
|
+
function logoRows() {
|
|
1016
|
+
const muted = chalk.dim("\u2588\u2588");
|
|
1017
|
+
return [
|
|
1018
|
+
`${chalk.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
|
|
1019
|
+
`${muted} ${accent("\u2588\u2588")} ${muted}`,
|
|
1020
|
+
`${muted} ${muted} ${chalk.hex("#7C3AED")("\u2588\u2588")}`
|
|
1021
|
+
];
|
|
947
1022
|
}
|
|
948
|
-
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1023
|
+
function renderShellBanner(snapshot) {
|
|
1024
|
+
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1025
|
+
const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
|
|
1026
|
+
const textRows = [
|
|
1027
|
+
heading,
|
|
1028
|
+
`${chalk.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
|
|
1029
|
+
chalk.dim("Ask TT anything \xB7 known commands run directly \xB7 /help for more")
|
|
1030
|
+
];
|
|
1031
|
+
const logo = logoRows();
|
|
1032
|
+
const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
|
|
1033
|
+
return `${lines.join("\n")}
|
|
1034
|
+
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
function renderShellPrompt(snapshot) {
|
|
1038
|
+
return `${accent("tt")} ${chalk.bold(snapshot.mode)} ${chalk.dim(snapshot.context.projectName)} \u203A `;
|
|
1039
|
+
}
|
|
1040
|
+
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
1041
|
+
constructor(runner, io, agent, env, contextProvider, version, initialContext) {
|
|
1042
|
+
this.runner = runner;
|
|
1043
|
+
this.io = io;
|
|
1044
|
+
this.agent = agent;
|
|
1045
|
+
this.env = env;
|
|
1046
|
+
this.contextProvider = contextProvider;
|
|
1047
|
+
this.version = version;
|
|
1048
|
+
this.mode = initialContext.inferredTarget;
|
|
1049
|
+
this.modeSource = initialContext.targetSource;
|
|
1050
|
+
this.cwd = initialContext.cwd;
|
|
1051
|
+
this.context = initialContext;
|
|
958
1052
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
};
|
|
1053
|
+
mode;
|
|
1054
|
+
modeSource;
|
|
1055
|
+
cwd;
|
|
1056
|
+
context;
|
|
1057
|
+
static async create(options) {
|
|
1058
|
+
const env = options.env ?? process.env;
|
|
1059
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
1060
|
+
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1061
|
+
const initialContext = await contextProvider({ cwd, env });
|
|
1062
|
+
return new _TunedTensorShellSession(
|
|
1063
|
+
options.runner,
|
|
1064
|
+
options.io,
|
|
1065
|
+
options.agent,
|
|
1066
|
+
env,
|
|
1067
|
+
contextProvider,
|
|
1068
|
+
options.version,
|
|
1069
|
+
initialContext
|
|
1070
|
+
);
|
|
968
1071
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1072
|
+
interruptAgent() {
|
|
1073
|
+
return this.agent?.interrupt() ?? false;
|
|
1074
|
+
}
|
|
1075
|
+
snapshot() {
|
|
971
1076
|
return {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1077
|
+
mode: this.mode,
|
|
1078
|
+
modeSource: this.modeSource,
|
|
1079
|
+
cwd: this.cwd,
|
|
1080
|
+
context: this.context,
|
|
1081
|
+
version: this.version
|
|
977
1082
|
};
|
|
978
1083
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
982
|
-
}
|
|
983
|
-
const body = raw;
|
|
984
|
-
const runRequestKeys = [
|
|
985
|
-
"run_id",
|
|
986
|
-
"behavior_spec_id",
|
|
987
|
-
"run_number",
|
|
988
|
-
"spec_snapshot"
|
|
989
|
-
].filter((key) => key in body);
|
|
990
|
-
if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
|
|
991
|
-
return {
|
|
992
|
-
args,
|
|
993
|
-
droppedSpecKeys: [],
|
|
994
|
-
pathReplacements: [],
|
|
995
|
-
cleanup: async () => {
|
|
996
|
-
}
|
|
997
|
-
};
|
|
1084
|
+
prompt() {
|
|
1085
|
+
return renderShellPrompt(this.snapshot());
|
|
998
1086
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1087
|
+
banner() {
|
|
1088
|
+
return renderShellBanner(this.snapshot());
|
|
1089
|
+
}
|
|
1090
|
+
async refreshContext() {
|
|
1091
|
+
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
1092
|
+
this.cwd = this.context.cwd;
|
|
1093
|
+
if (this.modeSource !== "session" && this.modeSource !== "environment") {
|
|
1094
|
+
this.mode = this.context.inferredTarget;
|
|
1095
|
+
this.modeSource = this.context.targetSource;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
writeLines(lines) {
|
|
1099
|
+
this.io.write(`${lines.join("\n")}
|
|
1100
|
+
`);
|
|
1101
|
+
}
|
|
1102
|
+
modelLines() {
|
|
1103
|
+
if (this.mode === "local") {
|
|
1104
|
+
return styleDetailLines([
|
|
1105
|
+
detailLine("Active model", this.context.local.activeModelId ?? "base"),
|
|
1106
|
+
detailLine("Change", "/model <id> to activate a verified local model")
|
|
1107
|
+
]);
|
|
1108
|
+
}
|
|
1109
|
+
return styleDetailLines([
|
|
1110
|
+
detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
|
|
1111
|
+
detailLine("Change", "edit base_model in tunedtensor.json, then run push")
|
|
1112
|
+
]);
|
|
1113
|
+
}
|
|
1114
|
+
async handleSlash(command) {
|
|
1115
|
+
switch (command.name) {
|
|
1116
|
+
case "palette":
|
|
1117
|
+
this.io.write(helpText(this.mode, void 0, true));
|
|
1118
|
+
return "continue";
|
|
1119
|
+
case "help":
|
|
1120
|
+
this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
|
|
1121
|
+
return "continue";
|
|
1122
|
+
case "status":
|
|
1123
|
+
assertNoArgs("status", command.args);
|
|
1124
|
+
await this.refreshContext();
|
|
1125
|
+
this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
|
|
1126
|
+
return "continue";
|
|
1127
|
+
case "context":
|
|
1128
|
+
assertNoArgs("context", command.args);
|
|
1129
|
+
await this.refreshContext();
|
|
1130
|
+
this.writeLines(styleDetailLines(
|
|
1131
|
+
formatShellContext(this.context, this.mode, this.modeSource)
|
|
1132
|
+
));
|
|
1133
|
+
return "continue";
|
|
1134
|
+
case "mode": {
|
|
1135
|
+
if (command.args.length === 0) {
|
|
1136
|
+
this.io.write(`Current workflow: ${this.mode}
|
|
1137
|
+
`);
|
|
1138
|
+
return "continue";
|
|
1139
|
+
}
|
|
1140
|
+
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1141
|
+
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1142
|
+
}
|
|
1143
|
+
this.mode = command.args[0];
|
|
1144
|
+
this.modeSource = "session";
|
|
1145
|
+
this.io.write(`${successMark()} Workflow switched to ${this.mode}.
|
|
1146
|
+
`);
|
|
1147
|
+
return "continue";
|
|
1006
1148
|
}
|
|
1007
|
-
|
|
1149
|
+
case "model": {
|
|
1150
|
+
if (command.args.length === 0) {
|
|
1151
|
+
await this.refreshContext();
|
|
1152
|
+
this.writeLines(this.modelLines());
|
|
1153
|
+
return "continue";
|
|
1154
|
+
}
|
|
1155
|
+
if (command.args.length !== 1) {
|
|
1156
|
+
throw new ShellParseError("Usage: /model [model-id]");
|
|
1157
|
+
}
|
|
1158
|
+
if (this.mode !== "local") {
|
|
1159
|
+
throw new ShellParseError(
|
|
1160
|
+
"Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
await this.runner({
|
|
1164
|
+
target: "local",
|
|
1165
|
+
args: ["models", "activate", command.args[0]],
|
|
1166
|
+
cwd: this.cwd
|
|
1167
|
+
});
|
|
1168
|
+
await this.refreshContext();
|
|
1169
|
+
return "continue";
|
|
1170
|
+
}
|
|
1171
|
+
case "clear":
|
|
1172
|
+
assertNoArgs("clear", command.args);
|
|
1173
|
+
this.io.clear();
|
|
1174
|
+
return "continue";
|
|
1175
|
+
case "cd": {
|
|
1176
|
+
if (command.args.length === 0) {
|
|
1177
|
+
this.io.write(`${this.cwd}
|
|
1178
|
+
`);
|
|
1179
|
+
return "continue";
|
|
1180
|
+
}
|
|
1181
|
+
if (command.args.length !== 1) {
|
|
1182
|
+
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
1183
|
+
}
|
|
1184
|
+
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
1185
|
+
const metadata = await stat2(nextDirectory).catch(() => null);
|
|
1186
|
+
if (!metadata?.isDirectory()) {
|
|
1187
|
+
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
1188
|
+
}
|
|
1189
|
+
this.cwd = nextDirectory;
|
|
1190
|
+
await this.refreshContext();
|
|
1191
|
+
this.io.write(`${successMark()} Directory: ${this.cwd}
|
|
1192
|
+
`);
|
|
1193
|
+
return "continue";
|
|
1194
|
+
}
|
|
1195
|
+
case "exit":
|
|
1196
|
+
assertNoArgs("exit", command.args);
|
|
1197
|
+
return "exit";
|
|
1198
|
+
}
|
|
1008
1199
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1200
|
+
async handleLine(input) {
|
|
1201
|
+
try {
|
|
1202
|
+
if (/^(exit|quit)$/i.test(input.trim())) return "exit";
|
|
1203
|
+
const trimmed = input.trim();
|
|
1204
|
+
if (trimmed.startsWith("/")) {
|
|
1205
|
+
const rawName = trimmed.slice(1).split(/\s+/, 1)[0].toLowerCase();
|
|
1206
|
+
if (AGENT_SLASH_COMMANDS.has(rawName)) {
|
|
1207
|
+
if (!this.agent) {
|
|
1208
|
+
throw new ShellParseError("The conversational agent is unavailable.");
|
|
1209
|
+
}
|
|
1210
|
+
await this.agent.handleLine(trimmed);
|
|
1211
|
+
await this.refreshContext();
|
|
1212
|
+
return "continue";
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const slash = parseSlashCommand(input);
|
|
1216
|
+
if (slash) return await this.handleSlash(slash);
|
|
1217
|
+
const explicitCommand = trimmed.startsWith(":") ? trimmed.slice(1).trimStart() : null;
|
|
1218
|
+
if (explicitCommand !== null && !explicitCommand) {
|
|
1219
|
+
throw new ShellParseError("Add a TT command after :.");
|
|
1220
|
+
}
|
|
1221
|
+
const explicitWorkflowPrefix = /^(?:tt\s+)?(?:cloud|local)(?:\s|$)/.test(trimmed) || /^tt(?:\s|$)/.test(trimmed);
|
|
1222
|
+
if (explicitCommand === null && !explicitWorkflowPrefix && this.agent && !hasCatalogIntent(input, this.mode)) {
|
|
1223
|
+
await this.agent.handleLine(input);
|
|
1224
|
+
await this.refreshContext();
|
|
1225
|
+
return "continue";
|
|
1226
|
+
}
|
|
1227
|
+
const commandInput = explicitCommand ?? input;
|
|
1228
|
+
const routed = routeShellCommand(commandInput, this.mode);
|
|
1229
|
+
if (!routed) return "continue";
|
|
1230
|
+
await this.runner({
|
|
1231
|
+
target: routed.target,
|
|
1232
|
+
args: [...routed.args],
|
|
1233
|
+
cwd: this.cwd
|
|
1234
|
+
});
|
|
1235
|
+
await this.refreshContext();
|
|
1236
|
+
return "continue";
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
this.io.writeError(`${errorMark()} ${errorMessage(error)}
|
|
1239
|
+
`);
|
|
1240
|
+
return "continue";
|
|
1241
|
+
}
|
|
1038
1242
|
}
|
|
1243
|
+
};
|
|
1244
|
+
async function createShellSession(options) {
|
|
1245
|
+
return TunedTensorShellSession.create(options);
|
|
1039
1246
|
}
|
|
1040
|
-
function
|
|
1041
|
-
|
|
1042
|
-
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
1247
|
+
function streamIsTTY(stream) {
|
|
1248
|
+
return Boolean(stream.isTTY);
|
|
1043
1249
|
}
|
|
1044
|
-
function
|
|
1045
|
-
|
|
1250
|
+
async function withForegroundSignalHandoff(control, run) {
|
|
1251
|
+
const signals = control.signals ?? process;
|
|
1252
|
+
const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
|
|
1253
|
+
const keepParentAlive = () => {
|
|
1254
|
+
};
|
|
1255
|
+
control.pauseReadline();
|
|
1256
|
+
if (restoreRawMode) control.input.setRawMode(false);
|
|
1257
|
+
signals.on("SIGINT", keepParentAlive);
|
|
1046
1258
|
try {
|
|
1047
|
-
return
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1259
|
+
return await run();
|
|
1260
|
+
} finally {
|
|
1261
|
+
signals.off("SIGINT", keepParentAlive);
|
|
1262
|
+
if (restoreRawMode) control.input.setRawMode(true);
|
|
1263
|
+
control.resumeReadline();
|
|
1050
1264
|
}
|
|
1051
1265
|
}
|
|
1052
|
-
function
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1266
|
+
function streamIO(output, error) {
|
|
1267
|
+
return {
|
|
1268
|
+
write(text2) {
|
|
1269
|
+
output.write(text2);
|
|
1270
|
+
},
|
|
1271
|
+
writeError(text2) {
|
|
1272
|
+
error.write(text2);
|
|
1273
|
+
},
|
|
1274
|
+
clear() {
|
|
1275
|
+
output.write("\x1B[2J\x1B[H");
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1059
1278
|
}
|
|
1060
|
-
function
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const encodedSourcePath = JSON.stringify(
|
|
1067
|
-
replacement.sourcePath
|
|
1068
|
-
).slice(1, -1);
|
|
1069
|
-
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
1279
|
+
async function startInteractiveShell(options) {
|
|
1280
|
+
const input = options.input ?? process.stdin;
|
|
1281
|
+
const output = options.output ?? process.stdout;
|
|
1282
|
+
const error = options.error ?? process.stderr;
|
|
1283
|
+
if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
|
|
1284
|
+
throw new Error("The interactive TT shell requires a TTY.");
|
|
1070
1285
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const output = args.stdout ?? process.stdout;
|
|
1075
|
-
const errors = args.stderr ?? process.stderr;
|
|
1076
|
-
const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
|
|
1077
|
-
const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
|
|
1078
|
-
const child = spawn(
|
|
1079
|
-
args.nodeExecutable,
|
|
1080
|
-
[args.entrypoint, ...args.commandArgs],
|
|
1286
|
+
let readline;
|
|
1287
|
+
const terminalInput = input;
|
|
1288
|
+
const foregroundRunner = (request2) => withForegroundSignalHandoff(
|
|
1081
1289
|
{
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
"pipe"
|
|
1088
|
-
],
|
|
1089
|
-
detached: process.platform !== "win32"
|
|
1090
|
-
}
|
|
1290
|
+
input: terminalInput,
|
|
1291
|
+
pauseReadline: () => readline?.pause(),
|
|
1292
|
+
resumeReadline: () => readline?.resume()
|
|
1293
|
+
},
|
|
1294
|
+
() => options.runner(request2)
|
|
1091
1295
|
);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
chunk,
|
|
1100
|
-
args.pathReplacements
|
|
1101
|
-
);
|
|
1102
|
-
if (args.streamingStdout) writeStream(output, normalized);
|
|
1103
|
-
else stdout2 += normalized;
|
|
1296
|
+
const session = await createShellSession({
|
|
1297
|
+
runner: foregroundRunner,
|
|
1298
|
+
io: streamIO(output, error),
|
|
1299
|
+
agent: options.agent,
|
|
1300
|
+
cwd: options.cwd,
|
|
1301
|
+
env: options.env,
|
|
1302
|
+
version: options.version
|
|
1104
1303
|
});
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
args.jsonMode ? normalized : adaptLocalCliText(normalized)
|
|
1114
|
-
);
|
|
1304
|
+
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
1305
|
+
readline = createInterface({
|
|
1306
|
+
input,
|
|
1307
|
+
output,
|
|
1308
|
+
terminal: true,
|
|
1309
|
+
historySize: 100,
|
|
1310
|
+
removeHistoryDuplicates: true,
|
|
1311
|
+
completer
|
|
1115
1312
|
});
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1313
|
+
readline.on("SIGINT", () => {
|
|
1314
|
+
if (session.interruptAgent()) {
|
|
1315
|
+
output.write("\n");
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
output.write("\n");
|
|
1319
|
+
readline?.setPrompt(session.prompt());
|
|
1320
|
+
readline?.write(null, { ctrl: true, name: "u" });
|
|
1321
|
+
});
|
|
1322
|
+
output.write(session.banner());
|
|
1323
|
+
readline.setPrompt(session.prompt());
|
|
1324
|
+
readline.prompt();
|
|
1325
|
+
try {
|
|
1326
|
+
for await (const line of readline) {
|
|
1327
|
+
const action = await session.handleLine(line);
|
|
1328
|
+
if (action === "exit") {
|
|
1329
|
+
readline.close();
|
|
1330
|
+
break;
|
|
1126
1331
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1332
|
+
readline.setPrompt(session.prompt());
|
|
1333
|
+
readline.prompt();
|
|
1334
|
+
}
|
|
1335
|
+
} finally {
|
|
1336
|
+
readline.close();
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function ciEnabled(value) {
|
|
1340
|
+
if (!value) return false;
|
|
1341
|
+
return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
1342
|
+
}
|
|
1343
|
+
function shouldStartInteractiveShell(options) {
|
|
1344
|
+
const env = options.env ?? process.env;
|
|
1345
|
+
return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// src/agent.ts
|
|
1349
|
+
var accent2 = chalk2.hex("#8B5CF6");
|
|
1350
|
+
var successMark2 = () => chalk2.green("\u2713");
|
|
1351
|
+
var errorMark2 = () => chalk2.red("\u2717");
|
|
1352
|
+
function isRecord(value) {
|
|
1353
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1354
|
+
}
|
|
1355
|
+
function stringValue2(value) {
|
|
1356
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1357
|
+
}
|
|
1358
|
+
function helpText2() {
|
|
1359
|
+
return [
|
|
1360
|
+
accent2.bold("Agent commands"),
|
|
1361
|
+
"",
|
|
1362
|
+
` ${accent2("/new".padEnd(22))} Start a new conversation.`,
|
|
1363
|
+
` ${accent2("/threads".padEnd(22))} List recent conversations.`,
|
|
1364
|
+
` ${accent2("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
|
|
1365
|
+
` ${accent2("/approve [id]".padEnd(22))} Approve a proposed action.`,
|
|
1366
|
+
` ${accent2("/reject [id]".padEnd(22))} Reject a proposed action.`,
|
|
1367
|
+
` ${accent2("/status".padEnd(22))} Show the active conversation.`,
|
|
1368
|
+
` ${accent2("/clear".padEnd(22))} Clear the terminal.`,
|
|
1369
|
+
` ${accent2("/exit".padEnd(22))} Exit the agent.`,
|
|
1370
|
+
"",
|
|
1371
|
+
chalk2.dim(
|
|
1372
|
+
"Everything else is sent to the Tuned Tensor agent; known TT commands still run directly in the shell."
|
|
1373
|
+
),
|
|
1374
|
+
""
|
|
1375
|
+
].join("\n");
|
|
1376
|
+
}
|
|
1377
|
+
function formatThread(thread, activeId) {
|
|
1378
|
+
const active = thread.id === activeId ? accent2("\u25CF") : chalk2.dim("\u25CB");
|
|
1379
|
+
const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
|
|
1380
|
+
const date = new Date(time);
|
|
1381
|
+
const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
|
|
1382
|
+
month: "short",
|
|
1383
|
+
day: "numeric",
|
|
1384
|
+
hour: "2-digit",
|
|
1385
|
+
minute: "2-digit"
|
|
1153
1386
|
});
|
|
1387
|
+
return `${active} ${chalk2.dim(thread.id.slice(0, 8))} ${thread.title} ${chalk2.dim(dateLabel)}`;
|
|
1154
1388
|
}
|
|
1155
|
-
function
|
|
1156
|
-
const
|
|
1389
|
+
function actionFromPayload(payload) {
|
|
1390
|
+
const value = isRecord(payload.action) ? payload.action : isRecord(payload.approval) ? payload.approval : payload;
|
|
1391
|
+
const id = stringValue2(value.id);
|
|
1392
|
+
const title = stringValue2(value.title);
|
|
1393
|
+
if (!id || !title) return null;
|
|
1157
1394
|
return {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1395
|
+
id,
|
|
1396
|
+
title,
|
|
1397
|
+
summary: stringValue2(value.summary) ?? "",
|
|
1398
|
+
risk: stringValue2(value.risk) ?? "unknown",
|
|
1399
|
+
status: stringValue2(value.status) ?? "proposed",
|
|
1400
|
+
operation: stringValue2(value.operation) ?? void 0,
|
|
1401
|
+
thread_id: stringValue2(value.thread_id) ?? void 0,
|
|
1402
|
+
turn_id: stringValue2(value.turn_id) ?? void 0,
|
|
1403
|
+
arguments: value.arguments,
|
|
1404
|
+
preview: value.preview,
|
|
1405
|
+
method: stringValue2(value.method) ?? void 0,
|
|
1406
|
+
path: stringValue2(value.path) ?? void 0
|
|
1170
1407
|
};
|
|
1171
1408
|
}
|
|
1172
|
-
|
|
1173
|
-
const cwd = resolve(options.cwd ?? process.cwd());
|
|
1174
|
-
let projected;
|
|
1409
|
+
function formatJson(value) {
|
|
1175
1410
|
try {
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1411
|
+
return JSON.stringify(value, null, 2);
|
|
1412
|
+
} catch {
|
|
1413
|
+
return String(value);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
var TunedTensorAgentSession = class {
|
|
1417
|
+
constructor(options) {
|
|
1418
|
+
this.options = options;
|
|
1419
|
+
this.thread = options.thread ?? null;
|
|
1420
|
+
}
|
|
1421
|
+
thread;
|
|
1422
|
+
pendingActions = /* @__PURE__ */ new Map();
|
|
1423
|
+
activeRequest = null;
|
|
1424
|
+
responseStarted = false;
|
|
1425
|
+
lineOpen = false;
|
|
1426
|
+
get busy() {
|
|
1427
|
+
return this.activeRequest !== null;
|
|
1428
|
+
}
|
|
1429
|
+
snapshot() {
|
|
1193
1430
|
return {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
entrypoint,
|
|
1197
|
-
...result,
|
|
1198
|
-
...parsed,
|
|
1199
|
-
streamingStdout,
|
|
1200
|
-
droppedSpecKeys: projected.droppedSpecKeys
|
|
1431
|
+
thread: this.thread,
|
|
1432
|
+
pendingActions: [...this.pendingActions.values()]
|
|
1201
1433
|
};
|
|
1202
|
-
} catch (error) {
|
|
1203
|
-
return setupFailure(args, error);
|
|
1204
|
-
} finally {
|
|
1205
|
-
await projected?.cleanup().catch(() => void 0);
|
|
1206
1434
|
}
|
|
1435
|
+
interrupt() {
|
|
1436
|
+
if (!this.activeRequest) return false;
|
|
1437
|
+
this.activeRequest.abort("user-stop");
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
endOpenLine() {
|
|
1441
|
+
if (this.lineOpen) this.options.io.write("\n");
|
|
1442
|
+
this.lineOpen = false;
|
|
1443
|
+
}
|
|
1444
|
+
renderAction(action) {
|
|
1445
|
+
this.endOpenLine();
|
|
1446
|
+
this.options.io.write(
|
|
1447
|
+
`
|
|
1448
|
+
${chalk2.yellow.bold("Approval required")} ${chalk2.dim(action.id.slice(0, 8))}
|
|
1449
|
+
`
|
|
1450
|
+
);
|
|
1451
|
+
this.options.io.write(`${chalk2.bold(action.title)} ${chalk2.yellow(`[${action.risk} risk]`)}
|
|
1452
|
+
`);
|
|
1453
|
+
if (action.summary) this.options.io.write(`${action.summary}
|
|
1454
|
+
`);
|
|
1455
|
+
const request2 = [action.method, action.path].filter(Boolean).join(" ");
|
|
1456
|
+
const technical = {
|
|
1457
|
+
operation: action.operation,
|
|
1458
|
+
...request2 ? { request: request2 } : {},
|
|
1459
|
+
arguments: action.arguments ?? null,
|
|
1460
|
+
preview: action.preview ?? null
|
|
1461
|
+
};
|
|
1462
|
+
this.options.io.write(`${chalk2.bold("Proposed action")}
|
|
1463
|
+
`);
|
|
1464
|
+
this.options.io.write(`${formatJson(technical)}
|
|
1465
|
+
`);
|
|
1466
|
+
this.options.io.write(
|
|
1467
|
+
chalk2.dim("Use /approve or /reject. The action will not run without approval.") + "\n"
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
renderEvent(event) {
|
|
1471
|
+
const payload = event.payload;
|
|
1472
|
+
if (event.type === "text_delta") {
|
|
1473
|
+
const delta = stringValue2(payload.delta) ?? stringValue2(payload.text) ?? stringValue2(payload.content);
|
|
1474
|
+
if (!delta) return;
|
|
1475
|
+
if (!this.responseStarted) {
|
|
1476
|
+
this.options.io.write(`${accent2.bold("TT")} `);
|
|
1477
|
+
this.responseStarted = true;
|
|
1478
|
+
this.lineOpen = true;
|
|
1479
|
+
}
|
|
1480
|
+
this.options.io.write(delta);
|
|
1481
|
+
this.lineOpen = !delta.endsWith("\n");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (event.type === "tool_call") {
|
|
1485
|
+
this.endOpenLine();
|
|
1486
|
+
const name = stringValue2(payload.name) ?? stringValue2(isRecord(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
1487
|
+
this.options.io.write(chalk2.dim(` \u25CB ${name}
|
|
1488
|
+
`));
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (event.type === "tool_result") {
|
|
1492
|
+
this.endOpenLine();
|
|
1493
|
+
const failed = payload.status === "error" || Boolean(payload.error);
|
|
1494
|
+
this.options.io.write(
|
|
1495
|
+
chalk2.dim(` ${failed ? chalk2.red("\u2717") : chalk2.green("\u2713")} Tool ${failed ? "failed" : "complete"}
|
|
1496
|
+
`)
|
|
1497
|
+
);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (event.type === "approval_required") {
|
|
1501
|
+
const action = actionFromPayload(payload);
|
|
1502
|
+
if (!action) return;
|
|
1503
|
+
this.pendingActions.set(action.id, action);
|
|
1504
|
+
this.renderAction(action);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
if (event.type === "action_started") {
|
|
1508
|
+
this.endOpenLine();
|
|
1509
|
+
this.options.io.write(chalk2.dim(" \u25CB Running approved action\u2026\n"));
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
if (event.type === "action_result") {
|
|
1513
|
+
this.endOpenLine();
|
|
1514
|
+
const failed = payload.status === "failed" || Boolean(payload.error);
|
|
1515
|
+
this.options.io.write(
|
|
1516
|
+
`${failed ? errorMark2() : successMark2()} Approved action ${failed ? "failed" : "completed"}.
|
|
1517
|
+
`
|
|
1518
|
+
);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
if (event.type === "error") {
|
|
1522
|
+
this.endOpenLine();
|
|
1523
|
+
const message = stringValue2(payload.message) ?? stringValue2(payload.error) ?? "The agent could not complete this request.";
|
|
1524
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1525
|
+
`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
async ensureThread() {
|
|
1529
|
+
if (!this.thread) this.thread = await this.options.client.createThread();
|
|
1530
|
+
return this.thread;
|
|
1531
|
+
}
|
|
1532
|
+
async runStream(operation) {
|
|
1533
|
+
if (this.activeRequest) {
|
|
1534
|
+
this.options.io.writeError(`${errorMark2()} A response is already running.
|
|
1535
|
+
`);
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const controller = new AbortController();
|
|
1539
|
+
this.activeRequest = controller;
|
|
1540
|
+
this.responseStarted = false;
|
|
1541
|
+
this.lineOpen = false;
|
|
1542
|
+
try {
|
|
1543
|
+
const result = await operation(controller.signal);
|
|
1544
|
+
this.endOpenLine();
|
|
1545
|
+
for (const action of result.actions) {
|
|
1546
|
+
this.pendingActions.set(action.id, action);
|
|
1547
|
+
}
|
|
1548
|
+
return result;
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
this.endOpenLine();
|
|
1551
|
+
if (controller.signal.aborted) {
|
|
1552
|
+
this.options.io.write(chalk2.dim("Response stopped.\n"));
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
throw error;
|
|
1556
|
+
} finally {
|
|
1557
|
+
this.activeRequest = null;
|
|
1558
|
+
this.responseStarted = false;
|
|
1559
|
+
this.lineOpen = false;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
async send(prompt) {
|
|
1563
|
+
const normalized = prompt.trim();
|
|
1564
|
+
if (!normalized) return null;
|
|
1565
|
+
const thread = await this.ensureThread();
|
|
1566
|
+
return await this.runStream(
|
|
1567
|
+
async (signal) => await this.options.client.runTurn(
|
|
1568
|
+
thread.id,
|
|
1569
|
+
normalized,
|
|
1570
|
+
(event) => this.renderEvent(event),
|
|
1571
|
+
signal
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
resolvePendingAction(idOrPrefix) {
|
|
1576
|
+
const candidates = [...this.pendingActions.values()].filter(
|
|
1577
|
+
(action) => idOrPrefix ? action.id.startsWith(idOrPrefix) : true
|
|
1578
|
+
);
|
|
1579
|
+
if (candidates.length === 0) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
idOrPrefix ? `No pending action matches ${idOrPrefix}.` : "There is no pending action."
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
if (candidates.length > 1) {
|
|
1585
|
+
throw new Error("More than one action is pending; include its ID or prefix.");
|
|
1586
|
+
}
|
|
1587
|
+
return candidates[0];
|
|
1588
|
+
}
|
|
1589
|
+
async approve(idOrPrefix) {
|
|
1590
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1591
|
+
const result = await this.runStream(
|
|
1592
|
+
async (signal) => await this.options.client.approveAction(
|
|
1593
|
+
action.id,
|
|
1594
|
+
(event) => this.renderEvent(event),
|
|
1595
|
+
signal
|
|
1596
|
+
)
|
|
1597
|
+
);
|
|
1598
|
+
if (result) this.pendingActions.delete(action.id);
|
|
1599
|
+
}
|
|
1600
|
+
async reject(idOrPrefix) {
|
|
1601
|
+
const action = this.resolvePendingAction(idOrPrefix);
|
|
1602
|
+
await this.options.client.rejectAction(action.id);
|
|
1603
|
+
this.pendingActions.delete(action.id);
|
|
1604
|
+
this.options.io.write(`${successMark2()} Action rejected.
|
|
1605
|
+
`);
|
|
1606
|
+
}
|
|
1607
|
+
async resolveThread(idOrPrefix) {
|
|
1608
|
+
const id = idOrPrefix.trim();
|
|
1609
|
+
if (!id) throw new Error("Usage: /resume <conversation-id>");
|
|
1610
|
+
if (id.length >= 32) return await this.options.client.getThread(id);
|
|
1611
|
+
const matches = (await this.options.client.listThreads()).filter((thread) => thread.id.startsWith(id));
|
|
1612
|
+
if (matches.length === 0) throw new Error(`No conversation matches ${id}.`);
|
|
1613
|
+
if (matches.length > 1) {
|
|
1614
|
+
throw new Error(`Conversation prefix ${id} is ambiguous.`);
|
|
1615
|
+
}
|
|
1616
|
+
return await this.options.client.getThread(matches[0].id);
|
|
1617
|
+
}
|
|
1618
|
+
async handleCommand(input) {
|
|
1619
|
+
const tokens = tokenizeShellInput(input);
|
|
1620
|
+
const command = tokens[0].slice(1).toLowerCase();
|
|
1621
|
+
const args = tokens.slice(1);
|
|
1622
|
+
switch (command) {
|
|
1623
|
+
case "help":
|
|
1624
|
+
this.options.io.write(helpText2());
|
|
1625
|
+
return "continue";
|
|
1626
|
+
case "new":
|
|
1627
|
+
if (args.length > 0) throw new Error("/new does not accept arguments.");
|
|
1628
|
+
this.thread = null;
|
|
1629
|
+
this.pendingActions.clear();
|
|
1630
|
+
this.options.io.write(`${successMark2()} New conversation ready.
|
|
1631
|
+
`);
|
|
1632
|
+
return "continue";
|
|
1633
|
+
case "threads": {
|
|
1634
|
+
if (args.length > 0) throw new Error("/threads does not accept arguments.");
|
|
1635
|
+
const threads = await this.options.client.listThreads();
|
|
1636
|
+
if (threads.length === 0) {
|
|
1637
|
+
this.options.io.write(chalk2.dim("No conversations yet.\n"));
|
|
1638
|
+
return "continue";
|
|
1639
|
+
}
|
|
1640
|
+
this.options.io.write(
|
|
1641
|
+
`${threads.slice(0, 20).map((thread) => formatThread(thread, this.thread?.id)).join("\n")}
|
|
1642
|
+
`
|
|
1643
|
+
);
|
|
1644
|
+
return "continue";
|
|
1645
|
+
}
|
|
1646
|
+
case "resume":
|
|
1647
|
+
if (args.length !== 1) throw new Error("Usage: /resume <conversation-id>");
|
|
1648
|
+
{
|
|
1649
|
+
const detail = await this.resolveThread(args[0]);
|
|
1650
|
+
this.thread = detail.thread;
|
|
1651
|
+
this.pendingActions.clear();
|
|
1652
|
+
for (const action of detail.actions) {
|
|
1653
|
+
if (action.status === "proposed") {
|
|
1654
|
+
this.pendingActions.set(action.id, action);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
this.options.io.write(
|
|
1659
|
+
`${successMark2()} Resumed ${this.thread.title} (${this.thread.id.slice(0, 8)}).
|
|
1660
|
+
`
|
|
1661
|
+
);
|
|
1662
|
+
return "continue";
|
|
1663
|
+
case "approve":
|
|
1664
|
+
if (args.length > 1) throw new Error("Usage: /approve [action-id]");
|
|
1665
|
+
await this.approve(args[0]);
|
|
1666
|
+
return "continue";
|
|
1667
|
+
case "reject":
|
|
1668
|
+
if (args.length > 1) throw new Error("Usage: /reject [action-id]");
|
|
1669
|
+
await this.reject(args[0]);
|
|
1670
|
+
return "continue";
|
|
1671
|
+
case "status":
|
|
1672
|
+
if (args.length > 0) throw new Error("/status does not accept arguments.");
|
|
1673
|
+
this.options.io.write(
|
|
1674
|
+
this.thread ? `${formatThread(this.thread, this.thread.id)}
|
|
1675
|
+
` : chalk2.dim("New conversation; send a message to create it.\n")
|
|
1676
|
+
);
|
|
1677
|
+
this.options.io.write(
|
|
1678
|
+
chalk2.dim(`${this.pendingActions.size} pending approval(s).
|
|
1679
|
+
`)
|
|
1680
|
+
);
|
|
1681
|
+
return "continue";
|
|
1682
|
+
case "clear":
|
|
1683
|
+
if (args.length > 0) throw new Error("/clear does not accept arguments.");
|
|
1684
|
+
this.options.io.clear();
|
|
1685
|
+
return "continue";
|
|
1686
|
+
case "exit":
|
|
1687
|
+
if (args.length > 0) throw new Error("/exit does not accept arguments.");
|
|
1688
|
+
return "exit";
|
|
1689
|
+
default:
|
|
1690
|
+
throw new Error(`Unknown agent command: /${command}. Use /help.`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
async handleLine(input) {
|
|
1694
|
+
try {
|
|
1695
|
+
const normalized = input.trim();
|
|
1696
|
+
if (!normalized) return "continue";
|
|
1697
|
+
if (/^(exit|quit)$/i.test(normalized)) return "exit";
|
|
1698
|
+
if (normalized.startsWith("/")) return await this.handleCommand(normalized);
|
|
1699
|
+
await this.send(normalized);
|
|
1700
|
+
return "continue";
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1703
|
+
this.options.io.writeError(`${errorMark2()} ${message}
|
|
1704
|
+
`);
|
|
1705
|
+
return "continue";
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
// src/local-runner.ts
|
|
1711
|
+
import { constants as osConstants } from "os";
|
|
1712
|
+
import { randomUUID } from "crypto";
|
|
1713
|
+
import { createRequire } from "module";
|
|
1714
|
+
import { spawn } from "child_process";
|
|
1715
|
+
import {
|
|
1716
|
+
readFile as readFile2,
|
|
1717
|
+
rm,
|
|
1718
|
+
stat as stat3,
|
|
1719
|
+
writeFile
|
|
1720
|
+
} from "fs/promises";
|
|
1721
|
+
import {
|
|
1722
|
+
basename as basename3,
|
|
1723
|
+
dirname as dirname2,
|
|
1724
|
+
isAbsolute as isAbsolute3,
|
|
1725
|
+
join as join3,
|
|
1726
|
+
resolve as resolve3
|
|
1727
|
+
} from "path";
|
|
1728
|
+
|
|
1729
|
+
// src/base-models.ts
|
|
1730
|
+
var SUPPORTED_BASE_MODELS = [
|
|
1731
|
+
"google/gemma-4-E2B-it",
|
|
1732
|
+
"google/gemma-4-E4B-it",
|
|
1733
|
+
"Qwen/Qwen3.5-2B",
|
|
1734
|
+
"Qwen/Qwen3-VL-2B-Instruct",
|
|
1735
|
+
"Qwen/Qwen3.5-4B",
|
|
1736
|
+
"meta-llama/Llama-3.2-3B-Instruct",
|
|
1737
|
+
"microsoft/Phi-4-mini-instruct",
|
|
1738
|
+
"ibm-granite/granite-3.3-2b-instruct",
|
|
1739
|
+
"bigcode/starcoder2-3b"
|
|
1740
|
+
];
|
|
1741
|
+
var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
|
|
1742
|
+
for (const model of SUPPORTED_BASE_MODELS) {
|
|
1743
|
+
BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
|
|
1744
|
+
}
|
|
1745
|
+
for (const [alias, model] of [
|
|
1746
|
+
["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
|
|
1747
|
+
["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
|
|
1748
|
+
["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
|
|
1749
|
+
["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
|
|
1750
|
+
["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
|
|
1751
|
+
["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
|
|
1752
|
+
["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
|
|
1753
|
+
["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
|
|
1754
|
+
["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
|
|
1755
|
+
["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1756
|
+
["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1757
|
+
["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
1758
|
+
["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
|
|
1759
|
+
["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
|
|
1760
|
+
["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
|
|
1761
|
+
["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1762
|
+
["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1763
|
+
["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
1764
|
+
["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1765
|
+
["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
1766
|
+
["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1767
|
+
["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
1768
|
+
["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
|
|
1769
|
+
["starcoder2-3b", "bigcode/starcoder2-3b"]
|
|
1770
|
+
]) {
|
|
1771
|
+
BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
|
|
1772
|
+
}
|
|
1773
|
+
var UnsupportedBaseModelError = class extends Error {
|
|
1774
|
+
constructor(model) {
|
|
1775
|
+
const supported = SUPPORTED_BASE_MODELS.join(", ");
|
|
1776
|
+
super(
|
|
1777
|
+
`Unsupported base_model "${String(model)}". Supported base models: ${supported}`
|
|
1778
|
+
);
|
|
1779
|
+
this.name = "UnsupportedBaseModelError";
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
function canonicalizeBaseModel(model) {
|
|
1783
|
+
if (typeof model !== "string" || !model.trim()) {
|
|
1784
|
+
throw new UnsupportedBaseModelError(model);
|
|
1785
|
+
}
|
|
1786
|
+
const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
|
|
1787
|
+
if (!canonical) {
|
|
1788
|
+
throw new UnsupportedBaseModelError(model);
|
|
1789
|
+
}
|
|
1790
|
+
return canonical;
|
|
1207
1791
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
});
|
|
1214
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
1215
|
-
return result;
|
|
1792
|
+
function canonicalizeSpecBaseModel(body) {
|
|
1793
|
+
if ("base_model" in body && body.base_model !== void 0) {
|
|
1794
|
+
return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
|
|
1795
|
+
}
|
|
1796
|
+
return body;
|
|
1216
1797
|
}
|
|
1217
1798
|
|
|
1218
|
-
// src/
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
var BOTH = ["cloud", "local"];
|
|
1229
|
-
var COMMAND_CATALOG = [
|
|
1230
|
-
{ path: "init", description: "Create a behaviour-spec project.", group: "Workflow", modes: BOTH },
|
|
1231
|
-
{ path: "eval", description: "Validate a managed-service behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1232
|
-
{ path: "push", description: "Create or update the cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1233
|
-
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
1234
|
-
{ path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
|
|
1235
|
-
{ path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
|
|
1236
|
-
{ path: "runs list", description: "List runs.", group: "Inspect", modes: BOTH },
|
|
1237
|
-
{ path: "runs get", description: "Show a run.", group: "Inspect", modes: BOTH },
|
|
1238
|
-
{ path: "runs report", description: "Show a run report.", group: "Inspect", modes: BOTH },
|
|
1239
|
-
{ path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
|
|
1240
|
-
{ path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
|
|
1241
|
-
{ path: "runs estimate", description: "Estimate managed run cost and duration.", group: "Workflow", modes: CLOUD },
|
|
1242
|
-
{ path: "runs start", description: "Start a managed fine-tuning run.", group: "Workflow", modes: CLOUD },
|
|
1243
|
-
{ path: "runs watch", description: "Watch a managed run.", group: "Inspect", modes: CLOUD },
|
|
1244
|
-
{ path: "runs diagnose", description: "Show managed run diagnostics.", group: "Inspect", modes: CLOUD },
|
|
1245
|
-
{ path: "runs cancel", description: "Cancel a managed run.", group: "Workflow", modes: CLOUD },
|
|
1246
|
-
{ path: "models list", description: "List models.", group: "Inspect", modes: BOTH },
|
|
1247
|
-
{ path: "models get", description: "Show a model.", group: "Inspect", modes: BOTH },
|
|
1248
|
-
{ path: "models base", description: "List managed-service base models.", group: "Inspect", modes: CLOUD },
|
|
1249
|
-
{ path: "models download", description: "Download a managed model artifact.", group: "Serving", modes: CLOUD },
|
|
1250
|
-
{ path: "models export", description: "Export a managed model to GGUF or Ollama.", group: "Serving", modes: CLOUD },
|
|
1251
|
-
{ path: "models setup-runtime", description: "Install the managed model-serving runtime.", group: "Serving", modes: CLOUD },
|
|
1252
|
-
{ path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: BOTH },
|
|
1253
|
-
{ path: "models delete", description: "Delete a managed model.", group: "Workflow", modes: CLOUD },
|
|
1254
|
-
{ path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
|
|
1255
|
-
{ path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
1256
|
-
{ path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
1257
|
-
{ path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
|
|
1258
|
-
{ path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
|
|
1259
|
-
{ path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
|
|
1260
|
-
{ path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
|
|
1261
|
-
{ path: "specs list", description: "List cloud behaviour specs.", group: "Inspect", modes: CLOUD },
|
|
1262
|
-
{ path: "specs get", description: "Show a cloud behaviour spec.", group: "Inspect", modes: CLOUD },
|
|
1263
|
-
{ path: "specs create", description: "Create a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1264
|
-
{ path: "specs update", description: "Update a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1265
|
-
{ path: "specs delete", description: "Delete a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1266
|
-
{ path: "datasets list", description: "List cloud datasets.", group: "Data", modes: CLOUD },
|
|
1267
|
-
{ path: "datasets get", description: "Show a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1268
|
-
{ path: "datasets upload", description: "Upload a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1269
|
-
{ path: "datasets delete", description: "Delete a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1270
|
-
{ path: "label upload", description: "Start a cloud teacher-labeling job.", group: "Data", modes: CLOUD },
|
|
1271
|
-
{ path: "label watch", description: "Watch a cloud labeling job.", group: "Data", modes: CLOUD },
|
|
1272
|
-
{ path: "label list", description: "List cloud labeling jobs.", group: "Data", modes: CLOUD },
|
|
1273
|
-
{ path: "label status", description: "Show cloud labeling-job status.", group: "Data", modes: CLOUD },
|
|
1274
|
-
{ path: "label rows", description: "Review labeled rows.", group: "Data", modes: CLOUD },
|
|
1275
|
-
{ path: "label accept", description: "Accept labeled rows.", group: "Data", modes: CLOUD },
|
|
1276
|
-
{ path: "label reject", description: "Reject labeled rows.", group: "Data", modes: CLOUD },
|
|
1277
|
-
{ path: "label edit", description: "Edit a labeled row.", group: "Data", modes: CLOUD },
|
|
1278
|
-
{ path: "label promote", description: "Promote reviewed rows to a dataset.", group: "Data", modes: CLOUD },
|
|
1279
|
-
{ path: "label cancel", description: "Cancel a labeling job.", group: "Data", modes: CLOUD },
|
|
1280
|
-
{ path: "auth login", description: "Store a cloud API key.", group: "Account", modes: CLOUD },
|
|
1281
|
-
{ path: "auth logout", description: "Remove stored cloud credentials.", group: "Account", modes: CLOUD },
|
|
1282
|
-
{ path: "auth status", description: "Show cloud authentication status.", group: "Account", modes: CLOUD },
|
|
1283
|
-
{ path: "balance", description: "Show cloud credit balance.", group: "Account", modes: CLOUD },
|
|
1284
|
-
{ path: "topup", description: "Open a cloud credit checkout.", group: "Account", modes: CLOUD },
|
|
1285
|
-
{ path: "info", description: "Show TT Local package information.", group: "Inspect", modes: LOCAL }
|
|
1799
|
+
// src/project-spec.ts
|
|
1800
|
+
var CLOUD_SPEC_KEYS = [
|
|
1801
|
+
"name",
|
|
1802
|
+
"description",
|
|
1803
|
+
"base_model",
|
|
1804
|
+
"system_prompt",
|
|
1805
|
+
"guidelines",
|
|
1806
|
+
"constraints",
|
|
1807
|
+
"examples",
|
|
1808
|
+
"eval_cases"
|
|
1286
1809
|
];
|
|
1287
|
-
var
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
{ path: "/context", description: "Show the current project and backend context." },
|
|
1291
|
-
{ path: "/mode cloud", description: "Use the managed cloud workflow." },
|
|
1292
|
-
{ path: "/mode local", description: "Use the local GPU workflow." },
|
|
1293
|
-
{ path: "/cd", description: "Change the shell's working directory." },
|
|
1294
|
-
{ path: "/clear", description: "Clear the terminal." },
|
|
1295
|
-
{ path: "/exit", description: "Exit the TT shell." }
|
|
1810
|
+
var LOCAL_ONLY_SPEC_KEYS = [
|
|
1811
|
+
"hyperparameters",
|
|
1812
|
+
"dataset_prebuilt"
|
|
1296
1813
|
];
|
|
1297
|
-
|
|
1298
|
-
|
|
1814
|
+
var LOCAL_SPEC_KEYS = [
|
|
1815
|
+
"id",
|
|
1816
|
+
"name",
|
|
1817
|
+
"description",
|
|
1818
|
+
"system_prompt",
|
|
1819
|
+
"guidelines",
|
|
1820
|
+
"constraints",
|
|
1821
|
+
"base_model",
|
|
1822
|
+
"examples",
|
|
1823
|
+
...LOCAL_ONLY_SPEC_KEYS
|
|
1824
|
+
];
|
|
1825
|
+
var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
|
|
1826
|
+
var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
|
|
1827
|
+
var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
|
|
1828
|
+
var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
|
|
1829
|
+
...CLOUD_SPEC_KEYS,
|
|
1830
|
+
...LOCAL_SPEC_KEYS
|
|
1831
|
+
]);
|
|
1832
|
+
function projectSpec(raw, supportedKeys) {
|
|
1833
|
+
const body = {};
|
|
1834
|
+
const droppedKeys = [];
|
|
1835
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
1836
|
+
if (supportedKeys.has(key)) {
|
|
1837
|
+
body[key] = value;
|
|
1838
|
+
} else {
|
|
1839
|
+
droppedKeys.push(key);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return {
|
|
1843
|
+
body: canonicalizeSpecBaseModel(body),
|
|
1844
|
+
droppedKeys
|
|
1845
|
+
};
|
|
1299
1846
|
}
|
|
1300
|
-
function
|
|
1301
|
-
return
|
|
1847
|
+
function projectCloudSpec(raw) {
|
|
1848
|
+
return projectSpec(raw, CLOUD_SPEC_KEY_SET);
|
|
1302
1849
|
}
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1850
|
+
function projectLocalSpec(raw) {
|
|
1851
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET);
|
|
1852
|
+
}
|
|
1853
|
+
function hasLocalOnlySpecFields(raw) {
|
|
1854
|
+
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
1855
|
+
}
|
|
1856
|
+
function unknownProjectSpecKeys(raw) {
|
|
1857
|
+
return Object.keys(raw).filter(
|
|
1858
|
+
(key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// src/output.ts
|
|
1863
|
+
import chalk3 from "chalk";
|
|
1864
|
+
import Table from "cli-table3";
|
|
1865
|
+
var jsonMode = false;
|
|
1866
|
+
function setJsonMode(enabled) {
|
|
1867
|
+
jsonMode = enabled;
|
|
1868
|
+
}
|
|
1869
|
+
function isJsonMode() {
|
|
1870
|
+
return jsonMode;
|
|
1871
|
+
}
|
|
1872
|
+
function printJson(data) {
|
|
1873
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1874
|
+
}
|
|
1875
|
+
function printTable(headers, rows2, meta) {
|
|
1876
|
+
const table = new Table({
|
|
1877
|
+
head: headers.map((h) => chalk3.bold.cyan(h)),
|
|
1878
|
+
style: { head: [], border: [] }
|
|
1308
1879
|
});
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1880
|
+
for (const row of rows2) {
|
|
1881
|
+
table.push(row);
|
|
1882
|
+
}
|
|
1883
|
+
console.log(table.toString());
|
|
1884
|
+
if (meta) {
|
|
1885
|
+
const totalPages = Math.ceil(meta.total / meta.per_page);
|
|
1886
|
+
console.log(
|
|
1887
|
+
chalk3.dim(
|
|
1888
|
+
`
|
|
1889
|
+
Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
1890
|
+
)
|
|
1891
|
+
);
|
|
1314
1892
|
}
|
|
1315
|
-
return groups;
|
|
1316
1893
|
}
|
|
1317
|
-
function
|
|
1318
|
-
const
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
1894
|
+
function printDetail(fields) {
|
|
1895
|
+
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
1896
|
+
for (const [label, value] of fields) {
|
|
1897
|
+
const paddedLabel = label.padEnd(maxLabel);
|
|
1898
|
+
console.log(`${chalk3.bold.cyan(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
|
|
1323
1899
|
}
|
|
1324
|
-
const mode = override[1];
|
|
1325
|
-
const prefix = `${leadingWhitespace}${mode} `;
|
|
1326
|
-
return {
|
|
1327
|
-
prefix,
|
|
1328
|
-
mode,
|
|
1329
|
-
fragment: content.slice(override[0].length)
|
|
1330
|
-
};
|
|
1331
1900
|
}
|
|
1332
|
-
function
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
1901
|
+
function printSuccess(message) {
|
|
1902
|
+
console.log(chalk3.green("\u2713") + " " + message);
|
|
1903
|
+
}
|
|
1904
|
+
function printWarning(message) {
|
|
1905
|
+
console.log(chalk3.yellow("!") + " " + message);
|
|
1906
|
+
}
|
|
1907
|
+
function printError(message) {
|
|
1908
|
+
console.error(chalk3.red("\u2717") + " " + message);
|
|
1909
|
+
}
|
|
1910
|
+
function reportError(err) {
|
|
1911
|
+
const isApi = err instanceof ApiError;
|
|
1912
|
+
const message = err?.message || String(err);
|
|
1913
|
+
if (jsonMode) {
|
|
1914
|
+
printJson({
|
|
1915
|
+
error: {
|
|
1916
|
+
status: isApi ? err.status : null,
|
|
1917
|
+
code: isApi ? err.code : "CLI_ERROR",
|
|
1918
|
+
message
|
|
1919
|
+
}
|
|
1352
1920
|
});
|
|
1353
|
-
|
|
1921
|
+
} else if (isApi) {
|
|
1922
|
+
printError(`[${err.status}] ${message}`);
|
|
1923
|
+
} else {
|
|
1924
|
+
printError(message);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function formatDate(iso) {
|
|
1928
|
+
if (!iso) return "\u2014";
|
|
1929
|
+
return new Date(iso).toLocaleString();
|
|
1930
|
+
}
|
|
1931
|
+
function formatStatus(status2) {
|
|
1932
|
+
const map = {
|
|
1933
|
+
completed: chalk3.green,
|
|
1934
|
+
running: chalk3.blue,
|
|
1935
|
+
training: chalk3.blue,
|
|
1936
|
+
evaluating: chalk3.blue,
|
|
1937
|
+
preparing: chalk3.yellow,
|
|
1938
|
+
pending: chalk3.yellow,
|
|
1939
|
+
uploading: chalk3.yellow,
|
|
1940
|
+
failed: chalk3.red,
|
|
1941
|
+
cancelled: chalk3.dim,
|
|
1942
|
+
validated: chalk3.green,
|
|
1943
|
+
invalid: chalk3.red
|
|
1354
1944
|
};
|
|
1945
|
+
const colorFn = map[status2] || chalk3.white;
|
|
1946
|
+
return colorFn(status2);
|
|
1947
|
+
}
|
|
1948
|
+
function truncate(str, max) {
|
|
1949
|
+
if (str.length <= max) return str;
|
|
1950
|
+
return str.slice(0, max - 1) + "\u2026";
|
|
1951
|
+
}
|
|
1952
|
+
function shortId(id) {
|
|
1953
|
+
return id.slice(0, 8);
|
|
1355
1954
|
}
|
|
1356
1955
|
|
|
1357
|
-
// src/
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
return { found: true, invalid: true };
|
|
1366
|
-
}
|
|
1367
|
-
return {
|
|
1368
|
-
found: true,
|
|
1369
|
-
value: parsed,
|
|
1370
|
-
invalid: false
|
|
1371
|
-
};
|
|
1372
|
-
} catch (error) {
|
|
1373
|
-
if (error.code === "ENOENT") {
|
|
1374
|
-
return { found: false, invalid: false };
|
|
1375
|
-
}
|
|
1376
|
-
return { found: true, invalid: true };
|
|
1956
|
+
// src/local-output.ts
|
|
1957
|
+
function isRecord2(value) {
|
|
1958
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1959
|
+
}
|
|
1960
|
+
function text(value) {
|
|
1961
|
+
if (typeof value === "string") return value;
|
|
1962
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
1963
|
+
return String(value);
|
|
1377
1964
|
}
|
|
1965
|
+
return void 0;
|
|
1378
1966
|
}
|
|
1379
|
-
function
|
|
1380
|
-
|
|
1381
|
-
return typeof field === "string" && field.trim() ? field : void 0;
|
|
1967
|
+
function number(value) {
|
|
1968
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1382
1969
|
}
|
|
1383
|
-
function
|
|
1384
|
-
|
|
1385
|
-
if (value.startsWith("~/")) return resolve2(homeDirectory, value.slice(2));
|
|
1386
|
-
return isAbsolute2(value) ? resolve2(value) : resolve2(baseDirectory, value);
|
|
1970
|
+
function record2(value) {
|
|
1971
|
+
return isRecord2(value) ? value : void 0;
|
|
1387
1972
|
}
|
|
1388
|
-
function
|
|
1389
|
-
return
|
|
1973
|
+
function rows(value) {
|
|
1974
|
+
return Array.isArray(value) ? value.filter(isRecord2) : [];
|
|
1390
1975
|
}
|
|
1391
|
-
function
|
|
1392
|
-
const
|
|
1393
|
-
return
|
|
1976
|
+
function displayId(value) {
|
|
1977
|
+
const id = text(value);
|
|
1978
|
+
return id ? shortId(id) : "\u2014";
|
|
1394
1979
|
}
|
|
1395
|
-
function
|
|
1396
|
-
|
|
1397
|
-
if (
|
|
1398
|
-
|
|
1399
|
-
return `${key.slice(0, prefixLength)}\u2026`;
|
|
1980
|
+
function displayNumber(value, digits = 4) {
|
|
1981
|
+
const parsed = number(value);
|
|
1982
|
+
if (parsed === void 0) return void 0;
|
|
1983
|
+
return parsed.toFixed(digits).replace(/\.?0+$/, "");
|
|
1400
1984
|
}
|
|
1401
|
-
function
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1985
|
+
function displayBytes(value) {
|
|
1986
|
+
const bytes = number(value);
|
|
1987
|
+
if (bytes === void 0 || bytes < 0) return void 0;
|
|
1988
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
1989
|
+
let amount = bytes;
|
|
1990
|
+
let unit = 0;
|
|
1991
|
+
while (amount >= 1024 && unit < units.length - 1) {
|
|
1992
|
+
amount /= 1024;
|
|
1993
|
+
unit += 1;
|
|
1994
|
+
}
|
|
1995
|
+
const precision = amount >= 10 || unit === 0 ? 0 : 1;
|
|
1996
|
+
return `${amount.toFixed(precision)} ${units[unit]}`;
|
|
1404
1997
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
return
|
|
1408
|
-
} catch {
|
|
1409
|
-
return false;
|
|
1998
|
+
function displayCommand(value) {
|
|
1999
|
+
if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
|
|
2000
|
+
return void 0;
|
|
1410
2001
|
}
|
|
2002
|
+
return value.map(
|
|
2003
|
+
(part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
|
|
2004
|
+
).join(" ");
|
|
1411
2005
|
}
|
|
1412
|
-
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1415
|
-
const modelId = result.value?.model_id;
|
|
1416
|
-
if (modelId === null) return "base";
|
|
1417
|
-
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
2006
|
+
function status(value) {
|
|
2007
|
+
const parsed = text(value) ?? "unknown";
|
|
2008
|
+
return formatStatus(parsed);
|
|
1418
2009
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
2010
|
+
function gateFrom(value) {
|
|
2011
|
+
return record2(record2(value)?.general_regression);
|
|
2012
|
+
}
|
|
2013
|
+
function renderGate(value) {
|
|
2014
|
+
const gate = gateFrom(value);
|
|
2015
|
+
if (!gate) {
|
|
2016
|
+
printWarning(
|
|
2017
|
+
"General regression was not run; this model cannot be activated yet."
|
|
2018
|
+
);
|
|
2019
|
+
return;
|
|
1426
2020
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}));
|
|
1439
|
-
return states.filter((state) => Boolean(state)).sort((left, right) => {
|
|
1440
|
-
const leftUpdated = left.updatedAt ?? "";
|
|
1441
|
-
const rightUpdated = right.updatedAt ?? "";
|
|
1442
|
-
if (leftUpdated !== rightUpdated) {
|
|
1443
|
-
return rightUpdated.localeCompare(leftUpdated);
|
|
1444
|
-
}
|
|
1445
|
-
return right.id.localeCompare(left.id);
|
|
1446
|
-
})[0];
|
|
2021
|
+
if (gate.passed === true) {
|
|
2022
|
+
printSuccess("General regression gate passed");
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
printWarning(
|
|
2026
|
+
"The local run completed, but the general regression gate failed."
|
|
2027
|
+
);
|
|
2028
|
+
const failures = Array.isArray(gate.failures) ? gate.failures.filter(
|
|
2029
|
+
(failure) => typeof failure === "string"
|
|
2030
|
+
) : [];
|
|
2031
|
+
for (const failure of failures) printWarning(failure);
|
|
1447
2032
|
}
|
|
1448
|
-
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
spec = {
|
|
1463
|
-
path: specPath,
|
|
1464
|
-
id: stringField(specJson.value, "id"),
|
|
1465
|
-
name: stringField(specJson.value, "name"),
|
|
1466
|
-
baseModel: stringField(specJson.value, "base_model"),
|
|
1467
|
-
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
1468
|
-
parseError: false
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
2033
|
+
function renderDoctor(value) {
|
|
2034
|
+
const checks = rows(value.checks);
|
|
2035
|
+
printTable(
|
|
2036
|
+
["Check", "Status", "Details"],
|
|
2037
|
+
checks.map((check) => [
|
|
2038
|
+
text(check.name) ?? "unknown",
|
|
2039
|
+
check.ok === true ? formatStatus("completed") : formatStatus("failed"),
|
|
2040
|
+
truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
|
|
2041
|
+
])
|
|
2042
|
+
);
|
|
2043
|
+
if (value.ok === true) {
|
|
2044
|
+
printSuccess("Local environment is ready");
|
|
2045
|
+
} else {
|
|
2046
|
+
printError("Local environment needs attention");
|
|
1471
2047
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
1475
|
-
if (localConfigJson.invalid) {
|
|
1476
|
-
warnings.push("local-runner.json could not be parsed.");
|
|
2048
|
+
if (value.config_path) {
|
|
2049
|
+
printDetail([["Config", text(value.config_path)]]);
|
|
1477
2050
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
2051
|
+
}
|
|
2052
|
+
function renderInit(value) {
|
|
2053
|
+
printSuccess("Created local Tuned Tensor project");
|
|
2054
|
+
printDetail([
|
|
2055
|
+
["Path", text(value.path)],
|
|
2056
|
+
["Name", text(value.name)],
|
|
2057
|
+
["ID", text(value.id)],
|
|
2058
|
+
["Base model", text(value.base_model)],
|
|
2059
|
+
["Config", text(value.config_path)]
|
|
2060
|
+
]);
|
|
2061
|
+
}
|
|
2062
|
+
function renderValidate(value) {
|
|
2063
|
+
printSuccess("Local behavior spec is valid");
|
|
2064
|
+
printDetail([
|
|
2065
|
+
["Input", text(value.input_path)],
|
|
2066
|
+
["Config", text(value.config_path)],
|
|
2067
|
+
["Spec", text(value.behavior_spec_id)],
|
|
2068
|
+
["Base model", text(value.base_model)],
|
|
2069
|
+
["Dataset", text(value.dataset_format)],
|
|
2070
|
+
["Artifacts", text(value.artifact_root)],
|
|
2071
|
+
["Store", text(value.store_root)],
|
|
2072
|
+
["Dry run", text(value.dry_run)]
|
|
2073
|
+
]);
|
|
2074
|
+
}
|
|
2075
|
+
function renderRun(value) {
|
|
2076
|
+
const runStatus = text(value.status) ?? "unknown";
|
|
2077
|
+
if (runStatus === "completed") {
|
|
2078
|
+
printSuccess("Local run completed");
|
|
2079
|
+
} else {
|
|
2080
|
+
printWarning(`Local run finished with status ${runStatus}`);
|
|
1487
2081
|
}
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
2082
|
+
const comparison = record2(value.comparison);
|
|
2083
|
+
printDetail([
|
|
2084
|
+
["Run", text(value.run_id)],
|
|
2085
|
+
["Status", status(runStatus)],
|
|
2086
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2087
|
+
["Report", text(value.report_path)],
|
|
2088
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2089
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2090
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)]
|
|
2091
|
+
]);
|
|
2092
|
+
renderGate(value);
|
|
2093
|
+
}
|
|
2094
|
+
function renderRunsList(value) {
|
|
2095
|
+
const runRows = rows(value);
|
|
2096
|
+
if (runRows.length === 0) {
|
|
2097
|
+
printWarning("No local runs found.");
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
printTable(
|
|
2101
|
+
["Run", "Spec", "Status", "Stage", "Model", "Updated"],
|
|
2102
|
+
runRows.map((run) => [
|
|
2103
|
+
displayId(run.id),
|
|
2104
|
+
truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
|
|
2105
|
+
status(run.status),
|
|
2106
|
+
text(run.current_stage) ?? "\u2014",
|
|
2107
|
+
displayId(run.model_id),
|
|
2108
|
+
formatDate(text(run.updated_at))
|
|
2109
|
+
])
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
function renderRunEvents(value) {
|
|
2113
|
+
const eventRows = rows(value);
|
|
2114
|
+
if (eventRows.length === 0) {
|
|
2115
|
+
printWarning("No local run events found.");
|
|
2116
|
+
return;
|
|
1495
2117
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2118
|
+
printTable(
|
|
2119
|
+
["Time", "Stage", "Status", "Message"],
|
|
2120
|
+
eventRows.map((event) => [
|
|
2121
|
+
formatDate(text(event.occurred_at)),
|
|
2122
|
+
text(event.stage) ?? "\u2014",
|
|
2123
|
+
status(event.status),
|
|
2124
|
+
truncate(text(event.message) ?? "\u2014", 80)
|
|
2125
|
+
])
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
function renderRunRecord(value) {
|
|
2129
|
+
printDetail([
|
|
2130
|
+
["Run", text(value.id) ?? text(value.run_id)],
|
|
2131
|
+
["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
|
|
2132
|
+
["Status", status(value.status)],
|
|
2133
|
+
["Stage", text(value.current_stage)],
|
|
2134
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
2135
|
+
["Base model", text(value.base_model)],
|
|
2136
|
+
["Report", text(value.report_path)],
|
|
2137
|
+
["Artifacts", text(value.artifact_dir)],
|
|
2138
|
+
["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
|
|
1501
2139
|
]);
|
|
1502
|
-
return {
|
|
1503
|
-
cwd,
|
|
1504
|
-
projectName: basename3(cwd) || cwd,
|
|
1505
|
-
inferredTarget,
|
|
1506
|
-
targetSource,
|
|
1507
|
-
spec,
|
|
1508
|
-
cloud: {
|
|
1509
|
-
authenticated: Boolean(apiKey),
|
|
1510
|
-
keyPrefix: redactApiKey(apiKey),
|
|
1511
|
-
baseUrl,
|
|
1512
|
-
configPath: cloudPath,
|
|
1513
|
-
configFound: cloudConfigJson.found
|
|
1514
|
-
},
|
|
1515
|
-
local: {
|
|
1516
|
-
configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
|
|
1517
|
-
artifactRoot,
|
|
1518
|
-
storeRoot,
|
|
1519
|
-
activeModelId,
|
|
1520
|
-
latestRun
|
|
1521
|
-
},
|
|
1522
|
-
warnings
|
|
1523
|
-
};
|
|
1524
2140
|
}
|
|
1525
|
-
function
|
|
1526
|
-
|
|
2141
|
+
function renderRunReport(value) {
|
|
2142
|
+
const baseline = record2(value.baseline);
|
|
2143
|
+
const candidate = record2(value.candidate);
|
|
2144
|
+
const comparison = record2(value.comparison);
|
|
2145
|
+
const metadata = record2(value.run_metadata);
|
|
2146
|
+
printDetail([
|
|
2147
|
+
["Run", text(value.run_id)],
|
|
2148
|
+
["Status", status(value.status)],
|
|
2149
|
+
["Base model", text(value.base_model)],
|
|
2150
|
+
["Tuned model", text(value.fine_tuned_model_id)],
|
|
2151
|
+
["Training examples", text(metadata?.training_example_count)],
|
|
2152
|
+
["Evaluation examples", text(metadata?.eval_examples_used)],
|
|
2153
|
+
["Baseline score", displayNumber(baseline?.avg_score)],
|
|
2154
|
+
["Candidate score", displayNumber(candidate?.avg_score)],
|
|
2155
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
2156
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
|
|
2157
|
+
["Regressions", text(comparison?.regressions)],
|
|
2158
|
+
["Improvements", text(comparison?.improvements)]
|
|
2159
|
+
]);
|
|
2160
|
+
renderGate(value);
|
|
1527
2161
|
}
|
|
1528
|
-
function
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
2162
|
+
function renderRunComparison(value) {
|
|
2163
|
+
const runA = record2(value.run_a);
|
|
2164
|
+
const runB = record2(value.run_b);
|
|
2165
|
+
const shared = record2(value.shared);
|
|
2166
|
+
const sharedA = record2(shared?.run_a);
|
|
2167
|
+
const sharedB = record2(shared?.run_b);
|
|
2168
|
+
printTable(
|
|
2169
|
+
["Run", "Candidate score", "Token F1", "Examples"],
|
|
2170
|
+
[
|
|
2171
|
+
[
|
|
2172
|
+
displayId(runA?.run_id),
|
|
2173
|
+
displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
|
|
2174
|
+
displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
|
|
2175
|
+
text(shared?.examples) ?? "0"
|
|
2176
|
+
],
|
|
2177
|
+
[
|
|
2178
|
+
displayId(runB?.run_id),
|
|
2179
|
+
displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
|
|
2180
|
+
displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
|
|
2181
|
+
text(shared?.examples) ?? "0"
|
|
2182
|
+
]
|
|
2183
|
+
]
|
|
2184
|
+
);
|
|
2185
|
+
const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
|
|
2186
|
+
for (const note of notes) printWarning(note);
|
|
2187
|
+
}
|
|
2188
|
+
function renderModelsList(value) {
|
|
2189
|
+
const modelRows = rows(value);
|
|
2190
|
+
if (modelRows.length === 0) {
|
|
2191
|
+
printWarning("No local models found.");
|
|
2192
|
+
return;
|
|
1536
2193
|
}
|
|
2194
|
+
printTable(
|
|
2195
|
+
["Model", "Base model", "Run", "Provider", "Created"],
|
|
2196
|
+
modelRows.map((model) => [
|
|
2197
|
+
displayId(model.id),
|
|
2198
|
+
truncate(text(model.base_model) ?? "\u2014", 32),
|
|
2199
|
+
displayId(model.run_id),
|
|
2200
|
+
text(model.provider) ?? "\u2014",
|
|
2201
|
+
formatDate(text(model.created_at))
|
|
2202
|
+
])
|
|
2203
|
+
);
|
|
1537
2204
|
}
|
|
1538
|
-
function
|
|
1539
|
-
const
|
|
1540
|
-
const
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
`Artifact root ${context.local.artifactRoot}`,
|
|
1553
|
-
`Store root ${context.local.storeRoot}`
|
|
1554
|
-
];
|
|
1555
|
-
for (const warning of context.warnings) lines.push(`Warning ${warning}`);
|
|
1556
|
-
return lines;
|
|
2205
|
+
function renderModelRecord(value) {
|
|
2206
|
+
const model = record2(value.model) ?? value;
|
|
2207
|
+
const artifact = record2(value.artifact);
|
|
2208
|
+
const integrity = record2(value.integrity);
|
|
2209
|
+
printDetail([
|
|
2210
|
+
["Model", text(model.id) ?? text(value.active)],
|
|
2211
|
+
["Base model", text(model.base_model)],
|
|
2212
|
+
["Run", text(model.run_id)],
|
|
2213
|
+
["Provider", text(model.provider)],
|
|
2214
|
+
["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
|
|
2215
|
+
["Manifest", text(value.manifest_path)],
|
|
2216
|
+
["Verified files", text(integrity?.checked)],
|
|
2217
|
+
["Created", formatDate(text(model.created_at))]
|
|
2218
|
+
]);
|
|
1557
2219
|
}
|
|
1558
|
-
function
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
)
|
|
1569
|
-
|
|
1570
|
-
lines.push(
|
|
1571
|
-
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1572
|
-
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
1573
|
-
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
1574
|
-
"Host checks not run (use doctor)"
|
|
1575
|
-
);
|
|
1576
|
-
}
|
|
1577
|
-
return lines;
|
|
2220
|
+
function renderModelPrefetch(value) {
|
|
2221
|
+
printSuccess(
|
|
2222
|
+
value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
|
|
2223
|
+
);
|
|
2224
|
+
printDetail([
|
|
2225
|
+
["Base model", text(value.base_model)],
|
|
2226
|
+
["Revision", text(value.snapshot_revision)],
|
|
2227
|
+
["Snapshot", text(value.snapshot_path)],
|
|
2228
|
+
["Files", text(value.file_count)],
|
|
2229
|
+
["Size", displayBytes(value.size_bytes)],
|
|
2230
|
+
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
2231
|
+
]);
|
|
1578
2232
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
2233
|
+
function renderActiveModel(value) {
|
|
2234
|
+
const active = text(value.active) ?? "base";
|
|
2235
|
+
const pointer = record2(value.pointer);
|
|
2236
|
+
if (pointer?.action === "activate") {
|
|
2237
|
+
printSuccess(`Activated local model ${active}`);
|
|
2238
|
+
} else if (pointer?.action === "rollback") {
|
|
2239
|
+
printSuccess(`Rolled back to ${active}`);
|
|
1585
2240
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2241
|
+
printDetail([
|
|
2242
|
+
["Active", active],
|
|
2243
|
+
["Run", text(pointer?.run_id)],
|
|
2244
|
+
["Previous", text(pointer?.previous_model_id) ?? "base"],
|
|
2245
|
+
["Changed", formatDate(text(pointer?.activated_at))],
|
|
2246
|
+
["Manifest", text(value.manifest_path)]
|
|
2247
|
+
]);
|
|
1591
2248
|
}
|
|
1592
|
-
function
|
|
1593
|
-
|
|
1594
|
-
|
|
2249
|
+
function renderServePlan(value) {
|
|
2250
|
+
printSuccess("Local serving plan is valid");
|
|
2251
|
+
printDetail([
|
|
2252
|
+
["Model", text(value.model_id) ?? text(value.base_model)],
|
|
2253
|
+
["URL", text(value.url)],
|
|
2254
|
+
["Artifact", text(value.artifact_path)],
|
|
2255
|
+
["Manifest", text(value.manifest_path)],
|
|
2256
|
+
["Command", displayCommand(value.command)]
|
|
2257
|
+
]);
|
|
2258
|
+
}
|
|
2259
|
+
function renderKnownJson(args, value) {
|
|
2260
|
+
const command = args[0];
|
|
2261
|
+
const subcommand = args[1];
|
|
2262
|
+
if (command === "doctor" && isRecord2(value)) {
|
|
2263
|
+
renderDoctor(value);
|
|
2264
|
+
return true;
|
|
1595
2265
|
}
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
2266
|
+
if (command === "init" && isRecord2(value)) {
|
|
2267
|
+
renderInit(value);
|
|
2268
|
+
return true;
|
|
1598
2269
|
}
|
|
1599
|
-
if (
|
|
1600
|
-
|
|
2270
|
+
if (command === "validate" && isRecord2(value)) {
|
|
2271
|
+
renderValidate(value);
|
|
2272
|
+
return true;
|
|
1601
2273
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
if (
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
if (quote === "single") {
|
|
1615
|
-
if (character === "'") {
|
|
1616
|
-
quote = "none";
|
|
1617
|
-
} else {
|
|
1618
|
-
token += character;
|
|
1619
|
-
}
|
|
1620
|
-
continue;
|
|
2274
|
+
if (command === "run" && isRecord2(value)) {
|
|
2275
|
+
renderRun(value);
|
|
2276
|
+
return true;
|
|
2277
|
+
}
|
|
2278
|
+
if (command === "runs") {
|
|
2279
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2280
|
+
renderRunsList(value);
|
|
2281
|
+
return true;
|
|
2282
|
+
}
|
|
2283
|
+
if (subcommand === "events" && Array.isArray(value)) {
|
|
2284
|
+
renderRunEvents(value);
|
|
2285
|
+
return true;
|
|
1621
2286
|
}
|
|
1622
|
-
if (
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
continue;
|
|
1626
|
-
}
|
|
1627
|
-
if (character === "\\") {
|
|
1628
|
-
const escaped = input[index + 1];
|
|
1629
|
-
if (escaped === void 0) {
|
|
1630
|
-
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1631
|
-
}
|
|
1632
|
-
token += escaped;
|
|
1633
|
-
tokenStarted = true;
|
|
1634
|
-
index += 1;
|
|
1635
|
-
continue;
|
|
1636
|
-
}
|
|
1637
|
-
if (character === "`") throw operatorError("`\u2026`");
|
|
1638
|
-
if (character === "$" && input[index + 1] === "(") {
|
|
1639
|
-
throw operatorError("$(\u2026)");
|
|
1640
|
-
}
|
|
1641
|
-
token += character;
|
|
1642
|
-
tokenStarted = true;
|
|
1643
|
-
continue;
|
|
2287
|
+
if (subcommand === "get" && isRecord2(value)) {
|
|
2288
|
+
renderRunRecord(value);
|
|
2289
|
+
return true;
|
|
1644
2290
|
}
|
|
1645
|
-
if (
|
|
1646
|
-
|
|
1647
|
-
|
|
2291
|
+
if (subcommand === "report" && isRecord2(value)) {
|
|
2292
|
+
renderRunReport(value);
|
|
2293
|
+
return true;
|
|
1648
2294
|
}
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
continue;
|
|
2295
|
+
if (subcommand === "compare" && isRecord2(value)) {
|
|
2296
|
+
renderRunComparison(value);
|
|
2297
|
+
return true;
|
|
1653
2298
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2299
|
+
}
|
|
2300
|
+
if (command === "models") {
|
|
2301
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
2302
|
+
renderModelsList(value);
|
|
2303
|
+
return true;
|
|
1658
2304
|
}
|
|
1659
|
-
if (
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
token += escaped;
|
|
1665
|
-
tokenStarted = true;
|
|
1666
|
-
index += 1;
|
|
1667
|
-
continue;
|
|
2305
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord2(value)) {
|
|
2306
|
+
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
2307
|
+
renderModelRecord(value);
|
|
2308
|
+
return true;
|
|
1668
2309
|
}
|
|
1669
|
-
if (
|
|
1670
|
-
|
|
1671
|
-
|
|
2310
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord2(value)) {
|
|
2311
|
+
renderModelPrefetch(value);
|
|
2312
|
+
return true;
|
|
1672
2313
|
}
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
1675
|
-
|
|
2314
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord2(value)) {
|
|
2315
|
+
renderActiveModel(value);
|
|
2316
|
+
return true;
|
|
2317
|
+
}
|
|
2318
|
+
if (subcommand === "serve" && isRecord2(value)) {
|
|
2319
|
+
renderServePlan(value);
|
|
2320
|
+
return true;
|
|
1676
2321
|
}
|
|
1677
|
-
token += character;
|
|
1678
|
-
tokenStarted = true;
|
|
1679
2322
|
}
|
|
1680
|
-
if (
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
);
|
|
2323
|
+
if (command === "serve" && isRecord2(value)) {
|
|
2324
|
+
renderServePlan(value);
|
|
2325
|
+
return true;
|
|
1684
2326
|
}
|
|
1685
|
-
|
|
1686
|
-
return tokens;
|
|
2327
|
+
return false;
|
|
1687
2328
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2329
|
+
function lastNonEmptyLine(value) {
|
|
2330
|
+
return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
|
|
2331
|
+
}
|
|
2332
|
+
function adaptLocalCliText(value) {
|
|
2333
|
+
return value.replace(
|
|
2334
|
+
/(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
|
|
2335
|
+
"tt local"
|
|
2336
|
+
).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
|
|
2337
|
+
}
|
|
2338
|
+
function localCliErrorEnvelope(payload) {
|
|
2339
|
+
const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
|
|
2340
|
+
return {
|
|
2341
|
+
error: {
|
|
2342
|
+
status: null,
|
|
2343
|
+
code: "LOCAL_CLI_ERROR",
|
|
2344
|
+
message: adaptLocalCliText(rawMessage),
|
|
2345
|
+
exit_code: payload.exitCode,
|
|
2346
|
+
signal: payload.signal
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
function localCliTextEnvelope(payload) {
|
|
2351
|
+
const output = adaptLocalCliText(payload.stdout).trim();
|
|
2352
|
+
if (payload.args[0] === "info") {
|
|
2353
|
+
const lines = output.split(/\r?\n/);
|
|
2354
|
+
const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
|
|
2355
|
+
const fields = Object.fromEntries(
|
|
2356
|
+
lines.slice(1).flatMap((line) => {
|
|
2357
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
2358
|
+
return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
|
|
2359
|
+
})
|
|
2360
|
+
);
|
|
2361
|
+
if (heading) {
|
|
2362
|
+
return {
|
|
2363
|
+
data: {
|
|
2364
|
+
name: heading[1],
|
|
2365
|
+
description: heading[2],
|
|
2366
|
+
...fields
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
1695
2369
|
}
|
|
1696
2370
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
2371
|
+
return { data: { output } };
|
|
2372
|
+
}
|
|
2373
|
+
function renderLocalOutput(payload, options = {}) {
|
|
2374
|
+
if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
|
|
2375
|
+
return;
|
|
1699
2376
|
}
|
|
1700
|
-
const
|
|
1701
|
-
if (
|
|
1702
|
-
if (
|
|
1703
|
-
|
|
2377
|
+
const output = options.stdout ?? process.stdout;
|
|
2378
|
+
if (options.jsonMode) {
|
|
2379
|
+
if (payload.hasJson) {
|
|
2380
|
+
output.write(payload.stdout);
|
|
2381
|
+
return;
|
|
1704
2382
|
}
|
|
1705
|
-
|
|
2383
|
+
if (payload.exitCode !== 0 || payload.errorMessage) {
|
|
2384
|
+
output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
|
|
2385
|
+
`);
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
|
|
2389
|
+
`);
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (payload.droppedSpecKeys.length > 0) {
|
|
2393
|
+
printWarning(
|
|
2394
|
+
`Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
|
|
2398
|
+
if (payload.hasJson) {
|
|
2399
|
+
printJson(payload.json);
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
|
|
2403
|
+
if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
|
|
2404
|
+
printError(
|
|
2405
|
+
payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
|
|
2406
|
+
);
|
|
1706
2407
|
}
|
|
1707
|
-
return { target: activeMode, args };
|
|
1708
2408
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
"
|
|
1715
|
-
"
|
|
1716
|
-
"
|
|
2409
|
+
|
|
2410
|
+
// src/local-runner.ts
|
|
2411
|
+
var localRequire = createRequire(import.meta.url);
|
|
2412
|
+
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
2413
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
2414
|
+
"--config",
|
|
2415
|
+
"--name",
|
|
2416
|
+
"--model",
|
|
2417
|
+
"--output",
|
|
2418
|
+
"--profile",
|
|
2419
|
+
"--host",
|
|
2420
|
+
"--port",
|
|
2421
|
+
"--device",
|
|
2422
|
+
"--max-tokens",
|
|
2423
|
+
"--temperature",
|
|
2424
|
+
"--top-p",
|
|
2425
|
+
"--max-concurrent-requests",
|
|
2426
|
+
"--spec",
|
|
2427
|
+
"--api-key-env"
|
|
1717
2428
|
]);
|
|
1718
|
-
function
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2429
|
+
function isOptionWithInlineValue(value) {
|
|
2430
|
+
const equals = value.indexOf("=");
|
|
2431
|
+
return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
|
|
2432
|
+
}
|
|
2433
|
+
function positionalIndices(args, start) {
|
|
2434
|
+
const indices = [];
|
|
2435
|
+
let optionsEnded = false;
|
|
2436
|
+
for (let index = start; index < args.length; index += 1) {
|
|
2437
|
+
const value = args[index];
|
|
2438
|
+
if (!optionsEnded && value === "--") {
|
|
2439
|
+
optionsEnded = true;
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
if (!optionsEnded && value.startsWith("-")) {
|
|
2443
|
+
if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
indices.push(index);
|
|
2447
|
+
}
|
|
2448
|
+
return indices;
|
|
2449
|
+
}
|
|
2450
|
+
function optionValueArgument(args, name) {
|
|
2451
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2452
|
+
const value = args[index];
|
|
2453
|
+
if (value === name) {
|
|
2454
|
+
const path = args[index + 1];
|
|
2455
|
+
if (!path) return null;
|
|
2456
|
+
return {
|
|
2457
|
+
path,
|
|
2458
|
+
replace(projectedPath) {
|
|
2459
|
+
args[index + 1] = projectedPath;
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
if (value.startsWith(`${name}=`)) {
|
|
2464
|
+
const path = value.slice(name.length + 1);
|
|
2465
|
+
if (!path) return null;
|
|
2466
|
+
return {
|
|
2467
|
+
path,
|
|
2468
|
+
replace(projectedPath) {
|
|
2469
|
+
args[index] = `${name}=${projectedPath}`;
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
function positionalSpecArgument(args, start, defaultPath) {
|
|
2477
|
+
const positional = positionalIndices(args, start)[0];
|
|
2478
|
+
if (positional !== void 0) {
|
|
1723
2479
|
return {
|
|
1724
|
-
|
|
1725
|
-
|
|
2480
|
+
path: args[positional],
|
|
2481
|
+
replace(projectedPath) {
|
|
2482
|
+
args[positional] = projectedPath;
|
|
2483
|
+
}
|
|
1726
2484
|
};
|
|
1727
2485
|
}
|
|
1728
|
-
if (!trimmed.startsWith("/")) return null;
|
|
1729
|
-
if (trimmed === "/") return { name: "palette", args: [] };
|
|
1730
|
-
const tokens = tokenizeShellInput(trimmed);
|
|
1731
|
-
const rawName = tokens[0].slice(1).toLowerCase();
|
|
1732
|
-
if (!SLASH_NAMES.has(rawName)) {
|
|
1733
|
-
throw new ShellParseError(`Unknown session command: /${rawName}. Use /help to see available commands.`);
|
|
1734
|
-
}
|
|
1735
2486
|
return {
|
|
1736
|
-
|
|
1737
|
-
|
|
2487
|
+
path: defaultPath,
|
|
2488
|
+
replace(projectedPath) {
|
|
2489
|
+
args.splice(start, 0, projectedPath);
|
|
2490
|
+
}
|
|
1738
2491
|
};
|
|
1739
2492
|
}
|
|
1740
|
-
function
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
if (args.length > 0) {
|
|
1745
|
-
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
function expandDirectory(value, cwd, env) {
|
|
1749
|
-
const home = env.HOME ? resolve3(env.HOME) : homedir3();
|
|
1750
|
-
if (value === "~") return home;
|
|
1751
|
-
if (value.startsWith("~/")) return resolve3(home, value.slice(2));
|
|
1752
|
-
return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
|
|
1753
|
-
}
|
|
1754
|
-
function helpText(mode, query, palette = false) {
|
|
1755
|
-
const groups = groupedCatalog(mode, query);
|
|
1756
|
-
const lines = [];
|
|
1757
|
-
lines.push(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)}` : ""}`);
|
|
1758
|
-
if (groups.size === 0) {
|
|
1759
|
-
lines.push(" No matching commands.");
|
|
1760
|
-
} else {
|
|
1761
|
-
for (const [group, commands] of groups) {
|
|
1762
|
-
lines.push(`
|
|
1763
|
-
${group}`);
|
|
1764
|
-
for (const command of commands) {
|
|
1765
|
-
lines.push(` ${command.path.padEnd(22)} ${command.description}`);
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
2493
|
+
function selectedSpecArgument(args, cwd) {
|
|
2494
|
+
const command = args[0];
|
|
2495
|
+
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
2496
|
+
return positionalSpecArgument(args, 1, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1768
2497
|
}
|
|
1769
|
-
if (
|
|
1770
|
-
|
|
1771
|
-
for (const command of SLASH_COMMANDS) {
|
|
1772
|
-
lines.push(` ${command.path.padEnd(22)} ${command.description}`);
|
|
1773
|
-
}
|
|
1774
|
-
lines.push(" ? Alias for /help.");
|
|
1775
|
-
lines.push("\nUse Tab to complete commands. Shell operators and shell escapes are disabled.");
|
|
2498
|
+
if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
|
|
2499
|
+
return positionalSpecArgument(args, 2, join3(cwd, DEFAULT_SPEC_NAME));
|
|
1776
2500
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
2501
|
+
const serves = command === "serve" || command === "models" && args[1] === "serve";
|
|
2502
|
+
if (!serves) return null;
|
|
2503
|
+
const explicit = optionValueArgument(args, "--spec");
|
|
2504
|
+
if (explicit) return explicit;
|
|
2505
|
+
return null;
|
|
1779
2506
|
}
|
|
1780
|
-
function
|
|
1781
|
-
|
|
1782
|
-
return [
|
|
1783
|
-
chalk2.bold.cyan("Tuned Tensor"),
|
|
1784
|
-
` ${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec}`,
|
|
1785
|
-
chalk2.dim(" /help for commands \xB7 /mode cloud|local to switch workflows"),
|
|
1786
|
-
""
|
|
1787
|
-
].join("\n");
|
|
2507
|
+
async function pathIsFile(path) {
|
|
2508
|
+
return (await stat3(path).catch(() => null))?.isFile() ?? false;
|
|
1788
2509
|
}
|
|
1789
|
-
function
|
|
1790
|
-
|
|
2510
|
+
function resolveFromCwd(path, cwd) {
|
|
2511
|
+
if (isAbsolute3(path)) return path;
|
|
2512
|
+
return resolve3(cwd, path);
|
|
1791
2513
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
this.io = io;
|
|
1796
|
-
this.env = env;
|
|
1797
|
-
this.contextProvider = contextProvider;
|
|
1798
|
-
this.mode = initialContext.inferredTarget;
|
|
1799
|
-
this.modeSource = initialContext.targetSource;
|
|
1800
|
-
this.cwd = initialContext.cwd;
|
|
1801
|
-
this.context = initialContext;
|
|
1802
|
-
}
|
|
1803
|
-
mode;
|
|
1804
|
-
modeSource;
|
|
1805
|
-
cwd;
|
|
1806
|
-
context;
|
|
1807
|
-
static async create(options) {
|
|
1808
|
-
const env = options.env ?? process.env;
|
|
1809
|
-
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
1810
|
-
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1811
|
-
const initialContext = await contextProvider({ cwd, env });
|
|
1812
|
-
return new _TunedTensorShellSession(
|
|
1813
|
-
options.runner,
|
|
1814
|
-
options.io,
|
|
1815
|
-
env,
|
|
1816
|
-
contextProvider,
|
|
1817
|
-
initialContext
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1820
|
-
snapshot() {
|
|
2514
|
+
async function projectSpecArguments(originalArgs, cwd) {
|
|
2515
|
+
const args = [...originalArgs];
|
|
2516
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1821
2517
|
return {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2518
|
+
args,
|
|
2519
|
+
droppedSpecKeys: [],
|
|
2520
|
+
pathReplacements: [],
|
|
2521
|
+
cleanup: async () => {
|
|
2522
|
+
}
|
|
1826
2523
|
};
|
|
1827
2524
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2525
|
+
const selected = selectedSpecArgument(args, cwd);
|
|
2526
|
+
if (!selected) {
|
|
2527
|
+
return {
|
|
2528
|
+
args,
|
|
2529
|
+
droppedSpecKeys: [],
|
|
2530
|
+
pathReplacements: [],
|
|
2531
|
+
cleanup: async () => {
|
|
2532
|
+
}
|
|
2533
|
+
};
|
|
1833
2534
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2535
|
+
const sourcePath = resolveFromCwd(selected.path, cwd);
|
|
2536
|
+
if (!await pathIsFile(sourcePath)) {
|
|
2537
|
+
return {
|
|
2538
|
+
args,
|
|
2539
|
+
droppedSpecKeys: [],
|
|
2540
|
+
pathReplacements: [],
|
|
2541
|
+
cleanup: async () => {
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
1841
2544
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2545
|
+
const raw = JSON.parse(await readFile2(sourcePath, "utf8"));
|
|
2546
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2547
|
+
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
1845
2548
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
case "context":
|
|
1860
|
-
assertNoArgs("context", command.args);
|
|
1861
|
-
await this.refreshContext();
|
|
1862
|
-
this.writeLines(formatShellContext(this.context, this.mode, this.modeSource));
|
|
1863
|
-
return "continue";
|
|
1864
|
-
case "mode": {
|
|
1865
|
-
if (command.args.length === 0) {
|
|
1866
|
-
this.io.write(`Current workflow: ${this.mode}
|
|
1867
|
-
`);
|
|
1868
|
-
return "continue";
|
|
1869
|
-
}
|
|
1870
|
-
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1871
|
-
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1872
|
-
}
|
|
1873
|
-
this.mode = command.args[0];
|
|
1874
|
-
this.modeSource = "session";
|
|
1875
|
-
this.io.write(`Workflow switched to ${this.mode}.
|
|
1876
|
-
`);
|
|
1877
|
-
return "continue";
|
|
1878
|
-
}
|
|
1879
|
-
case "clear":
|
|
1880
|
-
assertNoArgs("clear", command.args);
|
|
1881
|
-
this.io.clear();
|
|
1882
|
-
return "continue";
|
|
1883
|
-
case "cd": {
|
|
1884
|
-
if (command.args.length === 0) {
|
|
1885
|
-
this.io.write(`${this.cwd}
|
|
1886
|
-
`);
|
|
1887
|
-
return "continue";
|
|
1888
|
-
}
|
|
1889
|
-
if (command.args.length !== 1) {
|
|
1890
|
-
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
1891
|
-
}
|
|
1892
|
-
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
1893
|
-
const metadata = await stat3(nextDirectory).catch(() => null);
|
|
1894
|
-
if (!metadata?.isDirectory()) {
|
|
1895
|
-
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
1896
|
-
}
|
|
1897
|
-
this.cwd = nextDirectory;
|
|
1898
|
-
await this.refreshContext();
|
|
1899
|
-
this.io.write(`Directory: ${this.cwd}
|
|
1900
|
-
`);
|
|
1901
|
-
return "continue";
|
|
2549
|
+
const body = raw;
|
|
2550
|
+
const runRequestKeys = [
|
|
2551
|
+
"run_id",
|
|
2552
|
+
"behavior_spec_id",
|
|
2553
|
+
"run_number",
|
|
2554
|
+
"spec_snapshot"
|
|
2555
|
+
].filter((key) => key in body);
|
|
2556
|
+
if ("spec_snapshot" in body || runRequestKeys.length >= 2) {
|
|
2557
|
+
return {
|
|
2558
|
+
args,
|
|
2559
|
+
droppedSpecKeys: [],
|
|
2560
|
+
pathReplacements: [],
|
|
2561
|
+
cleanup: async () => {
|
|
1902
2562
|
}
|
|
1903
|
-
|
|
1904
|
-
assertNoArgs("exit", command.args);
|
|
1905
|
-
return "exit";
|
|
1906
|
-
}
|
|
2563
|
+
};
|
|
1907
2564
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
cwd: this.cwd
|
|
1918
|
-
});
|
|
1919
|
-
await this.refreshContext();
|
|
1920
|
-
return "continue";
|
|
1921
|
-
} catch (error) {
|
|
1922
|
-
this.io.writeError(`${chalk2.red("Error:")} ${errorMessage(error)}
|
|
1923
|
-
`);
|
|
1924
|
-
return "continue";
|
|
1925
|
-
}
|
|
2565
|
+
const projected = projectLocalSpec(body);
|
|
2566
|
+
if (projected.droppedKeys.length === 0) {
|
|
2567
|
+
return {
|
|
2568
|
+
args,
|
|
2569
|
+
droppedSpecKeys: [],
|
|
2570
|
+
pathReplacements: [],
|
|
2571
|
+
cleanup: async () => {
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
1926
2574
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2575
|
+
const temporaryPath = join3(
|
|
2576
|
+
dirname2(sourcePath),
|
|
2577
|
+
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
|
|
2578
|
+
);
|
|
2579
|
+
await writeFile(
|
|
2580
|
+
temporaryPath,
|
|
2581
|
+
`${JSON.stringify(projected.body, null, 2)}
|
|
2582
|
+
`,
|
|
2583
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
2584
|
+
);
|
|
2585
|
+
selected.replace(temporaryPath);
|
|
2586
|
+
return {
|
|
2587
|
+
args,
|
|
2588
|
+
droppedSpecKeys: projected.droppedKeys,
|
|
2589
|
+
pathReplacements: [{
|
|
2590
|
+
sourcePath,
|
|
2591
|
+
projectedPath: temporaryPath
|
|
2592
|
+
}],
|
|
2593
|
+
cleanup: () => rm(temporaryPath, { force: true })
|
|
2594
|
+
};
|
|
1930
2595
|
}
|
|
1931
|
-
function
|
|
1932
|
-
|
|
2596
|
+
function resolveLocalCliEntrypoint() {
|
|
2597
|
+
try {
|
|
2598
|
+
return localRequire.resolve("@tuned-tensor/local");
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
throw new Error(
|
|
2601
|
+
"TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
|
|
2602
|
+
{ cause: error }
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
1933
2605
|
}
|
|
1934
|
-
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
if (restoreRawMode) control.input.setRawMode(false);
|
|
1941
|
-
signals.on("SIGINT", keepParentAlive);
|
|
2606
|
+
function localCommandStreamsStdout(args) {
|
|
2607
|
+
const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
|
|
2608
|
+
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
2609
|
+
}
|
|
2610
|
+
function parseJsonOutput(stdout2) {
|
|
2611
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
1942
2612
|
try {
|
|
1943
|
-
return
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
if (restoreRawMode) control.input.setRawMode(true);
|
|
1947
|
-
control.resumeReadline();
|
|
2613
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
2614
|
+
} catch {
|
|
2615
|
+
return { hasJson: false, json: void 0 };
|
|
1948
2616
|
}
|
|
1949
2617
|
}
|
|
1950
|
-
function
|
|
1951
|
-
return
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
},
|
|
1955
|
-
writeError(text2) {
|
|
1956
|
-
error.write(text2);
|
|
1957
|
-
},
|
|
1958
|
-
clear() {
|
|
1959
|
-
output.write("\x1B[2J\x1B[H");
|
|
1960
|
-
}
|
|
1961
|
-
};
|
|
2618
|
+
function signalExitCode(signal) {
|
|
2619
|
+
if (!signal) return 1;
|
|
2620
|
+
const signalNumber = osConstants.signals[signal];
|
|
2621
|
+
return typeof signalNumber === "number" ? 128 + signalNumber : 1;
|
|
1962
2622
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2623
|
+
function writeStream(stream, chunk) {
|
|
2624
|
+
stream.write(chunk);
|
|
2625
|
+
}
|
|
2626
|
+
function normalizeProjectedPaths(value, replacements) {
|
|
2627
|
+
let normalized = value;
|
|
2628
|
+
for (const replacement of replacements) {
|
|
2629
|
+
const encodedProjectedPath = JSON.stringify(
|
|
2630
|
+
replacement.projectedPath
|
|
2631
|
+
).slice(1, -1);
|
|
2632
|
+
const encodedSourcePath = JSON.stringify(
|
|
2633
|
+
replacement.sourcePath
|
|
2634
|
+
).slice(1, -1);
|
|
2635
|
+
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
1969
2636
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2637
|
+
return normalized;
|
|
2638
|
+
}
|
|
2639
|
+
async function spawnLocalCli(args) {
|
|
2640
|
+
const output = args.stdout ?? process.stdout;
|
|
2641
|
+
const errors = args.stderr ?? process.stderr;
|
|
2642
|
+
const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
|
|
2643
|
+
const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
|
|
2644
|
+
const child = spawn(
|
|
2645
|
+
args.nodeExecutable,
|
|
2646
|
+
[args.entrypoint, ...args.commandArgs],
|
|
1973
2647
|
{
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2648
|
+
cwd: args.cwd,
|
|
2649
|
+
env: args.env,
|
|
2650
|
+
stdio: [
|
|
2651
|
+
pipeInput ? "pipe" : "inherit",
|
|
2652
|
+
args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
2653
|
+
"pipe"
|
|
2654
|
+
],
|
|
2655
|
+
detached: process.platform !== "win32"
|
|
2656
|
+
}
|
|
1979
2657
|
);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2658
|
+
if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
|
|
2659
|
+
let stdout2 = "";
|
|
2660
|
+
let stderr = "";
|
|
2661
|
+
child.stdout?.setEncoding("utf8");
|
|
2662
|
+
child.stderr?.setEncoding("utf8");
|
|
2663
|
+
child.stdout?.on("data", (chunk) => {
|
|
2664
|
+
const normalized = normalizeProjectedPaths(
|
|
2665
|
+
chunk,
|
|
2666
|
+
args.pathReplacements
|
|
2667
|
+
);
|
|
2668
|
+
if (args.streamingStdout) writeStream(output, normalized);
|
|
2669
|
+
else stdout2 += normalized;
|
|
1985
2670
|
});
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2671
|
+
child.stderr?.on("data", (chunk) => {
|
|
2672
|
+
const normalized = normalizeProjectedPaths(
|
|
2673
|
+
chunk,
|
|
2674
|
+
args.pathReplacements
|
|
2675
|
+
);
|
|
2676
|
+
stderr += normalized;
|
|
2677
|
+
writeStream(
|
|
2678
|
+
errors,
|
|
2679
|
+
args.jsonMode ? normalized : adaptLocalCliText(normalized)
|
|
2680
|
+
);
|
|
1994
2681
|
});
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2682
|
+
return new Promise((resolveChild) => {
|
|
2683
|
+
let spawnError;
|
|
2684
|
+
const forwardSignal = (signal) => {
|
|
2685
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
2686
|
+
if (child.pid && process.platform !== "win32") {
|
|
2687
|
+
try {
|
|
2688
|
+
process.kill(-child.pid, childSignal);
|
|
2689
|
+
return;
|
|
2690
|
+
} catch {
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
child.kill(childSignal);
|
|
2694
|
+
};
|
|
2695
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
2696
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
2697
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
2698
|
+
const cleanup = () => {
|
|
2699
|
+
process.off("SIGINT", onSigint);
|
|
2700
|
+
process.off("SIGTERM", onSigterm);
|
|
2701
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
2702
|
+
};
|
|
2703
|
+
process.on("SIGINT", onSigint);
|
|
2704
|
+
process.on("SIGTERM", onSigterm);
|
|
2705
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
2706
|
+
child.once("error", (error) => {
|
|
2707
|
+
spawnError = error;
|
|
2708
|
+
});
|
|
2709
|
+
child.once("close", (code, signal) => {
|
|
2710
|
+
cleanup();
|
|
2711
|
+
resolveChild({
|
|
2712
|
+
exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
|
|
2713
|
+
signal,
|
|
2714
|
+
stdout: stdout2,
|
|
2715
|
+
stderr,
|
|
2716
|
+
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
2717
|
+
});
|
|
2718
|
+
});
|
|
1999
2719
|
});
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2720
|
+
}
|
|
2721
|
+
function setupFailure(originalArgs, error) {
|
|
2722
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2723
|
+
return {
|
|
2724
|
+
args: [...originalArgs],
|
|
2725
|
+
projectedArgs: [...originalArgs],
|
|
2726
|
+
entrypoint: null,
|
|
2727
|
+
exitCode: 1,
|
|
2728
|
+
signal: null,
|
|
2729
|
+
stdout: "",
|
|
2730
|
+
stderr: "",
|
|
2731
|
+
hasJson: false,
|
|
2732
|
+
json: void 0,
|
|
2733
|
+
streamingStdout: false,
|
|
2734
|
+
droppedSpecKeys: [],
|
|
2735
|
+
errorMessage: message
|
|
2736
|
+
};
|
|
2737
|
+
}
|
|
2738
|
+
async function runLocalCommand(args, options = {}) {
|
|
2739
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
2740
|
+
let projected;
|
|
2003
2741
|
try {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2742
|
+
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
2743
|
+
projected = await projectSpecArguments(args, cwd);
|
|
2744
|
+
const streamingStdout = localCommandStreamsStdout(projected.args);
|
|
2745
|
+
const result = await spawnLocalCli({
|
|
2746
|
+
entrypoint,
|
|
2747
|
+
commandArgs: projected.args,
|
|
2748
|
+
cwd,
|
|
2749
|
+
env: options.env ?? process.env,
|
|
2750
|
+
nodeExecutable: options.nodeExecutable ?? process.execPath,
|
|
2751
|
+
streamingStdout,
|
|
2752
|
+
jsonMode: options.jsonMode ?? false,
|
|
2753
|
+
stdin: options.stdin,
|
|
2754
|
+
stdout: options.stdout,
|
|
2755
|
+
stderr: options.stderr,
|
|
2756
|
+
pathReplacements: projected.pathReplacements
|
|
2757
|
+
});
|
|
2758
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
2759
|
+
return {
|
|
2760
|
+
args: [...args],
|
|
2761
|
+
projectedArgs: projected.args,
|
|
2762
|
+
entrypoint,
|
|
2763
|
+
...result,
|
|
2764
|
+
...parsed,
|
|
2765
|
+
streamingStdout,
|
|
2766
|
+
droppedSpecKeys: projected.droppedSpecKeys
|
|
2767
|
+
};
|
|
2768
|
+
} catch (error) {
|
|
2769
|
+
return setupFailure(args, error);
|
|
2013
2770
|
} finally {
|
|
2014
|
-
|
|
2771
|
+
await projected?.cleanup().catch(() => void 0);
|
|
2015
2772
|
}
|
|
2016
2773
|
}
|
|
2017
|
-
function
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2774
|
+
async function executeLocalCommand(args, options = {}) {
|
|
2775
|
+
const result = await runLocalCommand(args, options);
|
|
2776
|
+
renderLocalOutput(result, {
|
|
2777
|
+
jsonMode: options.jsonMode,
|
|
2778
|
+
stdout: options.stdout
|
|
2779
|
+
});
|
|
2780
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
2781
|
+
return result;
|
|
2024
2782
|
}
|
|
2025
2783
|
|
|
2026
2784
|
// src/commands/auth.ts
|
|
2027
2785
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2028
2786
|
import { stdin, stdout } from "process";
|
|
2029
2787
|
import { Writable } from "stream";
|
|
2030
|
-
import
|
|
2788
|
+
import chalk4 from "chalk";
|
|
2031
2789
|
async function promptForApiKey(input = stdin, output = stdout) {
|
|
2032
2790
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2033
2791
|
throw new Error(
|
|
@@ -2069,9 +2827,9 @@ function registerAuthCommands(parent) {
|
|
|
2069
2827
|
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
2070
2828
|
console.log();
|
|
2071
2829
|
console.log(
|
|
2072
|
-
` To get your API key, go to ${
|
|
2830
|
+
` To get your API key, go to ${chalk4.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
2073
2831
|
);
|
|
2074
|
-
console.log(` ${
|
|
2832
|
+
console.log(` ${chalk4.cyan.underline(settingsUrl)}`);
|
|
2075
2833
|
console.log();
|
|
2076
2834
|
apiKey = await promptForApiKey();
|
|
2077
2835
|
}
|
|
@@ -2958,18 +3716,18 @@ function findControlChar(value) {
|
|
|
2958
3716
|
function formatCodepoint(code) {
|
|
2959
3717
|
return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
2960
3718
|
}
|
|
2961
|
-
function detectRowFormat(
|
|
2962
|
-
const input =
|
|
2963
|
-
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof
|
|
3719
|
+
function detectRowFormat(record3) {
|
|
3720
|
+
const input = record3.input;
|
|
3721
|
+
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record3.output === "string") {
|
|
2964
3722
|
return "document_ocr_jsonl";
|
|
2965
3723
|
}
|
|
2966
|
-
if (typeof
|
|
3724
|
+
if (typeof record3.input === "string" || typeof record3.output === "string" || !("input" in record3) || !("output" in record3)) {
|
|
2967
3725
|
return "jsonl";
|
|
2968
3726
|
}
|
|
2969
3727
|
return null;
|
|
2970
3728
|
}
|
|
2971
|
-
function validateDocumentOcrRow(
|
|
2972
|
-
const input =
|
|
3729
|
+
function validateDocumentOcrRow(record3, rowNumber, errors) {
|
|
3730
|
+
const input = record3.input;
|
|
2973
3731
|
const assets = input.assets;
|
|
2974
3732
|
if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
|
|
2975
3733
|
errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
|
|
@@ -2981,10 +3739,10 @@ function validateDocumentOcrRow(record2, rowNumber, errors) {
|
|
|
2981
3739
|
);
|
|
2982
3740
|
}
|
|
2983
3741
|
}
|
|
2984
|
-
if (typeof
|
|
3742
|
+
if (typeof record3.output !== "string" || record3.output.trim().length === 0) {
|
|
2985
3743
|
errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
|
|
2986
3744
|
} else {
|
|
2987
|
-
const hit = findControlChar(
|
|
3745
|
+
const hit = findControlChar(record3.output);
|
|
2988
3746
|
if (hit) {
|
|
2989
3747
|
errors.push(
|
|
2990
3748
|
`Row ${rowNumber}: OCR output contains invisible control character ${formatCodepoint(hit.codepoint)} (${hit.name}) at offset ${hit.offset} \u2014 strip or escape before uploading; some training paths split on it`
|
|
@@ -3053,14 +3811,14 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3053
3811
|
errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
|
|
3054
3812
|
continue;
|
|
3055
3813
|
}
|
|
3056
|
-
const
|
|
3057
|
-
if ("messages" in
|
|
3814
|
+
const record3 = row;
|
|
3815
|
+
if ("messages" in record3 && !("input" in record3) && !("output" in record3)) {
|
|
3058
3816
|
errors.push(
|
|
3059
3817
|
`Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
|
|
3060
3818
|
);
|
|
3061
3819
|
continue;
|
|
3062
3820
|
}
|
|
3063
|
-
const rowFormat = detectRowFormat(
|
|
3821
|
+
const rowFormat = detectRowFormat(record3);
|
|
3064
3822
|
if (!rowFormat) {
|
|
3065
3823
|
errors.push(
|
|
3066
3824
|
`Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
|
|
@@ -3077,17 +3835,17 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
3077
3835
|
continue;
|
|
3078
3836
|
}
|
|
3079
3837
|
if (rowFormat === "document_ocr_jsonl") {
|
|
3080
|
-
validateDocumentOcrRow(
|
|
3838
|
+
validateDocumentOcrRow(record3, rowNumber, errors);
|
|
3081
3839
|
continue;
|
|
3082
3840
|
}
|
|
3083
|
-
if (typeof
|
|
3841
|
+
if (typeof record3.input !== "string") {
|
|
3084
3842
|
errors.push(`Row ${rowNumber}: missing string "input" field`);
|
|
3085
3843
|
}
|
|
3086
|
-
if (typeof
|
|
3844
|
+
if (typeof record3.output !== "string") {
|
|
3087
3845
|
errors.push(`Row ${rowNumber}: missing string "output" field`);
|
|
3088
3846
|
}
|
|
3089
3847
|
for (const field of ["input", "output"]) {
|
|
3090
|
-
const value =
|
|
3848
|
+
const value = record3[field];
|
|
3091
3849
|
if (typeof value !== "string") continue;
|
|
3092
3850
|
const hit = findControlChar(value);
|
|
3093
3851
|
if (hit) {
|
|
@@ -4090,12 +4848,12 @@ var ManagedModelServer = class {
|
|
|
4090
4848
|
clearTimeout(this.idleTimer);
|
|
4091
4849
|
this.idleTimer = null;
|
|
4092
4850
|
}
|
|
4093
|
-
writeLog(
|
|
4851
|
+
writeLog(record3) {
|
|
4094
4852
|
if (this.logRecordOverride) {
|
|
4095
|
-
this.logRecordOverride(
|
|
4853
|
+
this.logRecordOverride(record3);
|
|
4096
4854
|
return;
|
|
4097
4855
|
}
|
|
4098
|
-
const line = `${JSON.stringify(
|
|
4856
|
+
const line = `${JSON.stringify(record3)}
|
|
4099
4857
|
`;
|
|
4100
4858
|
if (this.options.logFile) {
|
|
4101
4859
|
mkdirSync2(dirname3(this.options.logFile), { recursive: true });
|
|
@@ -5535,7 +6293,7 @@ function registerModelsCommands(parent) {
|
|
|
5535
6293
|
}
|
|
5536
6294
|
|
|
5537
6295
|
// src/commands/balance.ts
|
|
5538
|
-
import
|
|
6296
|
+
import chalk5 from "chalk";
|
|
5539
6297
|
var KIND_LABELS = {
|
|
5540
6298
|
signup_bonus: "Signup bonus",
|
|
5541
6299
|
topup: "Top-up",
|
|
@@ -5550,8 +6308,8 @@ function formatCents3(cents) {
|
|
|
5550
6308
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
5551
6309
|
}
|
|
5552
6310
|
function formatSigned(cents) {
|
|
5553
|
-
if (cents > 0) return
|
|
5554
|
-
if (cents < 0) return
|
|
6311
|
+
if (cents > 0) return chalk5.green(`+${formatCents3(cents)}`);
|
|
6312
|
+
if (cents < 0) return chalk5.red(formatCents3(cents));
|
|
5555
6313
|
return formatCents3(cents);
|
|
5556
6314
|
}
|
|
5557
6315
|
function registerBalanceCommands(parent) {
|
|
@@ -5566,7 +6324,7 @@ function registerBalanceCommands(parent) {
|
|
|
5566
6324
|
return printJson({ balance, transactions });
|
|
5567
6325
|
}
|
|
5568
6326
|
const lowBalance = balance.balance_cents < 100;
|
|
5569
|
-
const balanceLine = lowBalance ?
|
|
6327
|
+
const balanceLine = lowBalance ? chalk5.red(formatCents3(balance.balance_cents)) + chalk5.dim(" (low)") : chalk5.bold.green(formatCents3(balance.balance_cents));
|
|
5570
6328
|
printDetail([
|
|
5571
6329
|
["Credits", balanceLine],
|
|
5572
6330
|
[
|
|
@@ -5581,7 +6339,7 @@ function registerBalanceCommands(parent) {
|
|
|
5581
6339
|
]);
|
|
5582
6340
|
if (transactions && transactions.length > 0) {
|
|
5583
6341
|
console.log(`
|
|
5584
|
-
${
|
|
6342
|
+
${chalk5.bold("Recent transactions")}`);
|
|
5585
6343
|
printTable(
|
|
5586
6344
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
5587
6345
|
transactions.map((row) => [
|
|
@@ -5589,19 +6347,19 @@ ${chalk4.bold("Recent transactions")}`);
|
|
|
5589
6347
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
5590
6348
|
formatSigned(row.amount_cents),
|
|
5591
6349
|
formatCents3(row.balance_after_cents),
|
|
5592
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
6350
|
+
row.reference_id ? shortId(row.reference_id) : chalk5.dim("\u2014")
|
|
5593
6351
|
])
|
|
5594
6352
|
);
|
|
5595
6353
|
} else {
|
|
5596
6354
|
console.log(
|
|
5597
6355
|
`
|
|
5598
|
-
${
|
|
6356
|
+
${chalk5.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
5599
6357
|
);
|
|
5600
6358
|
}
|
|
5601
6359
|
if (lowBalance) {
|
|
5602
6360
|
console.log(
|
|
5603
6361
|
`
|
|
5604
|
-
${formatStatus("preparing")} ${
|
|
6362
|
+
${formatStatus("preparing")} ${chalk5.yellow(
|
|
5605
6363
|
"Run `tt topup` to add more credits before starting a run."
|
|
5606
6364
|
)}`
|
|
5607
6365
|
);
|
|
@@ -5610,7 +6368,7 @@ ${formatStatus("preparing")} ${chalk4.yellow(
|
|
|
5610
6368
|
}
|
|
5611
6369
|
|
|
5612
6370
|
// src/commands/topup.ts
|
|
5613
|
-
import
|
|
6371
|
+
import chalk6 from "chalk";
|
|
5614
6372
|
import open from "open";
|
|
5615
6373
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
5616
6374
|
var MIN_USD = 5;
|
|
@@ -5649,9 +6407,9 @@ function registerTopupCommands(parent) {
|
|
|
5649
6407
|
"amount is required in --json mode (use --amount <usd>)"
|
|
5650
6408
|
);
|
|
5651
6409
|
}
|
|
5652
|
-
console.log(
|
|
6410
|
+
console.log(chalk6.bold("Add credits via Stripe Checkout"));
|
|
5653
6411
|
console.log(
|
|
5654
|
-
|
|
6412
|
+
chalk6.dim(
|
|
5655
6413
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
5656
6414
|
)
|
|
5657
6415
|
);
|
|
@@ -5678,8 +6436,8 @@ function registerTopupCommands(parent) {
|
|
|
5678
6436
|
}
|
|
5679
6437
|
const url = data.checkout_url;
|
|
5680
6438
|
console.log(
|
|
5681
|
-
`${
|
|
5682
|
-
` +
|
|
6439
|
+
`${chalk6.bold("Checkout URL:")} ${chalk6.cyan(url)}
|
|
6440
|
+
` + chalk6.dim("Complete the payment to credit your account.")
|
|
5683
6441
|
);
|
|
5684
6442
|
if (options.open !== false) {
|
|
5685
6443
|
try {
|
|
@@ -5687,7 +6445,7 @@ function registerTopupCommands(parent) {
|
|
|
5687
6445
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
5688
6446
|
} catch {
|
|
5689
6447
|
console.log(
|
|
5690
|
-
|
|
6448
|
+
chalk6.yellow(
|
|
5691
6449
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
5692
6450
|
)
|
|
5693
6451
|
);
|
|
@@ -5697,7 +6455,7 @@ function registerTopupCommands(parent) {
|
|
|
5697
6455
|
}
|
|
5698
6456
|
|
|
5699
6457
|
// src/commands/eval.ts
|
|
5700
|
-
import
|
|
6458
|
+
import chalk7 from "chalk";
|
|
5701
6459
|
|
|
5702
6460
|
// src/eval/rules.ts
|
|
5703
6461
|
function validateSpec(spec) {
|
|
@@ -5862,7 +6620,7 @@ function registerEvalCommand(parent) {
|
|
|
5862
6620
|
const spec = loadSpec(cmdOpts.file);
|
|
5863
6621
|
const validation = validateSpec(spec);
|
|
5864
6622
|
if (!isJsonMode()) {
|
|
5865
|
-
console.log(
|
|
6623
|
+
console.log(chalk7.dim(`
|
|
5866
6624
|
Spec: ${cmdOpts.file}
|
|
5867
6625
|
`));
|
|
5868
6626
|
}
|
|
@@ -5877,10 +6635,10 @@ Spec: ${cmdOpts.file}
|
|
|
5877
6635
|
});
|
|
5878
6636
|
}
|
|
5879
6637
|
function printValidation(checks) {
|
|
5880
|
-
console.log(
|
|
6638
|
+
console.log(chalk7.bold("Spec Validation"));
|
|
5881
6639
|
for (const check of checks) {
|
|
5882
|
-
const icon = check.passed ?
|
|
5883
|
-
const msg = check.message ?
|
|
6640
|
+
const icon = check.passed ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
6641
|
+
const msg = check.message ? chalk7.dim(` \u2014 ${check.message}`) : "";
|
|
5884
6642
|
console.log(` ${icon} ${check.name}${msg}`);
|
|
5885
6643
|
}
|
|
5886
6644
|
}
|
|
@@ -6036,16 +6794,28 @@ function createProgram(version, runtime = {}) {
|
|
|
6036
6794
|
const openShell = async () => {
|
|
6037
6795
|
const root = program.opts();
|
|
6038
6796
|
const shellEnvironment = childEnvironment(root, env);
|
|
6797
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6798
|
+
const error = runtime.stderr ?? process.stderr;
|
|
6799
|
+
const agent = createShellAgent({
|
|
6800
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
6801
|
+
apiKey: root.apiKey ?? shellEnvironment.TUNED_TENSOR_API_KEY,
|
|
6802
|
+
baseUrl: root.baseUrl ?? shellEnvironment.TUNED_TENSOR_URL
|
|
6803
|
+
}),
|
|
6804
|
+
output,
|
|
6805
|
+
error
|
|
6806
|
+
});
|
|
6039
6807
|
await launchShell({
|
|
6040
6808
|
runner: shellRunner,
|
|
6809
|
+
agent,
|
|
6041
6810
|
input: runtime.stdin ?? process.stdin,
|
|
6042
|
-
output
|
|
6043
|
-
error
|
|
6811
|
+
output,
|
|
6812
|
+
error,
|
|
6044
6813
|
cwd,
|
|
6045
|
-
env: shellEnvironment
|
|
6814
|
+
env: shellEnvironment,
|
|
6815
|
+
version
|
|
6046
6816
|
});
|
|
6047
6817
|
};
|
|
6048
|
-
program.name("tt").description("Tuned Tensor \u2014
|
|
6818
|
+
program.name("tt").description("Tuned Tensor \u2014 converse, train, and inspect from one terminal").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
|
|
6049
6819
|
"-u, --base-url <url>",
|
|
6050
6820
|
"API base URL (default: https://tunedtensor.com)"
|
|
6051
6821
|
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
|
|
@@ -6053,7 +6823,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6053
6823
|
if (root.json) setJsonMode(true);
|
|
6054
6824
|
if (root.color === false) {
|
|
6055
6825
|
process.env.FORCE_COLOR = "0";
|
|
6056
|
-
|
|
6826
|
+
chalk8.level = 0;
|
|
6057
6827
|
}
|
|
6058
6828
|
});
|
|
6059
6829
|
registerAuthCommands(program);
|
|
@@ -6086,12 +6856,12 @@ function createProgram(version, runtime = {}) {
|
|
|
6086
6856
|
`);
|
|
6087
6857
|
return;
|
|
6088
6858
|
}
|
|
6089
|
-
output.write(`${
|
|
6859
|
+
output.write(`${chalk8.bold.cyan("Tuned Tensor status")}
|
|
6090
6860
|
`);
|
|
6091
6861
|
output.write(`${formatShellStatus(context, target).join("\n")}
|
|
6092
6862
|
|
|
6093
6863
|
`);
|
|
6094
|
-
output.write(`${
|
|
6864
|
+
output.write(`${chalk8.bold("Context")}
|
|
6095
6865
|
`);
|
|
6096
6866
|
output.write(
|
|
6097
6867
|
`${formatShellContext(
|
|
@@ -6105,7 +6875,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6105
6875
|
program.command("local").description("Run the local CUDA training and model workflow").helpOption(false).allowUnknownOption().argument("[args...]", "TT Local command and arguments").action(async (args) => {
|
|
6106
6876
|
const root = program.opts();
|
|
6107
6877
|
const passthrough = extractPassthroughOptions(args, root);
|
|
6108
|
-
if (!passthrough.color)
|
|
6878
|
+
if (!passthrough.color) chalk8.level = 0;
|
|
6109
6879
|
const result = await invokeLocal(
|
|
6110
6880
|
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6111
6881
|
{
|
|
@@ -6145,7 +6915,7 @@ function createProgram(version, runtime = {}) {
|
|
|
6145
6915
|
"after",
|
|
6146
6916
|
`
|
|
6147
6917
|
Workflows:
|
|
6148
|
-
tt Open the
|
|
6918
|
+
tt Open the conversational terminal (TTY only)
|
|
6149
6919
|
tt status Inspect cloud/local project context
|
|
6150
6920
|
tt cloud <command> Run a hosted command explicitly
|
|
6151
6921
|
tt local <command> Run a local GPU command
|
|
@@ -6158,6 +6928,22 @@ Examples:
|
|
|
6158
6928
|
);
|
|
6159
6929
|
return program;
|
|
6160
6930
|
}
|
|
6931
|
+
function createShellAgent(options) {
|
|
6932
|
+
return new TunedTensorAgentSession({
|
|
6933
|
+
client: options.client,
|
|
6934
|
+
io: {
|
|
6935
|
+
write(text2) {
|
|
6936
|
+
options.output.write(text2);
|
|
6937
|
+
},
|
|
6938
|
+
writeError(text2) {
|
|
6939
|
+
options.error.write(text2);
|
|
6940
|
+
},
|
|
6941
|
+
clear() {
|
|
6942
|
+
options.output.write("\x1B[2J\x1B[H");
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6161
6947
|
async function runCli(version, runtime = {}) {
|
|
6162
6948
|
const argv = runtime.argv ?? process.argv;
|
|
6163
6949
|
const env = runtime.env ?? process.env;
|
|
@@ -6169,6 +6955,16 @@ async function runCli(version, runtime = {}) {
|
|
|
6169
6955
|
env
|
|
6170
6956
|
})) {
|
|
6171
6957
|
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6958
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6959
|
+
const error = runtime.stderr ?? process.stderr;
|
|
6960
|
+
const agent = createShellAgent({
|
|
6961
|
+
client: runtime.agentClient ?? createAgentClient({
|
|
6962
|
+
apiKey: env.TUNED_TENSOR_API_KEY,
|
|
6963
|
+
baseUrl: env.TUNED_TENSOR_URL
|
|
6964
|
+
}),
|
|
6965
|
+
output,
|
|
6966
|
+
error
|
|
6967
|
+
});
|
|
6172
6968
|
await (runtime.startShell ?? startInteractiveShell)({
|
|
6173
6969
|
runner: async (request2) => await invokeSelf(
|
|
6174
6970
|
request2.target === "local" ? ["local", ...request2.args] : request2.args,
|
|
@@ -6178,11 +6974,13 @@ async function runCli(version, runtime = {}) {
|
|
|
6178
6974
|
entrypoint: argv[1]
|
|
6179
6975
|
}
|
|
6180
6976
|
),
|
|
6977
|
+
agent,
|
|
6181
6978
|
input: runtime.stdin ?? process.stdin,
|
|
6182
|
-
output
|
|
6183
|
-
error
|
|
6979
|
+
output,
|
|
6980
|
+
error,
|
|
6184
6981
|
cwd: runtime.cwd ?? process.cwd(),
|
|
6185
|
-
env
|
|
6982
|
+
env,
|
|
6983
|
+
version
|
|
6186
6984
|
});
|
|
6187
6985
|
return;
|
|
6188
6986
|
}
|
|
@@ -6191,7 +6989,7 @@ async function runCli(version, runtime = {}) {
|
|
|
6191
6989
|
}
|
|
6192
6990
|
|
|
6193
6991
|
// src/index.ts
|
|
6194
|
-
runCli("0.
|
|
6992
|
+
runCli("0.7.0").catch((err) => {
|
|
6195
6993
|
reportError(err);
|
|
6196
6994
|
process.exit(1);
|
|
6197
6995
|
});
|