@tuned-tensor/cli 0.4.24 → 0.6.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/LICENSE +161 -21
- package/README.md +104 -11
- package/dist/index.js +2464 -318
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,162 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { spawn as spawn4 } from "child_process";
|
|
5
|
+
import { constants as osConstants2 } from "os";
|
|
4
6
|
import { Command } from "commander";
|
|
7
|
+
import chalk7 from "chalk";
|
|
8
|
+
|
|
9
|
+
// src/local-runner.ts
|
|
10
|
+
import { constants as osConstants } from "os";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { createRequire } from "module";
|
|
13
|
+
import { spawn } from "child_process";
|
|
14
|
+
import {
|
|
15
|
+
readFile,
|
|
16
|
+
rm,
|
|
17
|
+
stat,
|
|
18
|
+
writeFile
|
|
19
|
+
} from "fs/promises";
|
|
20
|
+
import {
|
|
21
|
+
basename as basename2,
|
|
22
|
+
dirname,
|
|
23
|
+
isAbsolute,
|
|
24
|
+
join as join2,
|
|
25
|
+
resolve
|
|
26
|
+
} from "path";
|
|
27
|
+
|
|
28
|
+
// src/base-models.ts
|
|
29
|
+
var SUPPORTED_BASE_MODELS = [
|
|
30
|
+
"google/gemma-4-E2B-it",
|
|
31
|
+
"google/gemma-4-E4B-it",
|
|
32
|
+
"Qwen/Qwen3.5-2B",
|
|
33
|
+
"Qwen/Qwen3-VL-2B-Instruct",
|
|
34
|
+
"Qwen/Qwen3.5-4B",
|
|
35
|
+
"meta-llama/Llama-3.2-3B-Instruct",
|
|
36
|
+
"microsoft/Phi-4-mini-instruct",
|
|
37
|
+
"ibm-granite/granite-3.3-2b-instruct",
|
|
38
|
+
"bigcode/starcoder2-3b"
|
|
39
|
+
];
|
|
40
|
+
var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const model of SUPPORTED_BASE_MODELS) {
|
|
42
|
+
BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
|
|
43
|
+
}
|
|
44
|
+
for (const [alias, model] of [
|
|
45
|
+
["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
|
|
46
|
+
["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
|
|
47
|
+
["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
|
|
48
|
+
["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
|
|
49
|
+
["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
|
|
50
|
+
["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
|
|
51
|
+
["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
|
|
52
|
+
["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
|
|
53
|
+
["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
|
|
54
|
+
["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
55
|
+
["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
56
|
+
["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
57
|
+
["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
|
|
58
|
+
["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
|
|
59
|
+
["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
|
|
60
|
+
["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
61
|
+
["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
62
|
+
["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
63
|
+
["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
64
|
+
["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
65
|
+
["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
66
|
+
["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
67
|
+
["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
|
|
68
|
+
["starcoder2-3b", "bigcode/starcoder2-3b"]
|
|
69
|
+
]) {
|
|
70
|
+
BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
|
|
71
|
+
}
|
|
72
|
+
var UnsupportedBaseModelError = class extends Error {
|
|
73
|
+
constructor(model) {
|
|
74
|
+
const supported = SUPPORTED_BASE_MODELS.join(", ");
|
|
75
|
+
super(
|
|
76
|
+
`Unsupported base_model "${String(model)}". Supported base models: ${supported}`
|
|
77
|
+
);
|
|
78
|
+
this.name = "UnsupportedBaseModelError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function canonicalizeBaseModel(model) {
|
|
82
|
+
if (typeof model !== "string" || !model.trim()) {
|
|
83
|
+
throw new UnsupportedBaseModelError(model);
|
|
84
|
+
}
|
|
85
|
+
const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
|
|
86
|
+
if (!canonical) {
|
|
87
|
+
throw new UnsupportedBaseModelError(model);
|
|
88
|
+
}
|
|
89
|
+
return canonical;
|
|
90
|
+
}
|
|
91
|
+
function canonicalizeSpecBaseModel(body) {
|
|
92
|
+
if ("base_model" in body && body.base_model !== void 0) {
|
|
93
|
+
return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
|
|
94
|
+
}
|
|
95
|
+
return body;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/project-spec.ts
|
|
99
|
+
var CLOUD_SPEC_KEYS = [
|
|
100
|
+
"name",
|
|
101
|
+
"description",
|
|
102
|
+
"base_model",
|
|
103
|
+
"system_prompt",
|
|
104
|
+
"guidelines",
|
|
105
|
+
"constraints",
|
|
106
|
+
"examples",
|
|
107
|
+
"eval_cases"
|
|
108
|
+
];
|
|
109
|
+
var LOCAL_ONLY_SPEC_KEYS = [
|
|
110
|
+
"hyperparameters",
|
|
111
|
+
"dataset_prebuilt"
|
|
112
|
+
];
|
|
113
|
+
var LOCAL_SPEC_KEYS = [
|
|
114
|
+
"id",
|
|
115
|
+
"name",
|
|
116
|
+
"description",
|
|
117
|
+
"system_prompt",
|
|
118
|
+
"guidelines",
|
|
119
|
+
"constraints",
|
|
120
|
+
"base_model",
|
|
121
|
+
"examples",
|
|
122
|
+
...LOCAL_ONLY_SPEC_KEYS
|
|
123
|
+
];
|
|
124
|
+
var CLOUD_SPEC_KEY_SET = new Set(CLOUD_SPEC_KEYS);
|
|
125
|
+
var LOCAL_ONLY_SPEC_KEY_SET = new Set(LOCAL_ONLY_SPEC_KEYS);
|
|
126
|
+
var LOCAL_SPEC_KEY_SET = new Set(LOCAL_SPEC_KEYS);
|
|
127
|
+
var KNOWN_PROJECT_SPEC_KEY_SET = /* @__PURE__ */ new Set([
|
|
128
|
+
...CLOUD_SPEC_KEYS,
|
|
129
|
+
...LOCAL_SPEC_KEYS
|
|
130
|
+
]);
|
|
131
|
+
function projectSpec(raw, supportedKeys) {
|
|
132
|
+
const body = {};
|
|
133
|
+
const droppedKeys = [];
|
|
134
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
135
|
+
if (supportedKeys.has(key)) {
|
|
136
|
+
body[key] = value;
|
|
137
|
+
} else {
|
|
138
|
+
droppedKeys.push(key);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
body: canonicalizeSpecBaseModel(body),
|
|
143
|
+
droppedKeys
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function projectCloudSpec(raw) {
|
|
147
|
+
return projectSpec(raw, CLOUD_SPEC_KEY_SET);
|
|
148
|
+
}
|
|
149
|
+
function projectLocalSpec(raw) {
|
|
150
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET);
|
|
151
|
+
}
|
|
152
|
+
function hasLocalOnlySpecFields(raw) {
|
|
153
|
+
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
154
|
+
}
|
|
155
|
+
function unknownProjectSpecKeys(raw) {
|
|
156
|
+
return Object.keys(raw).filter(
|
|
157
|
+
(key) => !KNOWN_PROJECT_SPEC_KEY_SET.has(key)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
5
160
|
|
|
6
161
|
// src/output.ts
|
|
7
162
|
import chalk from "chalk";
|
|
@@ -12,7 +167,13 @@ import { readFileSync as readFileSync2, statSync } from "fs";
|
|
|
12
167
|
import { basename } from "path";
|
|
13
168
|
|
|
14
169
|
// src/config.ts
|
|
15
|
-
import {
|
|
170
|
+
import {
|
|
171
|
+
chmodSync,
|
|
172
|
+
existsSync,
|
|
173
|
+
mkdirSync,
|
|
174
|
+
readFileSync,
|
|
175
|
+
writeFileSync
|
|
176
|
+
} from "fs";
|
|
16
177
|
import { homedir } from "os";
|
|
17
178
|
import { join } from "path";
|
|
18
179
|
var CONFIG_DIR_NAME = "tuned-tensor";
|
|
@@ -36,8 +197,13 @@ function readConfig() {
|
|
|
36
197
|
}
|
|
37
198
|
function writeConfig(config) {
|
|
38
199
|
const dir = getConfigDir();
|
|
39
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
40
|
-
|
|
200
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
201
|
+
chmodSync(dir, 448);
|
|
202
|
+
const path = getConfigPath();
|
|
203
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", {
|
|
204
|
+
mode: 384
|
|
205
|
+
});
|
|
206
|
+
chmodSync(path, 384);
|
|
41
207
|
}
|
|
42
208
|
function updateConfig(partial) {
|
|
43
209
|
const current = readConfig();
|
|
@@ -45,7 +211,7 @@ function updateConfig(partial) {
|
|
|
45
211
|
}
|
|
46
212
|
function clearConfig() {
|
|
47
213
|
const path = getConfigPath();
|
|
48
|
-
if (existsSync(path))
|
|
214
|
+
if (existsSync(path)) writeConfig({});
|
|
49
215
|
}
|
|
50
216
|
var DEFAULT_BASE_URL = "https://tunedtensor.com";
|
|
51
217
|
function getBaseUrl(opts) {
|
|
@@ -57,9 +223,9 @@ function getApiKey(opts) {
|
|
|
57
223
|
|
|
58
224
|
// src/client.ts
|
|
59
225
|
var ApiError = class extends Error {
|
|
60
|
-
constructor(
|
|
226
|
+
constructor(status2, code, message) {
|
|
61
227
|
super(message);
|
|
62
|
-
this.status =
|
|
228
|
+
this.status = status2;
|
|
63
229
|
this.code = code;
|
|
64
230
|
this.name = "ApiError";
|
|
65
231
|
}
|
|
@@ -140,12 +306,12 @@ function isJsonMode() {
|
|
|
140
306
|
function printJson(data) {
|
|
141
307
|
console.log(JSON.stringify(data, null, 2));
|
|
142
308
|
}
|
|
143
|
-
function printTable(headers,
|
|
309
|
+
function printTable(headers, rows2, meta) {
|
|
144
310
|
const table = new Table({
|
|
145
311
|
head: headers.map((h) => chalk.bold.cyan(h)),
|
|
146
312
|
style: { head: [], border: [] }
|
|
147
313
|
});
|
|
148
|
-
for (const row of
|
|
314
|
+
for (const row of rows2) {
|
|
149
315
|
table.push(row);
|
|
150
316
|
}
|
|
151
317
|
console.log(table.toString());
|
|
@@ -158,103 +324,1898 @@ Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
|
158
324
|
)
|
|
159
325
|
);
|
|
160
326
|
}
|
|
161
|
-
}
|
|
162
|
-
function printDetail(fields) {
|
|
163
|
-
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
164
|
-
for (const [label, value] of fields) {
|
|
165
|
-
const paddedLabel = label.padEnd(maxLabel);
|
|
166
|
-
console.log(`${chalk.bold.cyan(paddedLabel)} ${value ?? chalk.dim("\u2014")}`);
|
|
327
|
+
}
|
|
328
|
+
function printDetail(fields) {
|
|
329
|
+
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
330
|
+
for (const [label, value] of fields) {
|
|
331
|
+
const paddedLabel = label.padEnd(maxLabel);
|
|
332
|
+
console.log(`${chalk.bold.cyan(paddedLabel)} ${value ?? chalk.dim("\u2014")}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function printSuccess(message) {
|
|
336
|
+
console.log(chalk.green("\u2713") + " " + message);
|
|
337
|
+
}
|
|
338
|
+
function printWarning(message) {
|
|
339
|
+
console.log(chalk.yellow("!") + " " + message);
|
|
340
|
+
}
|
|
341
|
+
function printError(message) {
|
|
342
|
+
console.error(chalk.red("\u2717") + " " + message);
|
|
343
|
+
}
|
|
344
|
+
function reportError(err) {
|
|
345
|
+
const isApi = err instanceof ApiError;
|
|
346
|
+
const message = err?.message || String(err);
|
|
347
|
+
if (jsonMode) {
|
|
348
|
+
printJson({
|
|
349
|
+
error: {
|
|
350
|
+
status: isApi ? err.status : null,
|
|
351
|
+
code: isApi ? err.code : "CLI_ERROR",
|
|
352
|
+
message
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
} else if (isApi) {
|
|
356
|
+
printError(`[${err.status}] ${message}`);
|
|
357
|
+
} else {
|
|
358
|
+
printError(message);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function formatDate(iso) {
|
|
362
|
+
if (!iso) return "\u2014";
|
|
363
|
+
return new Date(iso).toLocaleString();
|
|
364
|
+
}
|
|
365
|
+
function formatStatus(status2) {
|
|
366
|
+
const map = {
|
|
367
|
+
completed: chalk.green,
|
|
368
|
+
running: chalk.blue,
|
|
369
|
+
training: chalk.blue,
|
|
370
|
+
evaluating: chalk.blue,
|
|
371
|
+
preparing: chalk.yellow,
|
|
372
|
+
pending: chalk.yellow,
|
|
373
|
+
uploading: chalk.yellow,
|
|
374
|
+
failed: chalk.red,
|
|
375
|
+
cancelled: chalk.dim,
|
|
376
|
+
validated: chalk.green,
|
|
377
|
+
invalid: chalk.red
|
|
378
|
+
};
|
|
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
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/local-output.ts
|
|
391
|
+
function isRecord(value) {
|
|
392
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
393
|
+
}
|
|
394
|
+
function text(value) {
|
|
395
|
+
if (typeof value === "string") return value;
|
|
396
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
397
|
+
return String(value);
|
|
398
|
+
}
|
|
399
|
+
return void 0;
|
|
400
|
+
}
|
|
401
|
+
function number(value) {
|
|
402
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
403
|
+
}
|
|
404
|
+
function record(value) {
|
|
405
|
+
return isRecord(value) ? value : void 0;
|
|
406
|
+
}
|
|
407
|
+
function rows(value) {
|
|
408
|
+
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
409
|
+
}
|
|
410
|
+
function displayId(value) {
|
|
411
|
+
const id = text(value);
|
|
412
|
+
return id ? shortId(id) : "\u2014";
|
|
413
|
+
}
|
|
414
|
+
function displayNumber(value, digits = 4) {
|
|
415
|
+
const parsed = number(value);
|
|
416
|
+
if (parsed === void 0) return void 0;
|
|
417
|
+
return parsed.toFixed(digits).replace(/\.?0+$/, "");
|
|
418
|
+
}
|
|
419
|
+
function displayBytes(value) {
|
|
420
|
+
const bytes = number(value);
|
|
421
|
+
if (bytes === void 0 || bytes < 0) return void 0;
|
|
422
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
423
|
+
let amount = bytes;
|
|
424
|
+
let unit = 0;
|
|
425
|
+
while (amount >= 1024 && unit < units.length - 1) {
|
|
426
|
+
amount /= 1024;
|
|
427
|
+
unit += 1;
|
|
428
|
+
}
|
|
429
|
+
const precision = amount >= 10 || unit === 0 ? 0 : 1;
|
|
430
|
+
return `${amount.toFixed(precision)} ${units[unit]}`;
|
|
431
|
+
}
|
|
432
|
+
function displayCommand(value) {
|
|
433
|
+
if (!Array.isArray(value) || !value.every((part) => typeof part === "string")) {
|
|
434
|
+
return void 0;
|
|
435
|
+
}
|
|
436
|
+
return value.map(
|
|
437
|
+
(part) => /[\s"'\\]/.test(part) ? JSON.stringify(part) : part
|
|
438
|
+
).join(" ");
|
|
439
|
+
}
|
|
440
|
+
function status(value) {
|
|
441
|
+
const parsed = text(value) ?? "unknown";
|
|
442
|
+
return formatStatus(parsed);
|
|
443
|
+
}
|
|
444
|
+
function gateFrom(value) {
|
|
445
|
+
return record(record(value)?.general_regression);
|
|
446
|
+
}
|
|
447
|
+
function renderGate(value) {
|
|
448
|
+
const gate = gateFrom(value);
|
|
449
|
+
if (!gate) {
|
|
450
|
+
printWarning(
|
|
451
|
+
"General regression was not run; this model cannot be activated yet."
|
|
452
|
+
);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (gate.passed === true) {
|
|
456
|
+
printSuccess("General regression gate passed");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
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
|
+
}
|
|
467
|
+
function renderDoctor(value) {
|
|
468
|
+
const checks = rows(value.checks);
|
|
469
|
+
printTable(
|
|
470
|
+
["Check", "Status", "Details"],
|
|
471
|
+
checks.map((check) => [
|
|
472
|
+
text(check.name) ?? "unknown",
|
|
473
|
+
check.ok === true ? formatStatus("completed") : formatStatus("failed"),
|
|
474
|
+
truncate(text(check.message) ?? text(check.detail) ?? "\u2014", 100)
|
|
475
|
+
])
|
|
476
|
+
);
|
|
477
|
+
if (value.ok === true) {
|
|
478
|
+
printSuccess("Local environment is ready");
|
|
479
|
+
} else {
|
|
480
|
+
printError("Local environment needs attention");
|
|
481
|
+
}
|
|
482
|
+
if (value.config_path) {
|
|
483
|
+
printDetail([["Config", text(value.config_path)]]);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function renderInit(value) {
|
|
487
|
+
printSuccess("Created local Tuned Tensor project");
|
|
488
|
+
printDetail([
|
|
489
|
+
["Path", text(value.path)],
|
|
490
|
+
["Name", text(value.name)],
|
|
491
|
+
["ID", text(value.id)],
|
|
492
|
+
["Base model", text(value.base_model)],
|
|
493
|
+
["Config", text(value.config_path)]
|
|
494
|
+
]);
|
|
495
|
+
}
|
|
496
|
+
function renderValidate(value) {
|
|
497
|
+
printSuccess("Local behavior spec is valid");
|
|
498
|
+
printDetail([
|
|
499
|
+
["Input", text(value.input_path)],
|
|
500
|
+
["Config", text(value.config_path)],
|
|
501
|
+
["Spec", text(value.behavior_spec_id)],
|
|
502
|
+
["Base model", text(value.base_model)],
|
|
503
|
+
["Dataset", text(value.dataset_format)],
|
|
504
|
+
["Artifacts", text(value.artifact_root)],
|
|
505
|
+
["Store", text(value.store_root)],
|
|
506
|
+
["Dry run", text(value.dry_run)]
|
|
507
|
+
]);
|
|
508
|
+
}
|
|
509
|
+
function renderRun(value) {
|
|
510
|
+
const runStatus = text(value.status) ?? "unknown";
|
|
511
|
+
if (runStatus === "completed") {
|
|
512
|
+
printSuccess("Local run completed");
|
|
513
|
+
} else {
|
|
514
|
+
printWarning(`Local run finished with status ${runStatus}`);
|
|
515
|
+
}
|
|
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
|
+
}
|
|
528
|
+
function renderRunsList(value) {
|
|
529
|
+
const runRows = rows(value);
|
|
530
|
+
if (runRows.length === 0) {
|
|
531
|
+
printWarning("No local runs found.");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
printTable(
|
|
535
|
+
["Run", "Spec", "Status", "Stage", "Model", "Updated"],
|
|
536
|
+
runRows.map((run) => [
|
|
537
|
+
displayId(run.id),
|
|
538
|
+
truncate(text(run.spec_name) ?? displayId(run.behavior_spec_id), 28),
|
|
539
|
+
status(run.status),
|
|
540
|
+
text(run.current_stage) ?? "\u2014",
|
|
541
|
+
displayId(run.model_id),
|
|
542
|
+
formatDate(text(run.updated_at))
|
|
543
|
+
])
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
function renderRunEvents(value) {
|
|
547
|
+
const eventRows = rows(value);
|
|
548
|
+
if (eventRows.length === 0) {
|
|
549
|
+
printWarning("No local run events found.");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
printTable(
|
|
553
|
+
["Time", "Stage", "Status", "Message"],
|
|
554
|
+
eventRows.map((event) => [
|
|
555
|
+
formatDate(text(event.occurred_at)),
|
|
556
|
+
text(event.stage) ?? "\u2014",
|
|
557
|
+
status(event.status),
|
|
558
|
+
truncate(text(event.message) ?? "\u2014", 80)
|
|
559
|
+
])
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function renderRunRecord(value) {
|
|
563
|
+
printDetail([
|
|
564
|
+
["Run", text(value.id) ?? text(value.run_id)],
|
|
565
|
+
["Spec", text(value.spec_name) ?? text(value.behavior_spec_id)],
|
|
566
|
+
["Status", status(value.status)],
|
|
567
|
+
["Stage", text(value.current_stage)],
|
|
568
|
+
["Model", text(value.model_id) ?? text(value.fine_tuned_model_id)],
|
|
569
|
+
["Base model", text(value.base_model)],
|
|
570
|
+
["Report", text(value.report_path)],
|
|
571
|
+
["Artifacts", text(value.artifact_dir)],
|
|
572
|
+
["Updated", formatDate(text(value.updated_at) ?? text(value.created_at))]
|
|
573
|
+
]);
|
|
574
|
+
}
|
|
575
|
+
function renderRunReport(value) {
|
|
576
|
+
const baseline = record(value.baseline);
|
|
577
|
+
const candidate = record(value.candidate);
|
|
578
|
+
const comparison = record(value.comparison);
|
|
579
|
+
const metadata = record(value.run_metadata);
|
|
580
|
+
printDetail([
|
|
581
|
+
["Run", text(value.run_id)],
|
|
582
|
+
["Status", status(value.status)],
|
|
583
|
+
["Base model", text(value.base_model)],
|
|
584
|
+
["Tuned model", text(value.fine_tuned_model_id)],
|
|
585
|
+
["Training examples", text(metadata?.training_example_count)],
|
|
586
|
+
["Evaluation examples", text(metadata?.eval_examples_used)],
|
|
587
|
+
["Baseline score", displayNumber(baseline?.avg_score)],
|
|
588
|
+
["Candidate score", displayNumber(candidate?.avg_score)],
|
|
589
|
+
["Score delta", displayNumber(comparison?.avg_score_delta)],
|
|
590
|
+
["Pass-rate delta", displayNumber(comparison?.pass_rate_delta)],
|
|
591
|
+
["Regressions", text(comparison?.regressions)],
|
|
592
|
+
["Improvements", text(comparison?.improvements)]
|
|
593
|
+
]);
|
|
594
|
+
renderGate(value);
|
|
595
|
+
}
|
|
596
|
+
function renderRunComparison(value) {
|
|
597
|
+
const runA = record(value.run_a);
|
|
598
|
+
const runB = record(value.run_b);
|
|
599
|
+
const shared = record(value.shared);
|
|
600
|
+
const sharedA = record(shared?.run_a);
|
|
601
|
+
const sharedB = record(shared?.run_b);
|
|
602
|
+
printTable(
|
|
603
|
+
["Run", "Candidate score", "Token F1", "Examples"],
|
|
604
|
+
[
|
|
605
|
+
[
|
|
606
|
+
displayId(runA?.run_id),
|
|
607
|
+
displayNumber(sharedA?.candidate_avg_score) ?? "\u2014",
|
|
608
|
+
displayNumber(sharedA?.candidate_avg_token_f1) ?? "\u2014",
|
|
609
|
+
text(shared?.examples) ?? "0"
|
|
610
|
+
],
|
|
611
|
+
[
|
|
612
|
+
displayId(runB?.run_id),
|
|
613
|
+
displayNumber(sharedB?.candidate_avg_score) ?? "\u2014",
|
|
614
|
+
displayNumber(sharedB?.candidate_avg_token_f1) ?? "\u2014",
|
|
615
|
+
text(shared?.examples) ?? "0"
|
|
616
|
+
]
|
|
617
|
+
]
|
|
618
|
+
);
|
|
619
|
+
const notes = Array.isArray(value.notes) ? value.notes.filter((note) => typeof note === "string") : [];
|
|
620
|
+
for (const note of notes) printWarning(note);
|
|
621
|
+
}
|
|
622
|
+
function renderModelsList(value) {
|
|
623
|
+
const modelRows = rows(value);
|
|
624
|
+
if (modelRows.length === 0) {
|
|
625
|
+
printWarning("No local models found.");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
printTable(
|
|
629
|
+
["Model", "Base model", "Run", "Provider", "Created"],
|
|
630
|
+
modelRows.map((model) => [
|
|
631
|
+
displayId(model.id),
|
|
632
|
+
truncate(text(model.base_model) ?? "\u2014", 32),
|
|
633
|
+
displayId(model.run_id),
|
|
634
|
+
text(model.provider) ?? "\u2014",
|
|
635
|
+
formatDate(text(model.created_at))
|
|
636
|
+
])
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
function renderModelRecord(value) {
|
|
640
|
+
const model = record(value.model) ?? value;
|
|
641
|
+
const artifact = record(value.artifact);
|
|
642
|
+
const integrity = record(value.integrity);
|
|
643
|
+
printDetail([
|
|
644
|
+
["Model", text(model.id) ?? text(value.active)],
|
|
645
|
+
["Base model", text(model.base_model)],
|
|
646
|
+
["Run", text(model.run_id)],
|
|
647
|
+
["Provider", text(model.provider)],
|
|
648
|
+
["Artifact", text(model.artifact_uri) ?? text(artifact?.path)],
|
|
649
|
+
["Manifest", text(value.manifest_path)],
|
|
650
|
+
["Verified files", text(integrity?.checked)],
|
|
651
|
+
["Created", formatDate(text(model.created_at))]
|
|
652
|
+
]);
|
|
653
|
+
}
|
|
654
|
+
function renderModelPrefetch(value) {
|
|
655
|
+
printSuccess(
|
|
656
|
+
value.local_files_only === true ? "Verified the local base-model snapshot" : "Base model is ready locally"
|
|
657
|
+
);
|
|
658
|
+
printDetail([
|
|
659
|
+
["Base model", text(value.base_model)],
|
|
660
|
+
["Revision", text(value.snapshot_revision)],
|
|
661
|
+
["Snapshot", text(value.snapshot_path)],
|
|
662
|
+
["Files", text(value.file_count)],
|
|
663
|
+
["Size", displayBytes(value.size_bytes)],
|
|
664
|
+
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
665
|
+
]);
|
|
666
|
+
}
|
|
667
|
+
function renderActiveModel(value) {
|
|
668
|
+
const active = text(value.active) ?? "base";
|
|
669
|
+
const pointer = record(value.pointer);
|
|
670
|
+
if (pointer?.action === "activate") {
|
|
671
|
+
printSuccess(`Activated local model ${active}`);
|
|
672
|
+
} else if (pointer?.action === "rollback") {
|
|
673
|
+
printSuccess(`Rolled back to ${active}`);
|
|
674
|
+
}
|
|
675
|
+
printDetail([
|
|
676
|
+
["Active", active],
|
|
677
|
+
["Run", text(pointer?.run_id)],
|
|
678
|
+
["Previous", text(pointer?.previous_model_id) ?? "base"],
|
|
679
|
+
["Changed", formatDate(text(pointer?.activated_at))],
|
|
680
|
+
["Manifest", text(value.manifest_path)]
|
|
681
|
+
]);
|
|
682
|
+
}
|
|
683
|
+
function renderServePlan(value) {
|
|
684
|
+
printSuccess("Local serving plan is valid");
|
|
685
|
+
printDetail([
|
|
686
|
+
["Model", text(value.model_id) ?? text(value.base_model)],
|
|
687
|
+
["URL", text(value.url)],
|
|
688
|
+
["Artifact", text(value.artifact_path)],
|
|
689
|
+
["Manifest", text(value.manifest_path)],
|
|
690
|
+
["Command", displayCommand(value.command)]
|
|
691
|
+
]);
|
|
692
|
+
}
|
|
693
|
+
function renderKnownJson(args, value) {
|
|
694
|
+
const command = args[0];
|
|
695
|
+
const subcommand = args[1];
|
|
696
|
+
if (command === "doctor" && isRecord(value)) {
|
|
697
|
+
renderDoctor(value);
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
if (command === "init" && isRecord(value)) {
|
|
701
|
+
renderInit(value);
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
if (command === "validate" && isRecord(value)) {
|
|
705
|
+
renderValidate(value);
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
if (command === "run" && isRecord(value)) {
|
|
709
|
+
renderRun(value);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
if (command === "runs") {
|
|
713
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
714
|
+
renderRunsList(value);
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
if (subcommand === "events" && Array.isArray(value)) {
|
|
718
|
+
renderRunEvents(value);
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
if (subcommand === "get" && isRecord(value)) {
|
|
722
|
+
renderRunRecord(value);
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
if (subcommand === "report" && isRecord(value)) {
|
|
726
|
+
renderRunReport(value);
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
if (subcommand === "compare" && isRecord(value)) {
|
|
730
|
+
renderRunComparison(value);
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (command === "models") {
|
|
735
|
+
if ((subcommand === void 0 || subcommand === "list") && Array.isArray(value)) {
|
|
736
|
+
renderModelsList(value);
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord(value)) {
|
|
740
|
+
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
741
|
+
renderModelRecord(value);
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord(value)) {
|
|
745
|
+
renderModelPrefetch(value);
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord(value)) {
|
|
749
|
+
renderActiveModel(value);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
if (subcommand === "serve" && isRecord(value)) {
|
|
753
|
+
renderServePlan(value);
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (command === "serve" && isRecord(value)) {
|
|
758
|
+
renderServePlan(value);
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
function lastNonEmptyLine(value) {
|
|
764
|
+
return value.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).at(-1);
|
|
765
|
+
}
|
|
766
|
+
function adaptLocalCliText(value) {
|
|
767
|
+
return value.replace(
|
|
768
|
+
/(?<![.@/\w])tuned-tensor-local(?=$|[\s:`\]])/g,
|
|
769
|
+
"tt local"
|
|
770
|
+
).replace(/(?<![.@/\w])tt-local(?=$|[\s:`\]])/g, "tt local");
|
|
771
|
+
}
|
|
772
|
+
function localCliErrorEnvelope(payload) {
|
|
773
|
+
const rawMessage = payload.errorMessage ?? (payload.stderr.trim() || void 0) ?? lastNonEmptyLine(payload.stdout) ?? `tt local exited with code ${payload.exitCode}.`;
|
|
774
|
+
return {
|
|
775
|
+
error: {
|
|
776
|
+
status: null,
|
|
777
|
+
code: "LOCAL_CLI_ERROR",
|
|
778
|
+
message: adaptLocalCliText(rawMessage),
|
|
779
|
+
exit_code: payload.exitCode,
|
|
780
|
+
signal: payload.signal
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
function localCliTextEnvelope(payload) {
|
|
785
|
+
const output = adaptLocalCliText(payload.stdout).trim();
|
|
786
|
+
if (payload.args[0] === "info") {
|
|
787
|
+
const lines = output.split(/\r?\n/);
|
|
788
|
+
const heading = lines[0]?.match(/^([^:]+):\s*(.+)$/);
|
|
789
|
+
const fields = Object.fromEntries(
|
|
790
|
+
lines.slice(1).flatMap((line) => {
|
|
791
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
792
|
+
return match ? [[match[1].trim().toLowerCase().replaceAll(" ", "_"), match[2].trim()]] : [];
|
|
793
|
+
})
|
|
794
|
+
);
|
|
795
|
+
if (heading) {
|
|
796
|
+
return {
|
|
797
|
+
data: {
|
|
798
|
+
name: heading[1],
|
|
799
|
+
description: heading[2],
|
|
800
|
+
...fields
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return { data: { output } };
|
|
806
|
+
}
|
|
807
|
+
function renderLocalOutput(payload, options = {}) {
|
|
808
|
+
if (payload.streamingStdout && payload.exitCode === 0 && !payload.errorMessage) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
const output = options.stdout ?? process.stdout;
|
|
812
|
+
if (options.jsonMode) {
|
|
813
|
+
if (payload.hasJson) {
|
|
814
|
+
output.write(payload.stdout);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (payload.exitCode !== 0 || payload.errorMessage) {
|
|
818
|
+
output.write(`${JSON.stringify(localCliErrorEnvelope(payload), null, 2)}
|
|
819
|
+
`);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
output.write(`${JSON.stringify(localCliTextEnvelope(payload), null, 2)}
|
|
823
|
+
`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (payload.droppedSpecKeys.length > 0) {
|
|
827
|
+
printWarning(
|
|
828
|
+
`Ignored project field(s) unsupported by the local runtime: ${payload.droppedSpecKeys.join(", ")}.`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (payload.hasJson && renderKnownJson(payload.args, payload.json)) return;
|
|
832
|
+
if (payload.hasJson) {
|
|
833
|
+
printJson(payload.json);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (payload.stdout) output.write(adaptLocalCliText(payload.stdout));
|
|
837
|
+
if (payload.exitCode !== 0 && !payload.stderr && !payload.stdout) {
|
|
838
|
+
printError(
|
|
839
|
+
payload.errorMessage ? adaptLocalCliText(payload.errorMessage) : `tt local exited with code ${payload.exitCode}.`
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/local-runner.ts
|
|
845
|
+
var localRequire = createRequire(import.meta.url);
|
|
846
|
+
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
847
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
848
|
+
"--config",
|
|
849
|
+
"--name",
|
|
850
|
+
"--model",
|
|
851
|
+
"--output",
|
|
852
|
+
"--profile",
|
|
853
|
+
"--host",
|
|
854
|
+
"--port",
|
|
855
|
+
"--device",
|
|
856
|
+
"--max-tokens",
|
|
857
|
+
"--temperature",
|
|
858
|
+
"--top-p",
|
|
859
|
+
"--max-concurrent-requests",
|
|
860
|
+
"--spec",
|
|
861
|
+
"--api-key-env"
|
|
862
|
+
]);
|
|
863
|
+
function isOptionWithInlineValue(value) {
|
|
864
|
+
const equals = value.indexOf("=");
|
|
865
|
+
return equals !== -1 && VALUE_OPTIONS.has(value.slice(0, equals));
|
|
866
|
+
}
|
|
867
|
+
function positionalIndices(args, start) {
|
|
868
|
+
const indices = [];
|
|
869
|
+
let optionsEnded = false;
|
|
870
|
+
for (let index = start; index < args.length; index += 1) {
|
|
871
|
+
const value = args[index];
|
|
872
|
+
if (!optionsEnded && value === "--") {
|
|
873
|
+
optionsEnded = true;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (!optionsEnded && value.startsWith("-")) {
|
|
877
|
+
if (!isOptionWithInlineValue(value) && VALUE_OPTIONS.has(value)) index += 1;
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
indices.push(index);
|
|
881
|
+
}
|
|
882
|
+
return indices;
|
|
883
|
+
}
|
|
884
|
+
function optionValueArgument(args, name) {
|
|
885
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
886
|
+
const value = args[index];
|
|
887
|
+
if (value === name) {
|
|
888
|
+
const path = args[index + 1];
|
|
889
|
+
if (!path) return null;
|
|
890
|
+
return {
|
|
891
|
+
path,
|
|
892
|
+
replace(projectedPath) {
|
|
893
|
+
args[index + 1] = projectedPath;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
if (value.startsWith(`${name}=`)) {
|
|
898
|
+
const path = value.slice(name.length + 1);
|
|
899
|
+
if (!path) return null;
|
|
900
|
+
return {
|
|
901
|
+
path,
|
|
902
|
+
replace(projectedPath) {
|
|
903
|
+
args[index] = `${name}=${projectedPath}`;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
function positionalSpecArgument(args, start, defaultPath) {
|
|
911
|
+
const positional = positionalIndices(args, start)[0];
|
|
912
|
+
if (positional !== void 0) {
|
|
913
|
+
return {
|
|
914
|
+
path: args[positional],
|
|
915
|
+
replace(projectedPath) {
|
|
916
|
+
args[positional] = projectedPath;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
path: defaultPath,
|
|
922
|
+
replace(projectedPath) {
|
|
923
|
+
args.splice(start, 0, projectedPath);
|
|
924
|
+
}
|
|
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
|
+
}
|
|
932
|
+
if (command === "models" && ["prefetch", "verify-base"].includes(args[1] ?? "")) {
|
|
933
|
+
return positionalSpecArgument(args, 2, join2(cwd, DEFAULT_SPEC_NAME));
|
|
934
|
+
}
|
|
935
|
+
const serves = command === "serve" || command === "models" && args[1] === "serve";
|
|
936
|
+
if (!serves) return null;
|
|
937
|
+
const explicit = optionValueArgument(args, "--spec");
|
|
938
|
+
if (explicit) return explicit;
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
async function pathIsFile(path) {
|
|
942
|
+
return (await stat(path).catch(() => null))?.isFile() ?? false;
|
|
943
|
+
}
|
|
944
|
+
function resolveFromCwd(path, cwd) {
|
|
945
|
+
if (isAbsolute(path)) return path;
|
|
946
|
+
return resolve(cwd, path);
|
|
947
|
+
}
|
|
948
|
+
async function projectSpecArguments(originalArgs, cwd) {
|
|
949
|
+
const args = [...originalArgs];
|
|
950
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
951
|
+
return {
|
|
952
|
+
args,
|
|
953
|
+
droppedSpecKeys: [],
|
|
954
|
+
pathReplacements: [],
|
|
955
|
+
cleanup: async () => {
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const selected = selectedSpecArgument(args, cwd);
|
|
960
|
+
if (!selected) {
|
|
961
|
+
return {
|
|
962
|
+
args,
|
|
963
|
+
droppedSpecKeys: [],
|
|
964
|
+
pathReplacements: [],
|
|
965
|
+
cleanup: async () => {
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
const sourcePath = resolveFromCwd(selected.path, cwd);
|
|
970
|
+
if (!await pathIsFile(sourcePath)) {
|
|
971
|
+
return {
|
|
972
|
+
args,
|
|
973
|
+
droppedSpecKeys: [],
|
|
974
|
+
pathReplacements: [],
|
|
975
|
+
cleanup: async () => {
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const raw = JSON.parse(await readFile(sourcePath, "utf8"));
|
|
980
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
981
|
+
throw new Error(`${sourcePath} must contain a JSON object.`);
|
|
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
|
+
};
|
|
998
|
+
}
|
|
999
|
+
const projected = projectLocalSpec(body);
|
|
1000
|
+
if (projected.droppedKeys.length === 0) {
|
|
1001
|
+
return {
|
|
1002
|
+
args,
|
|
1003
|
+
droppedSpecKeys: [],
|
|
1004
|
+
pathReplacements: [],
|
|
1005
|
+
cleanup: async () => {
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
const temporaryPath = join2(
|
|
1010
|
+
dirname(sourcePath),
|
|
1011
|
+
`.${basename2(sourcePath)}.tt-local-${process.pid}-${randomUUID()}.json`
|
|
1012
|
+
);
|
|
1013
|
+
await writeFile(
|
|
1014
|
+
temporaryPath,
|
|
1015
|
+
`${JSON.stringify(projected.body, null, 2)}
|
|
1016
|
+
`,
|
|
1017
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
1018
|
+
);
|
|
1019
|
+
selected.replace(temporaryPath);
|
|
1020
|
+
return {
|
|
1021
|
+
args,
|
|
1022
|
+
droppedSpecKeys: projected.droppedKeys,
|
|
1023
|
+
pathReplacements: [{
|
|
1024
|
+
sourcePath,
|
|
1025
|
+
projectedPath: temporaryPath
|
|
1026
|
+
}],
|
|
1027
|
+
cleanup: () => rm(temporaryPath, { force: true })
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function resolveLocalCliEntrypoint() {
|
|
1031
|
+
try {
|
|
1032
|
+
return localRequire.resolve("@tuned-tensor/local");
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
"TT Local is not installed. Reinstall @tuned-tensor/cli with its local runtime dependency.",
|
|
1036
|
+
{ cause: error }
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function localCommandStreamsStdout(args) {
|
|
1041
|
+
const serves = args[0] === "serve" || args[0] === "models" && args[1] === "serve";
|
|
1042
|
+
return serves && !args.includes("--print-command") && !args.includes("--help") && !args.includes("-h");
|
|
1043
|
+
}
|
|
1044
|
+
function parseJsonOutput(stdout2) {
|
|
1045
|
+
if (!stdout2.trim()) return { hasJson: false, json: void 0 };
|
|
1046
|
+
try {
|
|
1047
|
+
return { hasJson: true, json: JSON.parse(stdout2) };
|
|
1048
|
+
} catch {
|
|
1049
|
+
return { hasJson: false, json: void 0 };
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function signalExitCode(signal) {
|
|
1053
|
+
if (!signal) return 1;
|
|
1054
|
+
const signalNumber = osConstants.signals[signal];
|
|
1055
|
+
return typeof signalNumber === "number" ? 128 + signalNumber : 1;
|
|
1056
|
+
}
|
|
1057
|
+
function writeStream(stream, chunk) {
|
|
1058
|
+
stream.write(chunk);
|
|
1059
|
+
}
|
|
1060
|
+
function normalizeProjectedPaths(value, replacements) {
|
|
1061
|
+
let normalized = value;
|
|
1062
|
+
for (const replacement of replacements) {
|
|
1063
|
+
const encodedProjectedPath = JSON.stringify(
|
|
1064
|
+
replacement.projectedPath
|
|
1065
|
+
).slice(1, -1);
|
|
1066
|
+
const encodedSourcePath = JSON.stringify(
|
|
1067
|
+
replacement.sourcePath
|
|
1068
|
+
).slice(1, -1);
|
|
1069
|
+
normalized = normalized.replaceAll(encodedProjectedPath, encodedSourcePath).replaceAll(replacement.projectedPath, replacement.sourcePath);
|
|
1070
|
+
}
|
|
1071
|
+
return normalized;
|
|
1072
|
+
}
|
|
1073
|
+
async function spawnLocalCli(args) {
|
|
1074
|
+
const output = args.stdout ?? process.stdout;
|
|
1075
|
+
const errors = args.stderr ?? process.stderr;
|
|
1076
|
+
const pipeInput = Boolean(args.stdin && args.stdin !== process.stdin);
|
|
1077
|
+
const pipeStreamingOutput = args.streamingStdout && Boolean(args.stdout && args.stdout !== process.stdout);
|
|
1078
|
+
const child = spawn(
|
|
1079
|
+
args.nodeExecutable,
|
|
1080
|
+
[args.entrypoint, ...args.commandArgs],
|
|
1081
|
+
{
|
|
1082
|
+
cwd: args.cwd,
|
|
1083
|
+
env: args.env,
|
|
1084
|
+
stdio: [
|
|
1085
|
+
pipeInput ? "pipe" : "inherit",
|
|
1086
|
+
args.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
1087
|
+
"pipe"
|
|
1088
|
+
],
|
|
1089
|
+
detached: process.platform !== "win32"
|
|
1090
|
+
}
|
|
1091
|
+
);
|
|
1092
|
+
if (pipeInput && args.stdin && child.stdin) args.stdin.pipe(child.stdin);
|
|
1093
|
+
let stdout2 = "";
|
|
1094
|
+
let stderr = "";
|
|
1095
|
+
child.stdout?.setEncoding("utf8");
|
|
1096
|
+
child.stderr?.setEncoding("utf8");
|
|
1097
|
+
child.stdout?.on("data", (chunk) => {
|
|
1098
|
+
const normalized = normalizeProjectedPaths(
|
|
1099
|
+
chunk,
|
|
1100
|
+
args.pathReplacements
|
|
1101
|
+
);
|
|
1102
|
+
if (args.streamingStdout) writeStream(output, normalized);
|
|
1103
|
+
else stdout2 += normalized;
|
|
1104
|
+
});
|
|
1105
|
+
child.stderr?.on("data", (chunk) => {
|
|
1106
|
+
const normalized = normalizeProjectedPaths(
|
|
1107
|
+
chunk,
|
|
1108
|
+
args.pathReplacements
|
|
1109
|
+
);
|
|
1110
|
+
stderr += normalized;
|
|
1111
|
+
writeStream(
|
|
1112
|
+
errors,
|
|
1113
|
+
args.jsonMode ? normalized : adaptLocalCliText(normalized)
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
return new Promise((resolveChild) => {
|
|
1117
|
+
let spawnError;
|
|
1118
|
+
const forwardSignal = (signal) => {
|
|
1119
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
1120
|
+
if (child.pid && process.platform !== "win32") {
|
|
1121
|
+
try {
|
|
1122
|
+
process.kill(-child.pid, childSignal);
|
|
1123
|
+
return;
|
|
1124
|
+
} catch {
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
child.kill(childSignal);
|
|
1128
|
+
};
|
|
1129
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
1130
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
1131
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
1132
|
+
const cleanup = () => {
|
|
1133
|
+
process.off("SIGINT", onSigint);
|
|
1134
|
+
process.off("SIGTERM", onSigterm);
|
|
1135
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
1136
|
+
};
|
|
1137
|
+
process.on("SIGINT", onSigint);
|
|
1138
|
+
process.on("SIGTERM", onSigterm);
|
|
1139
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
1140
|
+
child.once("error", (error) => {
|
|
1141
|
+
spawnError = error;
|
|
1142
|
+
});
|
|
1143
|
+
child.once("close", (code, signal) => {
|
|
1144
|
+
cleanup();
|
|
1145
|
+
resolveChild({
|
|
1146
|
+
exitCode: spawnError ? 1 : code ?? signalExitCode(signal),
|
|
1147
|
+
signal,
|
|
1148
|
+
stdout: stdout2,
|
|
1149
|
+
stderr,
|
|
1150
|
+
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
function setupFailure(originalArgs, error) {
|
|
1156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1157
|
+
return {
|
|
1158
|
+
args: [...originalArgs],
|
|
1159
|
+
projectedArgs: [...originalArgs],
|
|
1160
|
+
entrypoint: null,
|
|
1161
|
+
exitCode: 1,
|
|
1162
|
+
signal: null,
|
|
1163
|
+
stdout: "",
|
|
1164
|
+
stderr: "",
|
|
1165
|
+
hasJson: false,
|
|
1166
|
+
json: void 0,
|
|
1167
|
+
streamingStdout: false,
|
|
1168
|
+
droppedSpecKeys: [],
|
|
1169
|
+
errorMessage: message
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
async function runLocalCommand(args, options = {}) {
|
|
1173
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
1174
|
+
let projected;
|
|
1175
|
+
try {
|
|
1176
|
+
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
1177
|
+
projected = await projectSpecArguments(args, cwd);
|
|
1178
|
+
const streamingStdout = localCommandStreamsStdout(projected.args);
|
|
1179
|
+
const result = await spawnLocalCli({
|
|
1180
|
+
entrypoint,
|
|
1181
|
+
commandArgs: projected.args,
|
|
1182
|
+
cwd,
|
|
1183
|
+
env: options.env ?? process.env,
|
|
1184
|
+
nodeExecutable: options.nodeExecutable ?? process.execPath,
|
|
1185
|
+
streamingStdout,
|
|
1186
|
+
jsonMode: options.jsonMode ?? false,
|
|
1187
|
+
stdin: options.stdin,
|
|
1188
|
+
stdout: options.stdout,
|
|
1189
|
+
stderr: options.stderr,
|
|
1190
|
+
pathReplacements: projected.pathReplacements
|
|
1191
|
+
});
|
|
1192
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
1193
|
+
return {
|
|
1194
|
+
args: [...args],
|
|
1195
|
+
projectedArgs: projected.args,
|
|
1196
|
+
entrypoint,
|
|
1197
|
+
...result,
|
|
1198
|
+
...parsed,
|
|
1199
|
+
streamingStdout,
|
|
1200
|
+
droppedSpecKeys: projected.droppedSpecKeys
|
|
1201
|
+
};
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
return setupFailure(args, error);
|
|
1204
|
+
} finally {
|
|
1205
|
+
await projected?.cleanup().catch(() => void 0);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
async function executeLocalCommand(args, options = {}) {
|
|
1209
|
+
const result = await runLocalCommand(args, options);
|
|
1210
|
+
renderLocalOutput(result, {
|
|
1211
|
+
jsonMode: options.jsonMode,
|
|
1212
|
+
stdout: options.stdout
|
|
1213
|
+
});
|
|
1214
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/shell.ts
|
|
1219
|
+
import chalk2 from "chalk";
|
|
1220
|
+
import { stat as stat3 } from "fs/promises";
|
|
1221
|
+
import { homedir as homedir3 } from "os";
|
|
1222
|
+
import { isAbsolute as isAbsolute3, resolve as resolve3 } from "path";
|
|
1223
|
+
import { createInterface } from "readline";
|
|
1224
|
+
|
|
1225
|
+
// src/command-catalog.ts
|
|
1226
|
+
var CLOUD = ["cloud"];
|
|
1227
|
+
var LOCAL = ["local"];
|
|
1228
|
+
var BOTH = ["cloud", "local"];
|
|
1229
|
+
var COMMAND_CATALOG = [
|
|
1230
|
+
{ path: "init", description: "Create a behaviour-spec project.", group: "Workflow", modes: BOTH },
|
|
1231
|
+
{ path: "eval", description: "Validate a managed-service behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1232
|
+
{ path: "push", description: "Create or update the cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1233
|
+
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
1234
|
+
{ path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
|
|
1235
|
+
{ path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
|
|
1236
|
+
{ path: "runs list", description: "List runs.", group: "Inspect", modes: BOTH },
|
|
1237
|
+
{ path: "runs get", description: "Show a run.", group: "Inspect", modes: BOTH },
|
|
1238
|
+
{ path: "runs report", description: "Show a run report.", group: "Inspect", modes: BOTH },
|
|
1239
|
+
{ path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
|
|
1240
|
+
{ path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
|
|
1241
|
+
{ path: "runs estimate", description: "Estimate managed run cost and duration.", group: "Workflow", modes: CLOUD },
|
|
1242
|
+
{ path: "runs start", description: "Start a managed fine-tuning run.", group: "Workflow", modes: CLOUD },
|
|
1243
|
+
{ path: "runs watch", description: "Watch a managed run.", group: "Inspect", modes: CLOUD },
|
|
1244
|
+
{ path: "runs diagnose", description: "Show managed run diagnostics.", group: "Inspect", modes: CLOUD },
|
|
1245
|
+
{ path: "runs cancel", description: "Cancel a managed run.", group: "Workflow", modes: CLOUD },
|
|
1246
|
+
{ path: "models list", description: "List models.", group: "Inspect", modes: BOTH },
|
|
1247
|
+
{ path: "models get", description: "Show a model.", group: "Inspect", modes: BOTH },
|
|
1248
|
+
{ path: "models base", description: "List managed-service base models.", group: "Inspect", modes: CLOUD },
|
|
1249
|
+
{ path: "models download", description: "Download a managed model artifact.", group: "Serving", modes: CLOUD },
|
|
1250
|
+
{ path: "models export", description: "Export a managed model to GGUF or Ollama.", group: "Serving", modes: CLOUD },
|
|
1251
|
+
{ path: "models setup-runtime", description: "Install the managed model-serving runtime.", group: "Serving", modes: CLOUD },
|
|
1252
|
+
{ path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: BOTH },
|
|
1253
|
+
{ path: "models delete", description: "Delete a managed model.", group: "Workflow", modes: CLOUD },
|
|
1254
|
+
{ path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
|
|
1255
|
+
{ path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
1256
|
+
{ path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
1257
|
+
{ path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
|
|
1258
|
+
{ path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
|
|
1259
|
+
{ path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
|
|
1260
|
+
{ path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
|
|
1261
|
+
{ path: "specs list", description: "List cloud behaviour specs.", group: "Inspect", modes: CLOUD },
|
|
1262
|
+
{ path: "specs get", description: "Show a cloud behaviour spec.", group: "Inspect", modes: CLOUD },
|
|
1263
|
+
{ path: "specs create", description: "Create a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1264
|
+
{ path: "specs update", description: "Update a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1265
|
+
{ path: "specs delete", description: "Delete a cloud behaviour spec.", group: "Workflow", modes: CLOUD },
|
|
1266
|
+
{ path: "datasets list", description: "List cloud datasets.", group: "Data", modes: CLOUD },
|
|
1267
|
+
{ path: "datasets get", description: "Show a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1268
|
+
{ path: "datasets upload", description: "Upload a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1269
|
+
{ path: "datasets delete", description: "Delete a cloud dataset.", group: "Data", modes: CLOUD },
|
|
1270
|
+
{ path: "label upload", description: "Start a cloud teacher-labeling job.", group: "Data", modes: CLOUD },
|
|
1271
|
+
{ path: "label watch", description: "Watch a cloud labeling job.", group: "Data", modes: CLOUD },
|
|
1272
|
+
{ path: "label list", description: "List cloud labeling jobs.", group: "Data", modes: CLOUD },
|
|
1273
|
+
{ path: "label status", description: "Show cloud labeling-job status.", group: "Data", modes: CLOUD },
|
|
1274
|
+
{ path: "label rows", description: "Review labeled rows.", group: "Data", modes: CLOUD },
|
|
1275
|
+
{ path: "label accept", description: "Accept labeled rows.", group: "Data", modes: CLOUD },
|
|
1276
|
+
{ path: "label reject", description: "Reject labeled rows.", group: "Data", modes: CLOUD },
|
|
1277
|
+
{ path: "label edit", description: "Edit a labeled row.", group: "Data", modes: CLOUD },
|
|
1278
|
+
{ path: "label promote", description: "Promote reviewed rows to a dataset.", group: "Data", modes: CLOUD },
|
|
1279
|
+
{ path: "label cancel", description: "Cancel a labeling job.", group: "Data", modes: CLOUD },
|
|
1280
|
+
{ path: "auth login", description: "Store a cloud API key.", group: "Account", modes: CLOUD },
|
|
1281
|
+
{ path: "auth logout", description: "Remove stored cloud credentials.", group: "Account", modes: CLOUD },
|
|
1282
|
+
{ path: "auth status", description: "Show cloud authentication status.", group: "Account", modes: CLOUD },
|
|
1283
|
+
{ path: "balance", description: "Show cloud credit balance.", group: "Account", modes: CLOUD },
|
|
1284
|
+
{ path: "topup", description: "Open a cloud credit checkout.", group: "Account", modes: CLOUD },
|
|
1285
|
+
{ path: "info", description: "Show TT Local package information.", group: "Inspect", modes: LOCAL }
|
|
1286
|
+
];
|
|
1287
|
+
var SLASH_COMMANDS = [
|
|
1288
|
+
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
1289
|
+
{ path: "/status", description: "Show lightweight workflow status." },
|
|
1290
|
+
{ path: "/context", description: "Show the current project and backend context." },
|
|
1291
|
+
{ path: "/mode cloud", description: "Use the managed cloud workflow." },
|
|
1292
|
+
{ path: "/mode local", description: "Use the local GPU workflow." },
|
|
1293
|
+
{ path: "/model", description: "Show the active model; /model <id> activates a local model." },
|
|
1294
|
+
{ path: "/cd", description: "Change the shell's working directory." },
|
|
1295
|
+
{ path: "/clear", description: "Clear the terminal." },
|
|
1296
|
+
{ path: "/exit", description: "Exit the TT shell." }
|
|
1297
|
+
];
|
|
1298
|
+
function catalogForMode(mode) {
|
|
1299
|
+
return COMMAND_CATALOG.filter((command) => command.modes.includes(mode));
|
|
1300
|
+
}
|
|
1301
|
+
function commandPathsForMode(mode) {
|
|
1302
|
+
return [...new Set(catalogForMode(mode).map((command) => command.path))].sort((left, right) => left.localeCompare(right));
|
|
1303
|
+
}
|
|
1304
|
+
function groupedCatalog(mode, query) {
|
|
1305
|
+
const normalizedQuery = query?.trim().toLowerCase();
|
|
1306
|
+
const matches = catalogForMode(mode).filter((command) => {
|
|
1307
|
+
if (!normalizedQuery) return true;
|
|
1308
|
+
return command.path.toLowerCase().includes(normalizedQuery) || command.description.toLowerCase().includes(normalizedQuery) || command.group.toLowerCase().includes(normalizedQuery);
|
|
1309
|
+
});
|
|
1310
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1311
|
+
for (const command of matches) {
|
|
1312
|
+
const group = groups.get(command.group) ?? [];
|
|
1313
|
+
group.push(command);
|
|
1314
|
+
groups.set(command.group, group);
|
|
1315
|
+
}
|
|
1316
|
+
return groups;
|
|
1317
|
+
}
|
|
1318
|
+
function completionTarget(line, activeMode) {
|
|
1319
|
+
const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
|
|
1320
|
+
const content = line.slice(leadingWhitespace.length);
|
|
1321
|
+
const override = content.match(/^(cloud|local)(?:\s+|$)/);
|
|
1322
|
+
if (!override) {
|
|
1323
|
+
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
1324
|
+
}
|
|
1325
|
+
const mode = override[1];
|
|
1326
|
+
const prefix = `${leadingWhitespace}${mode} `;
|
|
1327
|
+
return {
|
|
1328
|
+
prefix,
|
|
1329
|
+
mode,
|
|
1330
|
+
fragment: content.slice(override[0].length)
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
function createCommandCompleter(getMode) {
|
|
1334
|
+
return (line) => {
|
|
1335
|
+
const trimmed = line.trimStart();
|
|
1336
|
+
if (trimmed.startsWith("/")) {
|
|
1337
|
+
const candidates = SLASH_COMMANDS.map((command) => command.path);
|
|
1338
|
+
const matches2 = candidates.filter((candidate) => candidate.startsWith(trimmed));
|
|
1339
|
+
return [matches2.length > 0 ? matches2 : candidates, line];
|
|
1340
|
+
}
|
|
1341
|
+
if (trimmed.startsWith("?")) {
|
|
1342
|
+
return [["?"], line];
|
|
1343
|
+
}
|
|
1344
|
+
const { prefix, mode, fragment } = completionTarget(line, getMode());
|
|
1345
|
+
const paths = commandPathsForMode(mode);
|
|
1346
|
+
const targetCandidates = paths.map((path) => `${prefix}${path}`);
|
|
1347
|
+
if (!prefix.trim()) targetCandidates.unshift("cloud ", "local ");
|
|
1348
|
+
const normalizedLine = line.trimStart().toLowerCase();
|
|
1349
|
+
const normalizedFragment = fragment.toLowerCase();
|
|
1350
|
+
const matches = targetCandidates.filter((candidate) => {
|
|
1351
|
+
if (prefix.trim()) return candidate.toLowerCase().startsWith(`${prefix.toLowerCase()}${normalizedFragment}`);
|
|
1352
|
+
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
1353
|
+
});
|
|
1354
|
+
return [matches.length > 0 ? matches : targetCandidates, line];
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/shell-context.ts
|
|
1359
|
+
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
1360
|
+
import { homedir as homedir2 } from "os";
|
|
1361
|
+
import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
|
|
1362
|
+
async function readJsonObject(path) {
|
|
1363
|
+
try {
|
|
1364
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
1365
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1366
|
+
return { found: true, invalid: true };
|
|
1367
|
+
}
|
|
1368
|
+
return {
|
|
1369
|
+
found: true,
|
|
1370
|
+
value: parsed,
|
|
1371
|
+
invalid: false
|
|
1372
|
+
};
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
if (error.code === "ENOENT") {
|
|
1375
|
+
return { found: false, invalid: false };
|
|
1376
|
+
}
|
|
1377
|
+
return { found: true, invalid: true };
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function stringField(value, key) {
|
|
1381
|
+
const field = value?.[key];
|
|
1382
|
+
return typeof field === "string" && field.trim() ? field : void 0;
|
|
1383
|
+
}
|
|
1384
|
+
function expandPath(value, baseDirectory, homeDirectory) {
|
|
1385
|
+
if (value === "~") return homeDirectory;
|
|
1386
|
+
if (value.startsWith("~/")) return resolve2(homeDirectory, value.slice(2));
|
|
1387
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(baseDirectory, value);
|
|
1388
|
+
}
|
|
1389
|
+
function environmentHome(env) {
|
|
1390
|
+
return env.HOME ? resolve2(env.HOME) : homedir2();
|
|
1391
|
+
}
|
|
1392
|
+
function cloudConfigPath(env, homeDirectory) {
|
|
1393
|
+
const configHome = env.XDG_CONFIG_HOME ? resolve2(env.XDG_CONFIG_HOME) : join3(homeDirectory, ".config");
|
|
1394
|
+
return join3(configHome, "tuned-tensor", "config.json");
|
|
1395
|
+
}
|
|
1396
|
+
function redactApiKey(key) {
|
|
1397
|
+
if (!key) return void 0;
|
|
1398
|
+
if (key.length <= 4) return "\u2026";
|
|
1399
|
+
const prefixLength = key.length > 8 ? 8 : Math.max(1, Math.floor(key.length / 2));
|
|
1400
|
+
return `${key.slice(0, prefixLength)}\u2026`;
|
|
1401
|
+
}
|
|
1402
|
+
function targetFromEnvironment(env) {
|
|
1403
|
+
const value = env.TT_TARGET?.trim().toLowerCase();
|
|
1404
|
+
return value === "cloud" || value === "local" ? value : void 0;
|
|
1405
|
+
}
|
|
1406
|
+
async function adjacentFile(path) {
|
|
1407
|
+
try {
|
|
1408
|
+
return (await stat2(path)).isFile();
|
|
1409
|
+
} catch {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
async function readActiveModelId(storeRoot) {
|
|
1414
|
+
const result = await readJsonObject(join3(storeRoot, "active-model.json"));
|
|
1415
|
+
if (!result.found || result.invalid) return void 0;
|
|
1416
|
+
const modelId = result.value?.model_id;
|
|
1417
|
+
if (modelId === null) return "base";
|
|
1418
|
+
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
1419
|
+
}
|
|
1420
|
+
async function readLatestRun(storeRoot) {
|
|
1421
|
+
const runsRoot = join3(storeRoot, "runs");
|
|
1422
|
+
let names;
|
|
1423
|
+
try {
|
|
1424
|
+
names = (await readdir(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1425
|
+
} catch {
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
const states = await Promise.all(names.map(async (name) => {
|
|
1429
|
+
const result = await readJsonObject(join3(runsRoot, name, "state.json"));
|
|
1430
|
+
if (!result.found || result.invalid || !result.value) return void 0;
|
|
1431
|
+
const id = stringField(result.value, "id") ?? name;
|
|
1432
|
+
const state = {
|
|
1433
|
+
id,
|
|
1434
|
+
status: stringField(result.value, "status"),
|
|
1435
|
+
specName: stringField(result.value, "spec_name"),
|
|
1436
|
+
updatedAt: stringField(result.value, "updated_at")
|
|
1437
|
+
};
|
|
1438
|
+
return state;
|
|
1439
|
+
}));
|
|
1440
|
+
return states.filter((state) => Boolean(state)).sort((left, right) => {
|
|
1441
|
+
const leftUpdated = left.updatedAt ?? "";
|
|
1442
|
+
const rightUpdated = right.updatedAt ?? "";
|
|
1443
|
+
if (leftUpdated !== rightUpdated) {
|
|
1444
|
+
return rightUpdated.localeCompare(leftUpdated);
|
|
1445
|
+
}
|
|
1446
|
+
return right.id.localeCompare(left.id);
|
|
1447
|
+
})[0];
|
|
1448
|
+
}
|
|
1449
|
+
async function discoverShellContext(options = {}) {
|
|
1450
|
+
const env = options.env ?? process.env;
|
|
1451
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
1452
|
+
const homeDirectory = environmentHome(env);
|
|
1453
|
+
const warnings = [];
|
|
1454
|
+
const specPath = join3(cwd, "tunedtensor.json");
|
|
1455
|
+
const specJson = await readJsonObject(specPath);
|
|
1456
|
+
let spec;
|
|
1457
|
+
if (specJson.found) {
|
|
1458
|
+
if (specJson.invalid || !specJson.value) {
|
|
1459
|
+
spec = { path: specPath, parseError: true };
|
|
1460
|
+
warnings.push("tunedtensor.json could not be parsed.");
|
|
1461
|
+
} else {
|
|
1462
|
+
const examples = specJson.value.examples;
|
|
1463
|
+
spec = {
|
|
1464
|
+
path: specPath,
|
|
1465
|
+
id: stringField(specJson.value, "id"),
|
|
1466
|
+
name: stringField(specJson.value, "name"),
|
|
1467
|
+
baseModel: stringField(specJson.value, "base_model"),
|
|
1468
|
+
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
1469
|
+
parseError: false
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const localConfigPath = join3(cwd, "local-runner.json");
|
|
1474
|
+
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
1475
|
+
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
1476
|
+
if (localConfigJson.invalid) {
|
|
1477
|
+
warnings.push("local-runner.json could not be parsed.");
|
|
1478
|
+
}
|
|
1479
|
+
const localConfigDirectory = hasAdjacentLocalConfig ? dirname2(localConfigPath) : cwd;
|
|
1480
|
+
const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
|
|
1481
|
+
const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
|
|
1482
|
+
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve2(cwd, ".tt-local", "artifacts");
|
|
1483
|
+
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join3(homeDirectory, ".tuned-tensor-local");
|
|
1484
|
+
const cloudPath = cloudConfigPath(env, homeDirectory);
|
|
1485
|
+
const cloudConfigJson = await readJsonObject(cloudPath);
|
|
1486
|
+
if (cloudConfigJson.invalid) {
|
|
1487
|
+
warnings.push("The Tuned Tensor cloud config could not be parsed.");
|
|
1488
|
+
}
|
|
1489
|
+
const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
|
|
1490
|
+
const storedApiKey = stringField(cloudConfigJson.value, "api_key");
|
|
1491
|
+
const apiKey = environmentApiKey || storedApiKey;
|
|
1492
|
+
const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
|
|
1493
|
+
const environmentTarget = targetFromEnvironment(env);
|
|
1494
|
+
if (env.TT_TARGET && !environmentTarget) {
|
|
1495
|
+
warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
|
|
1496
|
+
}
|
|
1497
|
+
const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
|
|
1498
|
+
const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
|
|
1499
|
+
const [activeModelId, latestRun] = await Promise.all([
|
|
1500
|
+
readActiveModelId(storeRoot),
|
|
1501
|
+
readLatestRun(storeRoot)
|
|
1502
|
+
]);
|
|
1503
|
+
return {
|
|
1504
|
+
cwd,
|
|
1505
|
+
projectName: basename3(cwd) || cwd,
|
|
1506
|
+
inferredTarget,
|
|
1507
|
+
targetSource,
|
|
1508
|
+
spec,
|
|
1509
|
+
cloud: {
|
|
1510
|
+
authenticated: Boolean(apiKey),
|
|
1511
|
+
keyPrefix: redactApiKey(apiKey),
|
|
1512
|
+
baseUrl,
|
|
1513
|
+
configPath: cloudPath,
|
|
1514
|
+
configFound: cloudConfigJson.found
|
|
1515
|
+
},
|
|
1516
|
+
local: {
|
|
1517
|
+
configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
|
|
1518
|
+
artifactRoot,
|
|
1519
|
+
storeRoot,
|
|
1520
|
+
activeModelId,
|
|
1521
|
+
latestRun
|
|
1522
|
+
},
|
|
1523
|
+
warnings
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
function valueOrDash(value) {
|
|
1527
|
+
return value === void 0 || value === "" ? "\u2014" : String(value);
|
|
1528
|
+
}
|
|
1529
|
+
function targetSourceLabel(source) {
|
|
1530
|
+
switch (source) {
|
|
1531
|
+
case "environment":
|
|
1532
|
+
return "TT_TARGET";
|
|
1533
|
+
case "adjacent-config":
|
|
1534
|
+
return "adjacent local-runner.json";
|
|
1535
|
+
default:
|
|
1536
|
+
return "default";
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function formatShellContext(context, selectedTarget, targetSource) {
|
|
1540
|
+
const specLabel = context.spec ? context.spec.parseError ? `${context.spec.path} (invalid JSON)` : context.spec.path : "not found";
|
|
1541
|
+
const sourceLabel = targetSource === "session" ? "session" : targetSource === "command-option" ? "--target" : targetSourceLabel(targetSource);
|
|
1542
|
+
const lines = [
|
|
1543
|
+
`Target ${selectedTarget} (${sourceLabel})`,
|
|
1544
|
+
`Project ${context.projectName}`,
|
|
1545
|
+
`Directory ${context.cwd}`,
|
|
1546
|
+
`Spec ${specLabel}`,
|
|
1547
|
+
`Spec ID ${valueOrDash(context.spec?.id)}`,
|
|
1548
|
+
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
1549
|
+
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
1550
|
+
`Cloud endpoint ${context.cloud.baseUrl}`,
|
|
1551
|
+
`Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
|
|
1552
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1553
|
+
`Artifact root ${context.local.artifactRoot}`,
|
|
1554
|
+
`Store root ${context.local.storeRoot}`
|
|
1555
|
+
];
|
|
1556
|
+
for (const warning of context.warnings) lines.push(`Warning ${warning}`);
|
|
1557
|
+
return lines;
|
|
1558
|
+
}
|
|
1559
|
+
function formatShellStatus(context, selectedTarget) {
|
|
1560
|
+
const lines = [
|
|
1561
|
+
`Workflow ${selectedTarget}`,
|
|
1562
|
+
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
|
|
1563
|
+
];
|
|
1564
|
+
if (selectedTarget === "cloud") {
|
|
1565
|
+
lines.push(
|
|
1566
|
+
`Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
|
|
1567
|
+
`Endpoint ${context.cloud.baseUrl}`,
|
|
1568
|
+
"Remote status not queried"
|
|
1569
|
+
);
|
|
1570
|
+
} else {
|
|
1571
|
+
lines.push(
|
|
1572
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1573
|
+
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
1574
|
+
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
1575
|
+
"Host checks not run (use doctor)"
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
return lines;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/shell.ts
|
|
1582
|
+
var ShellParseError = class extends Error {
|
|
1583
|
+
constructor(message) {
|
|
1584
|
+
super(message);
|
|
1585
|
+
this.name = "ShellParseError";
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
function operatorError(operator) {
|
|
1589
|
+
return new ShellParseError(
|
|
1590
|
+
`Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
function tokenizeShellInput(input) {
|
|
1594
|
+
if (input.includes("\0")) {
|
|
1595
|
+
throw new ShellParseError("Command input must not contain NUL bytes.");
|
|
1596
|
+
}
|
|
1597
|
+
if (/[\r\n]/.test(input)) {
|
|
1598
|
+
throw new ShellParseError("Enter one command at a time.");
|
|
1599
|
+
}
|
|
1600
|
+
if (input.trimStart().startsWith("!")) {
|
|
1601
|
+
throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
|
|
1602
|
+
}
|
|
1603
|
+
const tokens = [];
|
|
1604
|
+
let token = "";
|
|
1605
|
+
let tokenStarted = false;
|
|
1606
|
+
let quote = "none";
|
|
1607
|
+
const finishToken = () => {
|
|
1608
|
+
if (!tokenStarted) return;
|
|
1609
|
+
tokens.push(token);
|
|
1610
|
+
token = "";
|
|
1611
|
+
tokenStarted = false;
|
|
1612
|
+
};
|
|
1613
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1614
|
+
const character = input[index];
|
|
1615
|
+
if (quote === "single") {
|
|
1616
|
+
if (character === "'") {
|
|
1617
|
+
quote = "none";
|
|
1618
|
+
} else {
|
|
1619
|
+
token += character;
|
|
1620
|
+
}
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
if (quote === "double") {
|
|
1624
|
+
if (character === '"') {
|
|
1625
|
+
quote = "none";
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
if (character === "\\") {
|
|
1629
|
+
const escaped = input[index + 1];
|
|
1630
|
+
if (escaped === void 0) {
|
|
1631
|
+
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1632
|
+
}
|
|
1633
|
+
token += escaped;
|
|
1634
|
+
tokenStarted = true;
|
|
1635
|
+
index += 1;
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
1639
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
1640
|
+
throw operatorError("$(\u2026)");
|
|
1641
|
+
}
|
|
1642
|
+
token += character;
|
|
1643
|
+
tokenStarted = true;
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (/\s/.test(character)) {
|
|
1647
|
+
finishToken();
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
if (character === "'") {
|
|
1651
|
+
quote = "single";
|
|
1652
|
+
tokenStarted = true;
|
|
1653
|
+
continue;
|
|
1654
|
+
}
|
|
1655
|
+
if (character === '"') {
|
|
1656
|
+
quote = "double";
|
|
1657
|
+
tokenStarted = true;
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
if (character === "\\") {
|
|
1661
|
+
const escaped = input[index + 1];
|
|
1662
|
+
if (escaped === void 0) {
|
|
1663
|
+
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1664
|
+
}
|
|
1665
|
+
token += escaped;
|
|
1666
|
+
tokenStarted = true;
|
|
1667
|
+
index += 1;
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
1671
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
1672
|
+
throw operatorError("$(\u2026)");
|
|
1673
|
+
}
|
|
1674
|
+
if (character === "|" || character === ">" || character === "<" || character === ";" || character === "&") {
|
|
1675
|
+
const doubled = input[index + 1] === character && (character === "|" || character === "&");
|
|
1676
|
+
throw operatorError(doubled ? character.repeat(2) : character);
|
|
1677
|
+
}
|
|
1678
|
+
token += character;
|
|
1679
|
+
tokenStarted = true;
|
|
1680
|
+
}
|
|
1681
|
+
if (quote !== "none") {
|
|
1682
|
+
throw new ShellParseError(
|
|
1683
|
+
`Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
finishToken();
|
|
1687
|
+
return tokens;
|
|
1688
|
+
}
|
|
1689
|
+
function routeShellCommand(input, activeMode) {
|
|
1690
|
+
let args = tokenizeShellInput(input);
|
|
1691
|
+
if (args.length === 0) return null;
|
|
1692
|
+
if (args[0] === "tt") {
|
|
1693
|
+
args = args.slice(1);
|
|
1694
|
+
if (args.length === 0) {
|
|
1695
|
+
throw new ShellParseError("The TT shell is already open; enter a command such as `runs list`.");
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (args[0]?.startsWith("/")) {
|
|
1699
|
+
throw new ShellParseError("Slash commands are session commands and cannot be routed to a workflow.");
|
|
1700
|
+
}
|
|
1701
|
+
const override = args[0];
|
|
1702
|
+
if (override === "cloud" || override === "local") {
|
|
1703
|
+
if (args.length === 1) {
|
|
1704
|
+
throw new ShellParseError(`Add a ${override} command, or use \`/mode ${override}\` to switch workflows.`);
|
|
1705
|
+
}
|
|
1706
|
+
return { target: override, args: args.slice(1) };
|
|
1707
|
+
}
|
|
1708
|
+
return { target: activeMode, args };
|
|
1709
|
+
}
|
|
1710
|
+
var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
1711
|
+
"help",
|
|
1712
|
+
"status",
|
|
1713
|
+
"context",
|
|
1714
|
+
"mode",
|
|
1715
|
+
"model",
|
|
1716
|
+
"clear",
|
|
1717
|
+
"cd",
|
|
1718
|
+
"exit"
|
|
1719
|
+
]);
|
|
1720
|
+
var WORKFLOW_ROOT_COMMANDS = new Set(
|
|
1721
|
+
COMMAND_CATALOG.map((command) => command.path.split(" ", 1)[0])
|
|
1722
|
+
);
|
|
1723
|
+
function editDistance(left, right) {
|
|
1724
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
1725
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
1726
|
+
let diagonal = previous[0];
|
|
1727
|
+
previous[0] = i;
|
|
1728
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
1729
|
+
const above = previous[j];
|
|
1730
|
+
previous[j] = Math.min(
|
|
1731
|
+
previous[j] + 1,
|
|
1732
|
+
previous[j - 1] + 1,
|
|
1733
|
+
diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
1734
|
+
);
|
|
1735
|
+
diagonal = above;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return previous[right.length];
|
|
1739
|
+
}
|
|
1740
|
+
function suggestSlashCommands(rawName) {
|
|
1741
|
+
const names = [...SLASH_NAMES].sort();
|
|
1742
|
+
const prefixMatches = names.filter((name) => name.startsWith(rawName));
|
|
1743
|
+
if (prefixMatches.length > 0) return prefixMatches;
|
|
1744
|
+
return names.filter((name) => editDistance(name, rawName) <= 2);
|
|
1745
|
+
}
|
|
1746
|
+
function unknownSlashCommandError(rawName) {
|
|
1747
|
+
if (WORKFLOW_ROOT_COMMANDS.has(rawName)) {
|
|
1748
|
+
return new ShellParseError(
|
|
1749
|
+
`Unknown session command: /${rawName}. Workflow commands need no slash \u2014 try "${rawName} --help".`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const suggestions = suggestSlashCommands(rawName);
|
|
1753
|
+
const hint = suggestions.length > 0 ? ` Did you mean ${suggestions.map((name) => `/${name}`).join(" or ")}?` : "";
|
|
1754
|
+
return new ShellParseError(
|
|
1755
|
+
`Unknown session command: /${rawName}.${hint} Use /help to see available commands.`
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
function parseSlashCommand(input) {
|
|
1759
|
+
const trimmed = input.trim();
|
|
1760
|
+
if (!trimmed) return null;
|
|
1761
|
+
if (trimmed === "?") return { name: "help", args: [] };
|
|
1762
|
+
if (trimmed.startsWith("? ")) {
|
|
1763
|
+
return {
|
|
1764
|
+
name: "help",
|
|
1765
|
+
args: tokenizeShellInput(trimmed.slice(1))
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
if (!trimmed.startsWith("/")) return null;
|
|
1769
|
+
if (trimmed === "/") return { name: "palette", args: [] };
|
|
1770
|
+
const tokens = tokenizeShellInput(trimmed);
|
|
1771
|
+
const rawName = tokens[0].slice(1).toLowerCase();
|
|
1772
|
+
if (!SLASH_NAMES.has(rawName)) {
|
|
1773
|
+
throw unknownSlashCommandError(rawName);
|
|
1774
|
+
}
|
|
1775
|
+
return {
|
|
1776
|
+
name: rawName,
|
|
1777
|
+
args: tokens.slice(1)
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
function errorMessage(error) {
|
|
1781
|
+
return error instanceof Error ? error.message : String(error);
|
|
1782
|
+
}
|
|
1783
|
+
var successMark = () => chalk2.green("\u2713");
|
|
1784
|
+
var errorMark = () => chalk2.red("\u2717");
|
|
1785
|
+
var accent = chalk2.hex("#8B5CF6");
|
|
1786
|
+
var DETAIL_LABEL_WIDTH = 15;
|
|
1787
|
+
function styleDetailLines(lines) {
|
|
1788
|
+
return lines.map((line) => {
|
|
1789
|
+
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
1790
|
+
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
1791
|
+
if (!label.trim()) return line;
|
|
1792
|
+
const color = label.startsWith("Warning") ? chalk2.yellow : accent.bold;
|
|
1793
|
+
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
function detailLine(label, value) {
|
|
1797
|
+
return `${label.padEnd(DETAIL_LABEL_WIDTH)}${value}`;
|
|
1798
|
+
}
|
|
1799
|
+
function assertNoArgs(command, args) {
|
|
1800
|
+
if (args.length > 0) {
|
|
1801
|
+
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function expandDirectory(value, cwd, env) {
|
|
1805
|
+
const home = env.HOME ? resolve3(env.HOME) : homedir3();
|
|
1806
|
+
if (value === "~") return home;
|
|
1807
|
+
if (value.startsWith("~/")) return resolve3(home, value.slice(2));
|
|
1808
|
+
return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
|
|
1809
|
+
}
|
|
1810
|
+
function helpText(mode, query, palette = false) {
|
|
1811
|
+
const groups = groupedCatalog(mode, query);
|
|
1812
|
+
const lines = [];
|
|
1813
|
+
lines.push(accent.bold(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
|
|
1814
|
+
if (groups.size === 0) {
|
|
1815
|
+
lines.push(chalk2.dim(" No matching commands."));
|
|
1816
|
+
} else {
|
|
1817
|
+
for (const [group, commands] of groups) {
|
|
1818
|
+
lines.push("");
|
|
1819
|
+
lines.push(chalk2.bold(group));
|
|
1820
|
+
for (const command of commands) {
|
|
1821
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
if (!query) {
|
|
1826
|
+
lines.push("");
|
|
1827
|
+
lines.push(chalk2.bold("Session"));
|
|
1828
|
+
for (const command of SLASH_COMMANDS) {
|
|
1829
|
+
lines.push(` ${accent(command.path.padEnd(22))} ${command.description}`);
|
|
1830
|
+
}
|
|
1831
|
+
lines.push(` ${accent("?".padEnd(22))} Alias for /help.`);
|
|
1832
|
+
lines.push(chalk2.dim(
|
|
1833
|
+
"\nTab completes commands. Shell operators and shell escapes are disabled."
|
|
1834
|
+
));
|
|
1835
|
+
}
|
|
1836
|
+
return `${lines.join("\n")}
|
|
1837
|
+
`;
|
|
1838
|
+
}
|
|
1839
|
+
function activeModelLabel(snapshot) {
|
|
1840
|
+
return snapshot.mode === "local" ? snapshot.context.local.activeModelId ?? "base" : snapshot.context.spec?.baseModel ?? "\u2014";
|
|
1841
|
+
}
|
|
1842
|
+
function logoRows() {
|
|
1843
|
+
const muted = chalk2.dim("\u2588\u2588");
|
|
1844
|
+
return [
|
|
1845
|
+
`${chalk2.hex("#A78BFA")("\u2588\u2588")} ${muted} ${muted}`,
|
|
1846
|
+
`${muted} ${accent("\u2588\u2588")} ${muted}`,
|
|
1847
|
+
`${muted} ${muted} ${chalk2.hex("#7C3AED")("\u2588\u2588")}`
|
|
1848
|
+
];
|
|
1849
|
+
}
|
|
1850
|
+
function renderShellBanner(snapshot) {
|
|
1851
|
+
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1852
|
+
const heading = snapshot.version ? `${accent.bold("Tuned Tensor")} ${chalk2.dim(`v${snapshot.version}`)}` : accent.bold("Tuned Tensor");
|
|
1853
|
+
const textRows = [
|
|
1854
|
+
heading,
|
|
1855
|
+
`${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec} \xB7 model ${activeModelLabel(snapshot)}`,
|
|
1856
|
+
chalk2.dim("/help for commands \xB7 Tab completes \xB7 /mode cloud|local switches")
|
|
1857
|
+
];
|
|
1858
|
+
const logo = logoRows();
|
|
1859
|
+
const lines = textRows.map((row, index) => `${logo[index]} ${row}`);
|
|
1860
|
+
return `${lines.join("\n")}
|
|
1861
|
+
|
|
1862
|
+
`;
|
|
1863
|
+
}
|
|
1864
|
+
function renderShellPrompt(snapshot) {
|
|
1865
|
+
return `${accent("tt")} ${chalk2.bold(snapshot.mode)} ${chalk2.dim(snapshot.context.projectName)} \u203A `;
|
|
1866
|
+
}
|
|
1867
|
+
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
1868
|
+
constructor(runner, io, env, contextProvider, version, initialContext) {
|
|
1869
|
+
this.runner = runner;
|
|
1870
|
+
this.io = io;
|
|
1871
|
+
this.env = env;
|
|
1872
|
+
this.contextProvider = contextProvider;
|
|
1873
|
+
this.version = version;
|
|
1874
|
+
this.mode = initialContext.inferredTarget;
|
|
1875
|
+
this.modeSource = initialContext.targetSource;
|
|
1876
|
+
this.cwd = initialContext.cwd;
|
|
1877
|
+
this.context = initialContext;
|
|
1878
|
+
}
|
|
1879
|
+
mode;
|
|
1880
|
+
modeSource;
|
|
1881
|
+
cwd;
|
|
1882
|
+
context;
|
|
1883
|
+
static async create(options) {
|
|
1884
|
+
const env = options.env ?? process.env;
|
|
1885
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
1886
|
+
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1887
|
+
const initialContext = await contextProvider({ cwd, env });
|
|
1888
|
+
return new _TunedTensorShellSession(
|
|
1889
|
+
options.runner,
|
|
1890
|
+
options.io,
|
|
1891
|
+
env,
|
|
1892
|
+
contextProvider,
|
|
1893
|
+
options.version,
|
|
1894
|
+
initialContext
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
snapshot() {
|
|
1898
|
+
return {
|
|
1899
|
+
mode: this.mode,
|
|
1900
|
+
modeSource: this.modeSource,
|
|
1901
|
+
cwd: this.cwd,
|
|
1902
|
+
context: this.context,
|
|
1903
|
+
version: this.version
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
prompt() {
|
|
1907
|
+
return renderShellPrompt(this.snapshot());
|
|
1908
|
+
}
|
|
1909
|
+
banner() {
|
|
1910
|
+
return renderShellBanner(this.snapshot());
|
|
1911
|
+
}
|
|
1912
|
+
async refreshContext() {
|
|
1913
|
+
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
1914
|
+
this.cwd = this.context.cwd;
|
|
1915
|
+
if (this.modeSource !== "session" && this.modeSource !== "environment") {
|
|
1916
|
+
this.mode = this.context.inferredTarget;
|
|
1917
|
+
this.modeSource = this.context.targetSource;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
writeLines(lines) {
|
|
1921
|
+
this.io.write(`${lines.join("\n")}
|
|
1922
|
+
`);
|
|
1923
|
+
}
|
|
1924
|
+
modelLines() {
|
|
1925
|
+
if (this.mode === "local") {
|
|
1926
|
+
return styleDetailLines([
|
|
1927
|
+
detailLine("Active model", this.context.local.activeModelId ?? "base"),
|
|
1928
|
+
detailLine("Change", "/model <id> to activate a verified local model")
|
|
1929
|
+
]);
|
|
1930
|
+
}
|
|
1931
|
+
return styleDetailLines([
|
|
1932
|
+
detailLine("Base model", this.context.spec?.baseModel ?? "\u2014"),
|
|
1933
|
+
detailLine("Change", "edit base_model in tunedtensor.json, then run push")
|
|
1934
|
+
]);
|
|
1935
|
+
}
|
|
1936
|
+
async handleSlash(command) {
|
|
1937
|
+
switch (command.name) {
|
|
1938
|
+
case "palette":
|
|
1939
|
+
this.io.write(helpText(this.mode, void 0, true));
|
|
1940
|
+
return "continue";
|
|
1941
|
+
case "help":
|
|
1942
|
+
this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
|
|
1943
|
+
return "continue";
|
|
1944
|
+
case "status":
|
|
1945
|
+
assertNoArgs("status", command.args);
|
|
1946
|
+
await this.refreshContext();
|
|
1947
|
+
this.writeLines(styleDetailLines(formatShellStatus(this.context, this.mode)));
|
|
1948
|
+
return "continue";
|
|
1949
|
+
case "context":
|
|
1950
|
+
assertNoArgs("context", command.args);
|
|
1951
|
+
await this.refreshContext();
|
|
1952
|
+
this.writeLines(styleDetailLines(
|
|
1953
|
+
formatShellContext(this.context, this.mode, this.modeSource)
|
|
1954
|
+
));
|
|
1955
|
+
return "continue";
|
|
1956
|
+
case "mode": {
|
|
1957
|
+
if (command.args.length === 0) {
|
|
1958
|
+
this.io.write(`Current workflow: ${this.mode}
|
|
1959
|
+
`);
|
|
1960
|
+
return "continue";
|
|
1961
|
+
}
|
|
1962
|
+
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1963
|
+
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1964
|
+
}
|
|
1965
|
+
this.mode = command.args[0];
|
|
1966
|
+
this.modeSource = "session";
|
|
1967
|
+
this.io.write(`${successMark()} Workflow switched to ${this.mode}.
|
|
1968
|
+
`);
|
|
1969
|
+
return "continue";
|
|
1970
|
+
}
|
|
1971
|
+
case "model": {
|
|
1972
|
+
if (command.args.length === 0) {
|
|
1973
|
+
await this.refreshContext();
|
|
1974
|
+
this.writeLines(this.modelLines());
|
|
1975
|
+
return "continue";
|
|
1976
|
+
}
|
|
1977
|
+
if (command.args.length !== 1) {
|
|
1978
|
+
throw new ShellParseError("Usage: /model [model-id]");
|
|
1979
|
+
}
|
|
1980
|
+
if (this.mode !== "local") {
|
|
1981
|
+
throw new ShellParseError(
|
|
1982
|
+
"Activating models is a local workflow action. Use /mode local first; cloud specs change base_model in tunedtensor.json."
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
await this.runner({
|
|
1986
|
+
target: "local",
|
|
1987
|
+
args: ["models", "activate", command.args[0]],
|
|
1988
|
+
cwd: this.cwd
|
|
1989
|
+
});
|
|
1990
|
+
await this.refreshContext();
|
|
1991
|
+
return "continue";
|
|
1992
|
+
}
|
|
1993
|
+
case "clear":
|
|
1994
|
+
assertNoArgs("clear", command.args);
|
|
1995
|
+
this.io.clear();
|
|
1996
|
+
return "continue";
|
|
1997
|
+
case "cd": {
|
|
1998
|
+
if (command.args.length === 0) {
|
|
1999
|
+
this.io.write(`${this.cwd}
|
|
2000
|
+
`);
|
|
2001
|
+
return "continue";
|
|
2002
|
+
}
|
|
2003
|
+
if (command.args.length !== 1) {
|
|
2004
|
+
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
2005
|
+
}
|
|
2006
|
+
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
2007
|
+
const metadata = await stat3(nextDirectory).catch(() => null);
|
|
2008
|
+
if (!metadata?.isDirectory()) {
|
|
2009
|
+
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
2010
|
+
}
|
|
2011
|
+
this.cwd = nextDirectory;
|
|
2012
|
+
await this.refreshContext();
|
|
2013
|
+
this.io.write(`${successMark()} Directory: ${this.cwd}
|
|
2014
|
+
`);
|
|
2015
|
+
return "continue";
|
|
2016
|
+
}
|
|
2017
|
+
case "exit":
|
|
2018
|
+
assertNoArgs("exit", command.args);
|
|
2019
|
+
return "exit";
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
async handleLine(input) {
|
|
2023
|
+
try {
|
|
2024
|
+
if (/^(exit|quit)$/i.test(input.trim())) return "exit";
|
|
2025
|
+
const slash = parseSlashCommand(input);
|
|
2026
|
+
if (slash) return await this.handleSlash(slash);
|
|
2027
|
+
const routed = routeShellCommand(input, this.mode);
|
|
2028
|
+
if (!routed) return "continue";
|
|
2029
|
+
await this.runner({
|
|
2030
|
+
target: routed.target,
|
|
2031
|
+
args: [...routed.args],
|
|
2032
|
+
cwd: this.cwd
|
|
2033
|
+
});
|
|
2034
|
+
await this.refreshContext();
|
|
2035
|
+
return "continue";
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
this.io.writeError(`${errorMark()} ${errorMessage(error)}
|
|
2038
|
+
`);
|
|
2039
|
+
return "continue";
|
|
2040
|
+
}
|
|
167
2041
|
}
|
|
2042
|
+
};
|
|
2043
|
+
async function createShellSession(options) {
|
|
2044
|
+
return TunedTensorShellSession.create(options);
|
|
168
2045
|
}
|
|
169
|
-
function
|
|
170
|
-
|
|
2046
|
+
function streamIsTTY(stream) {
|
|
2047
|
+
return Boolean(stream.isTTY);
|
|
171
2048
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
2049
|
+
async function withForegroundSignalHandoff(control, run) {
|
|
2050
|
+
const signals = control.signals ?? process;
|
|
2051
|
+
const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
|
|
2052
|
+
const keepParentAlive = () => {
|
|
2053
|
+
};
|
|
2054
|
+
control.pauseReadline();
|
|
2055
|
+
if (restoreRawMode) control.input.setRawMode(false);
|
|
2056
|
+
signals.on("SIGINT", keepParentAlive);
|
|
2057
|
+
try {
|
|
2058
|
+
return await run();
|
|
2059
|
+
} finally {
|
|
2060
|
+
signals.off("SIGINT", keepParentAlive);
|
|
2061
|
+
if (restoreRawMode) control.input.setRawMode(true);
|
|
2062
|
+
control.resumeReadline();
|
|
2063
|
+
}
|
|
174
2064
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
2065
|
+
function streamIO(output, error) {
|
|
2066
|
+
return {
|
|
2067
|
+
write(text2) {
|
|
2068
|
+
output.write(text2);
|
|
2069
|
+
},
|
|
2070
|
+
writeError(text2) {
|
|
2071
|
+
error.write(text2);
|
|
2072
|
+
},
|
|
2073
|
+
clear() {
|
|
2074
|
+
output.write("\x1B[2J\x1B[H");
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
177
2077
|
}
|
|
178
|
-
function
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
2078
|
+
async function startInteractiveShell(options) {
|
|
2079
|
+
const input = options.input ?? process.stdin;
|
|
2080
|
+
const output = options.output ?? process.stdout;
|
|
2081
|
+
const error = options.error ?? process.stderr;
|
|
2082
|
+
if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
|
|
2083
|
+
throw new Error("The interactive TT shell requires a TTY.");
|
|
2084
|
+
}
|
|
2085
|
+
let readline;
|
|
2086
|
+
const terminalInput = input;
|
|
2087
|
+
const foregroundRunner = (request2) => withForegroundSignalHandoff(
|
|
2088
|
+
{
|
|
2089
|
+
input: terminalInput,
|
|
2090
|
+
pauseReadline: () => readline?.pause(),
|
|
2091
|
+
resumeReadline: () => readline?.resume()
|
|
2092
|
+
},
|
|
2093
|
+
() => options.runner(request2)
|
|
2094
|
+
);
|
|
2095
|
+
const session = await createShellSession({
|
|
2096
|
+
runner: foregroundRunner,
|
|
2097
|
+
io: streamIO(output, error),
|
|
2098
|
+
cwd: options.cwd,
|
|
2099
|
+
env: options.env,
|
|
2100
|
+
version: options.version
|
|
2101
|
+
});
|
|
2102
|
+
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
2103
|
+
readline = createInterface({
|
|
2104
|
+
input,
|
|
2105
|
+
output,
|
|
2106
|
+
terminal: true,
|
|
2107
|
+
historySize: 100,
|
|
2108
|
+
removeHistoryDuplicates: true,
|
|
2109
|
+
completer
|
|
2110
|
+
});
|
|
2111
|
+
readline.on("SIGINT", () => {
|
|
2112
|
+
output.write("\n");
|
|
2113
|
+
readline?.setPrompt(session.prompt());
|
|
2114
|
+
readline?.write(null, { ctrl: true, name: "u" });
|
|
2115
|
+
});
|
|
2116
|
+
output.write(session.banner());
|
|
2117
|
+
readline.setPrompt(session.prompt());
|
|
2118
|
+
readline.prompt();
|
|
2119
|
+
try {
|
|
2120
|
+
for await (const line of readline) {
|
|
2121
|
+
const action = await session.handleLine(line);
|
|
2122
|
+
if (action === "exit") {
|
|
2123
|
+
readline.close();
|
|
2124
|
+
break;
|
|
187
2125
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
2126
|
+
readline.setPrompt(session.prompt());
|
|
2127
|
+
readline.prompt();
|
|
2128
|
+
}
|
|
2129
|
+
} finally {
|
|
2130
|
+
readline.close();
|
|
193
2131
|
}
|
|
194
2132
|
}
|
|
195
|
-
function
|
|
196
|
-
if (!
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
function formatStatus(status) {
|
|
200
|
-
const map = {
|
|
201
|
-
completed: chalk.green,
|
|
202
|
-
running: chalk.blue,
|
|
203
|
-
training: chalk.blue,
|
|
204
|
-
evaluating: chalk.blue,
|
|
205
|
-
preparing: chalk.yellow,
|
|
206
|
-
pending: chalk.yellow,
|
|
207
|
-
uploading: chalk.yellow,
|
|
208
|
-
failed: chalk.red,
|
|
209
|
-
cancelled: chalk.dim,
|
|
210
|
-
validated: chalk.green,
|
|
211
|
-
invalid: chalk.red
|
|
212
|
-
};
|
|
213
|
-
const colorFn = map[status] || chalk.white;
|
|
214
|
-
return colorFn(status);
|
|
215
|
-
}
|
|
216
|
-
function truncate(str, max) {
|
|
217
|
-
if (str.length <= max) return str;
|
|
218
|
-
return str.slice(0, max - 1) + "\u2026";
|
|
2133
|
+
function ciEnabled(value) {
|
|
2134
|
+
if (!value) return false;
|
|
2135
|
+
return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
219
2136
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
2137
|
+
function shouldStartInteractiveShell(options) {
|
|
2138
|
+
const env = options.env ?? process.env;
|
|
2139
|
+
return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
|
|
222
2140
|
}
|
|
223
2141
|
|
|
224
2142
|
// src/commands/auth.ts
|
|
225
|
-
import { createInterface } from "readline/promises";
|
|
2143
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
226
2144
|
import { stdin, stdout } from "process";
|
|
227
|
-
import
|
|
2145
|
+
import { Writable } from "stream";
|
|
2146
|
+
import chalk3 from "chalk";
|
|
2147
|
+
async function promptForApiKey(input = stdin, output = stdout) {
|
|
2148
|
+
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2149
|
+
throw new Error(
|
|
2150
|
+
"An API key is required in non-interactive mode. Pass it to `tt auth login <key>` or set TUNED_TENSOR_API_KEY."
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
let muted = false;
|
|
2154
|
+
const maskedOutput = new Writable({
|
|
2155
|
+
write(chunk, _encoding, callback) {
|
|
2156
|
+
if (!muted) output.write(chunk);
|
|
2157
|
+
callback();
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
const rl = createInterface2({
|
|
2161
|
+
input,
|
|
2162
|
+
output: maskedOutput,
|
|
2163
|
+
terminal: true
|
|
2164
|
+
});
|
|
2165
|
+
try {
|
|
2166
|
+
const pending = rl.question("Enter your API key (tt_...): ");
|
|
2167
|
+
muted = true;
|
|
2168
|
+
return await pending;
|
|
2169
|
+
} finally {
|
|
2170
|
+
muted = false;
|
|
2171
|
+
output.write("\n");
|
|
2172
|
+
rl.close();
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
228
2175
|
function registerAuthCommands(parent) {
|
|
229
2176
|
const auth = parent.command("auth").description("Manage authentication");
|
|
230
2177
|
auth.command("login").description("Store an API key for authentication").argument("[key]", "API key (tt_...). If omitted, you will be prompted.").action(async (key) => {
|
|
231
2178
|
let apiKey = key;
|
|
232
2179
|
if (!apiKey) {
|
|
2180
|
+
if (isJsonMode()) {
|
|
2181
|
+
throw new Error(
|
|
2182
|
+
"An API key argument is required in JSON mode. Use `tt --json auth login <key>`."
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
233
2185
|
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
234
2186
|
console.log();
|
|
235
2187
|
console.log(
|
|
236
|
-
` To get your API key, go to ${
|
|
2188
|
+
` To get your API key, go to ${chalk3.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
237
2189
|
);
|
|
238
|
-
console.log(` ${
|
|
2190
|
+
console.log(` ${chalk3.cyan.underline(settingsUrl)}`);
|
|
239
2191
|
console.log();
|
|
240
|
-
|
|
241
|
-
apiKey = await rl.question("Enter your API key (tt_...): ");
|
|
242
|
-
rl.close();
|
|
2192
|
+
apiKey = await promptForApiKey();
|
|
243
2193
|
}
|
|
244
2194
|
apiKey = apiKey.trim();
|
|
245
2195
|
if (!apiKey.startsWith("tt_") || apiKey.length !== 51) {
|
|
246
|
-
|
|
2196
|
+
throw new Error(
|
|
247
2197
|
"Invalid API key format. Keys start with tt_ and are 51 characters long."
|
|
248
2198
|
);
|
|
249
|
-
process.exit(1);
|
|
250
2199
|
}
|
|
251
2200
|
updateConfig({ api_key: apiKey });
|
|
2201
|
+
if (isJsonMode()) {
|
|
2202
|
+
printJson({
|
|
2203
|
+
authenticated: true,
|
|
2204
|
+
key_prefix: `${apiKey.slice(0, 8)}...`,
|
|
2205
|
+
base_url: getBaseUrl(parent.opts())
|
|
2206
|
+
});
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
252
2209
|
printSuccess(
|
|
253
2210
|
`API key stored (${apiKey.slice(0, 8)}...). You're ready to go.`
|
|
254
2211
|
);
|
|
255
2212
|
});
|
|
256
2213
|
auth.command("logout").description("Remove stored credentials").action(() => {
|
|
257
2214
|
clearConfig();
|
|
2215
|
+
if (isJsonMode()) {
|
|
2216
|
+
printJson({ authenticated: false });
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
258
2219
|
printSuccess("Credentials removed.");
|
|
259
2220
|
});
|
|
260
2221
|
auth.command("status").description("Show current authentication status").action(() => {
|
|
@@ -382,87 +2343,7 @@ function resolveLabelingJobId(prefix, opts) {
|
|
|
382
2343
|
);
|
|
383
2344
|
}
|
|
384
2345
|
|
|
385
|
-
// src/base-models.ts
|
|
386
|
-
var SUPPORTED_BASE_MODELS = [
|
|
387
|
-
"google/gemma-4-E2B-it",
|
|
388
|
-
"google/gemma-4-E4B-it",
|
|
389
|
-
"Qwen/Qwen3.5-2B",
|
|
390
|
-
"Qwen/Qwen3-VL-2B-Instruct",
|
|
391
|
-
"Qwen/Qwen3.5-4B",
|
|
392
|
-
"meta-llama/Llama-3.2-3B-Instruct",
|
|
393
|
-
"microsoft/Phi-4-mini-instruct",
|
|
394
|
-
"ibm-granite/granite-3.3-2b-instruct",
|
|
395
|
-
"bigcode/starcoder2-3b"
|
|
396
|
-
];
|
|
397
|
-
var BASE_MODEL_ALIASES = /* @__PURE__ */ new Map();
|
|
398
|
-
for (const model of SUPPORTED_BASE_MODELS) {
|
|
399
|
-
BASE_MODEL_ALIASES.set(model.toLowerCase(), model);
|
|
400
|
-
}
|
|
401
|
-
for (const [alias, model] of [
|
|
402
|
-
["google/gemma-4-E2B", "google/gemma-4-E2B-it"],
|
|
403
|
-
["google/gemma-4-e2b", "google/gemma-4-E2B-it"],
|
|
404
|
-
["google/gemma-4-e2b-it", "google/gemma-4-E2B-it"],
|
|
405
|
-
["google/gemma-4-E4B", "google/gemma-4-E4B-it"],
|
|
406
|
-
["google/gemma-4-e4b", "google/gemma-4-E4B-it"],
|
|
407
|
-
["google/gemma-4-e4b-it", "google/gemma-4-E4B-it"],
|
|
408
|
-
["qwen/qwen3.5-2b", "Qwen/Qwen3.5-2B"],
|
|
409
|
-
["Qwen/Qwen3.5-2B-Base", "Qwen/Qwen3.5-2B"],
|
|
410
|
-
["qwen/qwen3.5-2b-base", "Qwen/Qwen3.5-2B"],
|
|
411
|
-
["qwen/qwen3-vl-2b-instruct", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
412
|
-
["Qwen/Qwen3-VL-2B", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
413
|
-
["qwen/qwen3-vl-2b", "Qwen/Qwen3-VL-2B-Instruct"],
|
|
414
|
-
["qwen/qwen3.5-4b", "Qwen/Qwen3.5-4B"],
|
|
415
|
-
["Qwen/Qwen3.5-4B-Base", "Qwen/Qwen3.5-4B"],
|
|
416
|
-
["qwen/qwen3.5-4b-base", "Qwen/Qwen3.5-4B"],
|
|
417
|
-
["meta-llama/llama-3.2-3b-instruct", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
418
|
-
["meta-llama/Llama-3.2-3B", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
419
|
-
["meta-llama/llama-3.2-3b", "meta-llama/Llama-3.2-3B-Instruct"],
|
|
420
|
-
["microsoft/phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
421
|
-
["phi-4-mini-instruct", "microsoft/Phi-4-mini-instruct"],
|
|
422
|
-
["ibm-granite/granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
423
|
-
["granite-3.3-2b-instruct", "ibm-granite/granite-3.3-2b-instruct"],
|
|
424
|
-
["bigcode/starcoder2-3b", "bigcode/starcoder2-3b"],
|
|
425
|
-
["starcoder2-3b", "bigcode/starcoder2-3b"]
|
|
426
|
-
]) {
|
|
427
|
-
BASE_MODEL_ALIASES.set(alias.toLowerCase(), model);
|
|
428
|
-
}
|
|
429
|
-
var UnsupportedBaseModelError = class extends Error {
|
|
430
|
-
constructor(model) {
|
|
431
|
-
const supported = SUPPORTED_BASE_MODELS.join(", ");
|
|
432
|
-
super(
|
|
433
|
-
`Unsupported base_model "${String(model)}". Supported base models: ${supported}`
|
|
434
|
-
);
|
|
435
|
-
this.name = "UnsupportedBaseModelError";
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
function canonicalizeBaseModel(model) {
|
|
439
|
-
if (typeof model !== "string" || !model.trim()) {
|
|
440
|
-
throw new UnsupportedBaseModelError(model);
|
|
441
|
-
}
|
|
442
|
-
const canonical = BASE_MODEL_ALIASES.get(model.trim().toLowerCase());
|
|
443
|
-
if (!canonical) {
|
|
444
|
-
throw new UnsupportedBaseModelError(model);
|
|
445
|
-
}
|
|
446
|
-
return canonical;
|
|
447
|
-
}
|
|
448
|
-
function canonicalizeSpecBaseModel(body) {
|
|
449
|
-
if ("base_model" in body && body.base_model !== void 0) {
|
|
450
|
-
return { ...body, base_model: canonicalizeBaseModel(body.base_model) };
|
|
451
|
-
}
|
|
452
|
-
return body;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
2346
|
// src/commands/specs.ts
|
|
456
|
-
var SPEC_BODY_KEYS = /* @__PURE__ */ new Set([
|
|
457
|
-
"name",
|
|
458
|
-
"description",
|
|
459
|
-
"base_model",
|
|
460
|
-
"system_prompt",
|
|
461
|
-
"guidelines",
|
|
462
|
-
"constraints",
|
|
463
|
-
"examples",
|
|
464
|
-
"eval_cases"
|
|
465
|
-
]);
|
|
466
2347
|
var RUN_INPUT_KEYS = ["run_id", "behavior_spec_id", "spec_snapshot", "run_number"];
|
|
467
2348
|
var SpecBodyError = class extends Error {
|
|
468
2349
|
};
|
|
@@ -493,15 +2374,13 @@ function loadSpecBody(filePath, mode) {
|
|
|
493
2374
|
`${filePath} is missing required field "name" (string).`
|
|
494
2375
|
);
|
|
495
2376
|
}
|
|
496
|
-
const unknown =
|
|
497
|
-
(k) => !SPEC_BODY_KEYS.has(k) && k !== "id" && k !== "eval_cases"
|
|
498
|
-
);
|
|
2377
|
+
const unknown = unknownProjectSpecKeys(body);
|
|
499
2378
|
if (unknown.length && !isJsonMode()) {
|
|
500
2379
|
printWarning(
|
|
501
|
-
`Unknown spec field(s) in ${filePath}: ${unknown.join(", ")}. They will be
|
|
2380
|
+
`Unknown spec field(s) in ${filePath}: ${unknown.join(", ")}. They will be ignored by the cloud API.`
|
|
502
2381
|
);
|
|
503
2382
|
}
|
|
504
|
-
return
|
|
2383
|
+
return projectCloudSpec(body).body;
|
|
505
2384
|
}
|
|
506
2385
|
function registerSpecsCommands(parent) {
|
|
507
2386
|
const specs = parent.command("specs").description("Manage behaviour specs");
|
|
@@ -1195,18 +3074,18 @@ function findControlChar(value) {
|
|
|
1195
3074
|
function formatCodepoint(code) {
|
|
1196
3075
|
return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
1197
3076
|
}
|
|
1198
|
-
function detectRowFormat(
|
|
1199
|
-
const input =
|
|
1200
|
-
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof
|
|
3077
|
+
function detectRowFormat(record2) {
|
|
3078
|
+
const input = record2.input;
|
|
3079
|
+
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record2.output === "string") {
|
|
1201
3080
|
return "document_ocr_jsonl";
|
|
1202
3081
|
}
|
|
1203
|
-
if (typeof
|
|
3082
|
+
if (typeof record2.input === "string" || typeof record2.output === "string" || !("input" in record2) || !("output" in record2)) {
|
|
1204
3083
|
return "jsonl";
|
|
1205
3084
|
}
|
|
1206
3085
|
return null;
|
|
1207
3086
|
}
|
|
1208
|
-
function validateDocumentOcrRow(
|
|
1209
|
-
const input =
|
|
3087
|
+
function validateDocumentOcrRow(record2, rowNumber, errors) {
|
|
3088
|
+
const input = record2.input;
|
|
1210
3089
|
const assets = input.assets;
|
|
1211
3090
|
if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
|
|
1212
3091
|
errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
|
|
@@ -1218,10 +3097,10 @@ function validateDocumentOcrRow(record, rowNumber, errors) {
|
|
|
1218
3097
|
);
|
|
1219
3098
|
}
|
|
1220
3099
|
}
|
|
1221
|
-
if (typeof
|
|
3100
|
+
if (typeof record2.output !== "string" || record2.output.trim().length === 0) {
|
|
1222
3101
|
errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
|
|
1223
3102
|
} else {
|
|
1224
|
-
const hit = findControlChar(
|
|
3103
|
+
const hit = findControlChar(record2.output);
|
|
1225
3104
|
if (hit) {
|
|
1226
3105
|
errors.push(
|
|
1227
3106
|
`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`
|
|
@@ -1290,14 +3169,14 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
1290
3169
|
errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
|
|
1291
3170
|
continue;
|
|
1292
3171
|
}
|
|
1293
|
-
const
|
|
1294
|
-
if ("messages" in
|
|
3172
|
+
const record2 = row;
|
|
3173
|
+
if ("messages" in record2 && !("input" in record2) && !("output" in record2)) {
|
|
1295
3174
|
errors.push(
|
|
1296
3175
|
`Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
|
|
1297
3176
|
);
|
|
1298
3177
|
continue;
|
|
1299
3178
|
}
|
|
1300
|
-
const rowFormat = detectRowFormat(
|
|
3179
|
+
const rowFormat = detectRowFormat(record2);
|
|
1301
3180
|
if (!rowFormat) {
|
|
1302
3181
|
errors.push(
|
|
1303
3182
|
`Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
|
|
@@ -1314,17 +3193,17 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
1314
3193
|
continue;
|
|
1315
3194
|
}
|
|
1316
3195
|
if (rowFormat === "document_ocr_jsonl") {
|
|
1317
|
-
validateDocumentOcrRow(
|
|
3196
|
+
validateDocumentOcrRow(record2, rowNumber, errors);
|
|
1318
3197
|
continue;
|
|
1319
3198
|
}
|
|
1320
|
-
if (typeof
|
|
3199
|
+
if (typeof record2.input !== "string") {
|
|
1321
3200
|
errors.push(`Row ${rowNumber}: missing string "input" field`);
|
|
1322
3201
|
}
|
|
1323
|
-
if (typeof
|
|
3202
|
+
if (typeof record2.output !== "string") {
|
|
1324
3203
|
errors.push(`Row ${rowNumber}: missing string "output" field`);
|
|
1325
3204
|
}
|
|
1326
3205
|
for (const field of ["input", "output"]) {
|
|
1327
|
-
const value =
|
|
3206
|
+
const value = record2[field];
|
|
1328
3207
|
if (typeof value !== "string") continue;
|
|
1329
3208
|
const hit = findControlChar(value);
|
|
1330
3209
|
if (hit) {
|
|
@@ -1452,7 +3331,7 @@ function formatCents2(cents) {
|
|
|
1452
3331
|
return `$${(cents / 100).toFixed(2)}`;
|
|
1453
3332
|
}
|
|
1454
3333
|
function sleep(ms) {
|
|
1455
|
-
return new Promise((
|
|
3334
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1456
3335
|
}
|
|
1457
3336
|
function parseCsvHeader(line) {
|
|
1458
3337
|
const fields = [];
|
|
@@ -1845,7 +3724,7 @@ function registerLabelCommands(parent) {
|
|
|
1845
3724
|
}
|
|
1846
3725
|
|
|
1847
3726
|
// src/commands/models.ts
|
|
1848
|
-
import { spawn as
|
|
3727
|
+
import { spawn as spawn3, execFileSync } from "child_process";
|
|
1849
3728
|
import { createHash } from "crypto";
|
|
1850
3729
|
import {
|
|
1851
3730
|
existsSync as existsSync5,
|
|
@@ -1858,14 +3737,14 @@ import {
|
|
|
1858
3737
|
readdirSync,
|
|
1859
3738
|
rmSync
|
|
1860
3739
|
} from "fs";
|
|
1861
|
-
import { homedir as
|
|
1862
|
-
import { dirname as
|
|
3740
|
+
import { homedir as homedir4 } from "os";
|
|
3741
|
+
import { dirname as dirname4, join as join4, resolve as resolve5, basename as basename4, normalize, isAbsolute as isAbsolute4 } from "path";
|
|
1863
3742
|
import { Readable, Transform } from "stream";
|
|
1864
3743
|
import { pipeline } from "stream/promises";
|
|
1865
3744
|
|
|
1866
3745
|
// src/commands/init.ts
|
|
1867
3746
|
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1868
|
-
import { resolve } from "path";
|
|
3747
|
+
import { resolve as resolve4 } from "path";
|
|
1869
3748
|
var DEFAULT_SPEC_FILE = "tunedtensor.json";
|
|
1870
3749
|
var SCAFFOLD = {
|
|
1871
3750
|
name: "My Agent",
|
|
@@ -1875,30 +3754,45 @@ var SCAFFOLD = {
|
|
|
1875
3754
|
guidelines: [],
|
|
1876
3755
|
constraints: [],
|
|
1877
3756
|
examples: [
|
|
1878
|
-
{ input: "Hello", output: "Hi! How can I help you today?" }
|
|
1879
|
-
|
|
1880
|
-
|
|
3757
|
+
{ input: "Hello", output: "Hi! How can I help you today?" },
|
|
3758
|
+
{
|
|
3759
|
+
input: "Summarize this update: The launch moved to Friday.",
|
|
3760
|
+
output: "The launch is now scheduled for Friday."
|
|
3761
|
+
}
|
|
3762
|
+
]
|
|
1881
3763
|
};
|
|
1882
3764
|
function registerInitCommand(parent) {
|
|
1883
|
-
parent.command("init").description("Create a
|
|
1884
|
-
const filePath =
|
|
3765
|
+
parent.command("init").description("Create a behaviour spec project file").option("-n, --name <name>", "Spec name").option("--model <model>", "Base model ID").option("-f, --file <path>", "Output file path", DEFAULT_SPEC_FILE).action(async (cmdOpts) => {
|
|
3766
|
+
const filePath = resolve4(cmdOpts.file);
|
|
1885
3767
|
if (existsSync4(filePath)) {
|
|
3768
|
+
if (isJsonMode()) {
|
|
3769
|
+
printJson({ created: false, path: filePath });
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
1886
3772
|
printWarning(`${cmdOpts.file} already exists. Use tt eval to validate it or edit it directly.`);
|
|
1887
3773
|
return;
|
|
1888
3774
|
}
|
|
1889
|
-
const spec = {
|
|
3775
|
+
const spec = {
|
|
3776
|
+
...SCAFFOLD,
|
|
3777
|
+
examples: SCAFFOLD.examples.map((example) => ({ ...example }))
|
|
3778
|
+
};
|
|
1890
3779
|
if (cmdOpts.name) spec.name = cmdOpts.name;
|
|
1891
3780
|
if (cmdOpts.model) spec.base_model = canonicalizeBaseModel(cmdOpts.model);
|
|
1892
3781
|
writeFileSync2(filePath, JSON.stringify(spec, null, 2) + "\n");
|
|
3782
|
+
if (isJsonMode()) {
|
|
3783
|
+
printJson({ created: true, path: filePath, spec });
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
1893
3786
|
printSuccess(`Created ${cmdOpts.file}`);
|
|
1894
3787
|
console.log("\nNext steps:");
|
|
1895
3788
|
console.log(" 1. Edit the spec: system_prompt, guidelines, examples");
|
|
1896
|
-
console.log(" 2. Validate
|
|
1897
|
-
console.log(" 3. Push to
|
|
3789
|
+
console.log(" 2. Validate for cloud: tt eval");
|
|
3790
|
+
console.log(" 3. Push to cloud: tt push");
|
|
3791
|
+
console.log(" Or validate locally: tt local validate");
|
|
1898
3792
|
});
|
|
1899
3793
|
}
|
|
1900
3794
|
function loadSpec(filePath) {
|
|
1901
|
-
const resolved =
|
|
3795
|
+
const resolved = resolve4(filePath || DEFAULT_SPEC_FILE);
|
|
1902
3796
|
if (!existsSync4(resolved)) {
|
|
1903
3797
|
printError(
|
|
1904
3798
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE}
|
|
@@ -1911,30 +3805,30 @@ Run \`tt init\` to create one.`
|
|
|
1911
3805
|
}
|
|
1912
3806
|
|
|
1913
3807
|
// src/serve-manager.ts
|
|
1914
|
-
import { spawn } from "child_process";
|
|
1915
|
-
import { randomUUID } from "crypto";
|
|
3808
|
+
import { spawn as spawn2 } from "child_process";
|
|
3809
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1916
3810
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1917
3811
|
import { createServer } from "http";
|
|
1918
3812
|
import { createServer as createNetServer } from "net";
|
|
1919
|
-
import { dirname } from "path";
|
|
3813
|
+
import { dirname as dirname3 } from "path";
|
|
1920
3814
|
var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
|
|
1921
3815
|
function parsePositiveInteger(value, fallback) {
|
|
1922
3816
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
1923
3817
|
}
|
|
1924
3818
|
function sleep2(ms) {
|
|
1925
|
-
return new Promise((
|
|
3819
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1926
3820
|
}
|
|
1927
3821
|
function readRequestBody(req) {
|
|
1928
|
-
return new Promise((
|
|
3822
|
+
return new Promise((resolve7, reject) => {
|
|
1929
3823
|
const chunks = [];
|
|
1930
3824
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
1931
|
-
req.on("end", () =>
|
|
3825
|
+
req.on("end", () => resolve7(Buffer.concat(chunks)));
|
|
1932
3826
|
req.on("error", reject);
|
|
1933
3827
|
});
|
|
1934
3828
|
}
|
|
1935
|
-
function sendJson(res,
|
|
3829
|
+
function sendJson(res, status2, payload) {
|
|
1936
3830
|
const body = Buffer.from(JSON.stringify(payload), "utf8");
|
|
1937
|
-
res.writeHead(
|
|
3831
|
+
res.writeHead(status2, {
|
|
1938
3832
|
"content-type": "application/json",
|
|
1939
3833
|
"content-length": String(body.byteLength),
|
|
1940
3834
|
"access-control-allow-origin": "*",
|
|
@@ -1996,7 +3890,7 @@ function errorSummary(response, fallback) {
|
|
|
1996
3890
|
return typeof message === "string" ? message : fallback;
|
|
1997
3891
|
}
|
|
1998
3892
|
async function allocatePort(host) {
|
|
1999
|
-
return new Promise((
|
|
3893
|
+
return new Promise((resolve7, reject) => {
|
|
2000
3894
|
const server = createNetServer();
|
|
2001
3895
|
server.on("error", reject);
|
|
2002
3896
|
server.listen(0, host, () => {
|
|
@@ -2006,14 +3900,14 @@ async function allocatePort(host) {
|
|
|
2006
3900
|
return;
|
|
2007
3901
|
}
|
|
2008
3902
|
const port = address.port;
|
|
2009
|
-
server.close(() =>
|
|
3903
|
+
server.close(() => resolve7(port));
|
|
2010
3904
|
});
|
|
2011
3905
|
});
|
|
2012
3906
|
}
|
|
2013
3907
|
var ManagedModelServer = class {
|
|
2014
3908
|
constructor(options) {
|
|
2015
3909
|
this.options = options;
|
|
2016
|
-
this.spawnModelServer = options.spawnModelServer ??
|
|
3910
|
+
this.spawnModelServer = options.spawnModelServer ?? spawn2;
|
|
2017
3911
|
this.idleTimeoutMs = parsePositiveInteger(options.idleTimeoutSeconds, 300) * 1e3;
|
|
2018
3912
|
this.restartAfterRequests = parsePositiveInteger(options.restartAfterRequests, 100);
|
|
2019
3913
|
this.logRecordOverride = options.logRecord;
|
|
@@ -2046,11 +3940,11 @@ var ManagedModelServer = class {
|
|
|
2046
3940
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
2047
3941
|
});
|
|
2048
3942
|
});
|
|
2049
|
-
await new Promise((
|
|
3943
|
+
await new Promise((resolve7, reject) => {
|
|
2050
3944
|
this.server?.once("error", reject);
|
|
2051
3945
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
2052
3946
|
this.server?.off("error", reject);
|
|
2053
|
-
|
|
3947
|
+
resolve7();
|
|
2054
3948
|
});
|
|
2055
3949
|
});
|
|
2056
3950
|
}
|
|
@@ -2060,15 +3954,15 @@ var ManagedModelServer = class {
|
|
|
2060
3954
|
if (!this.server) return;
|
|
2061
3955
|
const server = this.server;
|
|
2062
3956
|
this.server = null;
|
|
2063
|
-
await new Promise((
|
|
2064
|
-
server.close((err) => err ? reject(err) :
|
|
3957
|
+
await new Promise((resolve7, reject) => {
|
|
3958
|
+
server.close((err) => err ? reject(err) : resolve7());
|
|
2065
3959
|
});
|
|
2066
3960
|
}
|
|
2067
3961
|
async run() {
|
|
2068
3962
|
await this.start();
|
|
2069
|
-
await new Promise((
|
|
3963
|
+
await new Promise((resolve7, reject) => {
|
|
2070
3964
|
this.server?.once("error", reject);
|
|
2071
|
-
this.server?.once("close",
|
|
3965
|
+
this.server?.once("close", resolve7);
|
|
2072
3966
|
});
|
|
2073
3967
|
}
|
|
2074
3968
|
async handle(req, res) {
|
|
@@ -2136,20 +4030,20 @@ var ManagedModelServer = class {
|
|
|
2136
4030
|
});
|
|
2137
4031
|
}
|
|
2138
4032
|
async processGeneration(route, body, res, receivedAt) {
|
|
2139
|
-
const requestId = `ttreq-${
|
|
4033
|
+
const requestId = `ttreq-${randomUUID2()}`;
|
|
2140
4034
|
const startedAt = Date.now();
|
|
2141
4035
|
const queuedMs = startedAt - receivedAt;
|
|
2142
4036
|
this.activeRequests += 1;
|
|
2143
4037
|
this.clearIdleTimer();
|
|
2144
|
-
let
|
|
4038
|
+
let status2 = 500;
|
|
2145
4039
|
let parsed = null;
|
|
2146
4040
|
let failure = null;
|
|
2147
4041
|
try {
|
|
2148
4042
|
await this.ensureModelReady();
|
|
2149
4043
|
const forwarded = await this.forwardGeneration(route, body);
|
|
2150
|
-
|
|
4044
|
+
status2 = forwarded.status;
|
|
2151
4045
|
parsed = forwarded.parsed;
|
|
2152
|
-
res.writeHead(
|
|
4046
|
+
res.writeHead(status2, {
|
|
2153
4047
|
...copyResponseHeaders(forwarded.headers),
|
|
2154
4048
|
"content-length": String(Buffer.byteLength(forwarded.body))
|
|
2155
4049
|
});
|
|
@@ -2158,8 +4052,8 @@ var ManagedModelServer = class {
|
|
|
2158
4052
|
failure = errorSummary(parsed, null);
|
|
2159
4053
|
} catch (err) {
|
|
2160
4054
|
failure = err instanceof Error ? err.message : String(err);
|
|
2161
|
-
|
|
2162
|
-
sendJson(res,
|
|
4055
|
+
status2 = 502;
|
|
4056
|
+
sendJson(res, status2, { error: { message: failure } });
|
|
2163
4057
|
} finally {
|
|
2164
4058
|
const finishedAt = Date.now();
|
|
2165
4059
|
this.activeRequests -= 1;
|
|
@@ -2172,13 +4066,13 @@ var ManagedModelServer = class {
|
|
|
2172
4066
|
path: this.options.modelPath
|
|
2173
4067
|
},
|
|
2174
4068
|
request_bytes: body.byteLength,
|
|
2175
|
-
response_status:
|
|
4069
|
+
response_status: status2,
|
|
2176
4070
|
latency_ms: finishedAt - receivedAt,
|
|
2177
4071
|
queued_ms: queuedMs,
|
|
2178
4072
|
prompt_tokens: usageNumber(parsed, "prompt_tokens"),
|
|
2179
4073
|
completion_tokens: usageNumber(parsed, "completion_tokens"),
|
|
2180
4074
|
total_tokens: usageNumber(parsed, "total_tokens"),
|
|
2181
|
-
schema_validity:
|
|
4075
|
+
schema_validity: status2 < 400 ? true : status2 === 422 ? false : null,
|
|
2182
4076
|
gate_field: this.options.gateField,
|
|
2183
4077
|
gate_result: this.extractGateResult(parsed),
|
|
2184
4078
|
restart_count: this.restartCount,
|
|
@@ -2242,7 +4136,7 @@ var ManagedModelServer = class {
|
|
|
2242
4136
|
try {
|
|
2243
4137
|
child.kill("SIGTERM");
|
|
2244
4138
|
await Promise.race([
|
|
2245
|
-
new Promise((
|
|
4139
|
+
new Promise((resolve7) => child.once("exit", () => resolve7())),
|
|
2246
4140
|
sleep2(5e3).then(() => {
|
|
2247
4141
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
2248
4142
|
})
|
|
@@ -2259,8 +4153,8 @@ var ManagedModelServer = class {
|
|
|
2259
4153
|
const response = await fetch(`http://127.0.0.1:${this.internalPort}/health`, {
|
|
2260
4154
|
signal: controller.signal
|
|
2261
4155
|
});
|
|
2262
|
-
const
|
|
2263
|
-
return { ok: response.ok, body: tryJsonParse(
|
|
4156
|
+
const text2 = await response.text();
|
|
4157
|
+
return { ok: response.ok, body: tryJsonParse(text2) };
|
|
2264
4158
|
} catch {
|
|
2265
4159
|
return { ok: false, body: null };
|
|
2266
4160
|
} finally {
|
|
@@ -2284,12 +4178,12 @@ var ManagedModelServer = class {
|
|
|
2284
4178
|
headers: { "content-type": "application/json" },
|
|
2285
4179
|
body
|
|
2286
4180
|
});
|
|
2287
|
-
const
|
|
4181
|
+
const text2 = await response.text();
|
|
2288
4182
|
return {
|
|
2289
4183
|
status: response.status,
|
|
2290
4184
|
headers: response.headers,
|
|
2291
|
-
body:
|
|
2292
|
-
parsed: tryJsonParse(
|
|
4185
|
+
body: text2,
|
|
4186
|
+
parsed: tryJsonParse(text2)
|
|
2293
4187
|
};
|
|
2294
4188
|
}
|
|
2295
4189
|
extractGateResult(response) {
|
|
@@ -2312,15 +4206,15 @@ var ManagedModelServer = class {
|
|
|
2312
4206
|
clearTimeout(this.idleTimer);
|
|
2313
4207
|
this.idleTimer = null;
|
|
2314
4208
|
}
|
|
2315
|
-
writeLog(
|
|
4209
|
+
writeLog(record2) {
|
|
2316
4210
|
if (this.logRecordOverride) {
|
|
2317
|
-
this.logRecordOverride(
|
|
4211
|
+
this.logRecordOverride(record2);
|
|
2318
4212
|
return;
|
|
2319
4213
|
}
|
|
2320
|
-
const line = `${JSON.stringify(
|
|
4214
|
+
const line = `${JSON.stringify(record2)}
|
|
2321
4215
|
`;
|
|
2322
4216
|
if (this.options.logFile) {
|
|
2323
|
-
mkdirSync2(
|
|
4217
|
+
mkdirSync2(dirname3(this.options.logFile), { recursive: true });
|
|
2324
4218
|
appendFileSync(this.options.logFile, line, "utf8");
|
|
2325
4219
|
return;
|
|
2326
4220
|
}
|
|
@@ -2868,7 +4762,7 @@ if __name__ == "__main__":
|
|
|
2868
4762
|
function resolveOutputPath(output, filename) {
|
|
2869
4763
|
if (!output) return filename;
|
|
2870
4764
|
if (existsSync5(output) && statSync4(output).isDirectory()) {
|
|
2871
|
-
return
|
|
4765
|
+
return join4(output, filename);
|
|
2872
4766
|
}
|
|
2873
4767
|
return output;
|
|
2874
4768
|
}
|
|
@@ -2944,7 +4838,7 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
2944
4838
|
if (!res.body) {
|
|
2945
4839
|
throw new Error("Download failed: response body was empty");
|
|
2946
4840
|
}
|
|
2947
|
-
mkdirSync3(
|
|
4841
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
2948
4842
|
const file = createWriteStream(outputPath);
|
|
2949
4843
|
const contentLength = parseContentLength(res.headers.get("content-length"));
|
|
2950
4844
|
const progress = createDownloadProgress(
|
|
@@ -2975,15 +4869,15 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
2975
4869
|
return contentLength;
|
|
2976
4870
|
}
|
|
2977
4871
|
function defaultCacheDir() {
|
|
2978
|
-
const root = process.env.XDG_CACHE_HOME ||
|
|
2979
|
-
return
|
|
4872
|
+
const root = process.env.XDG_CACHE_HOME || join4(homedir4(), ".cache");
|
|
4873
|
+
return join4(root, "tuned-tensor", "models");
|
|
2980
4874
|
}
|
|
2981
4875
|
function runtimeDir(cacheDir) {
|
|
2982
|
-
return
|
|
4876
|
+
return join4(cacheDir, "_runtime");
|
|
2983
4877
|
}
|
|
2984
4878
|
function runtimePythonPath(cacheDir) {
|
|
2985
4879
|
const dir = runtimeDir(cacheDir);
|
|
2986
|
-
return process.platform === "win32" ?
|
|
4880
|
+
return process.platform === "win32" ? join4(dir, "Scripts", "python.exe") : join4(dir, "bin", "python");
|
|
2987
4881
|
}
|
|
2988
4882
|
function resolveServePython(cacheDir, explicitPython) {
|
|
2989
4883
|
if (explicitPython) return explicitPython;
|
|
@@ -3056,7 +4950,7 @@ print(json.dumps({"missing": missing}))
|
|
|
3056
4950
|
}
|
|
3057
4951
|
}
|
|
3058
4952
|
function setupRuntimeCommands(python, venvDir, deps) {
|
|
3059
|
-
const venvPython = process.platform === "win32" ?
|
|
4953
|
+
const venvPython = process.platform === "win32" ? join4(venvDir, "Scripts", "python.exe") : join4(venvDir, "bin", "python");
|
|
3060
4954
|
return [
|
|
3061
4955
|
[python, "-m", "venv", venvDir],
|
|
3062
4956
|
[venvPython, "-m", "pip", "install", "--upgrade", "pip"],
|
|
@@ -3071,7 +4965,7 @@ function directoryHasFiles(path) {
|
|
|
3071
4965
|
}
|
|
3072
4966
|
function validateArchiveEntryPath(entry) {
|
|
3073
4967
|
const normalized = normalize(entry).replace(/^[\\/]+/, "");
|
|
3074
|
-
if (!entry.trim() ||
|
|
4968
|
+
if (!entry.trim() || isAbsolute4(entry) || normalized === ".." || normalized.startsWith(`..${"/"}`) || normalized.startsWith(`..${"\\"}`)) {
|
|
3075
4969
|
throw new Error(`Unsafe archive entry path: ${entry}`);
|
|
3076
4970
|
}
|
|
3077
4971
|
return normalized;
|
|
@@ -3094,9 +4988,9 @@ function extractArchive(archivePath, targetDir, force) {
|
|
|
3094
4988
|
return targetDir;
|
|
3095
4989
|
}
|
|
3096
4990
|
function localArchiveCacheDir(archivePath, cacheDir) {
|
|
3097
|
-
const resolved =
|
|
4991
|
+
const resolved = resolve5(archivePath);
|
|
3098
4992
|
const digest = createHash("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
3099
|
-
return
|
|
4993
|
+
return join4(cacheDir, `local-${safeSegment(basename4(archivePath))}-${digest}`);
|
|
3100
4994
|
}
|
|
3101
4995
|
function buildSystemPrompt(spec) {
|
|
3102
4996
|
const parts = [];
|
|
@@ -3112,9 +5006,9 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
|
3112
5006
|
return parts.join("\n\n");
|
|
3113
5007
|
}
|
|
3114
5008
|
function findSpecPath(explicitPath, modelPath) {
|
|
3115
|
-
const candidates = explicitPath ? [explicitPath] : [
|
|
5009
|
+
const candidates = explicitPath ? [explicitPath] : [join4(process.cwd(), DEFAULT_SPEC_FILE), join4(modelPath, DEFAULT_SPEC_FILE)];
|
|
3116
5010
|
for (const candidate of candidates) {
|
|
3117
|
-
const resolved =
|
|
5011
|
+
const resolved = resolve5(candidate);
|
|
3118
5012
|
if (existsSync5(resolved) && statSync4(resolved).isFile()) return resolved;
|
|
3119
5013
|
}
|
|
3120
5014
|
return null;
|
|
@@ -3124,7 +5018,7 @@ function loadSystemPromptFromSpec(specPath) {
|
|
|
3124
5018
|
return buildSystemPrompt(spec);
|
|
3125
5019
|
}
|
|
3126
5020
|
function loadJsonSchemaForServe(schemaPath) {
|
|
3127
|
-
const resolved =
|
|
5021
|
+
const resolved = resolve5(schemaPath);
|
|
3128
5022
|
if (!existsSync5(resolved) || !statSync4(resolved).isFile()) {
|
|
3129
5023
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
3130
5024
|
}
|
|
@@ -3136,28 +5030,28 @@ function loadJsonSchemaForServe(schemaPath) {
|
|
|
3136
5030
|
}
|
|
3137
5031
|
}
|
|
3138
5032
|
function writeReferenceServerScript(cacheDir) {
|
|
3139
|
-
const scriptDir =
|
|
5033
|
+
const scriptDir = join4(cacheDir, "_server");
|
|
3140
5034
|
mkdirSync3(scriptDir, { recursive: true });
|
|
3141
|
-
const scriptPath =
|
|
5035
|
+
const scriptPath = join4(scriptDir, "openai_reference_server.py");
|
|
3142
5036
|
writeFileSync3(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
|
|
3143
5037
|
return scriptPath;
|
|
3144
5038
|
}
|
|
3145
5039
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
3146
5040
|
if (existsSync5(target)) {
|
|
3147
|
-
const resolved =
|
|
5041
|
+
const resolved = resolve5(target);
|
|
3148
5042
|
const stats = statSync4(resolved);
|
|
3149
5043
|
if (stats.isDirectory()) {
|
|
3150
5044
|
return {
|
|
3151
5045
|
modelPath: resolved,
|
|
3152
|
-
modelName:
|
|
5046
|
+
modelName: basename4(resolved),
|
|
3153
5047
|
source: "local-directory"
|
|
3154
5048
|
};
|
|
3155
5049
|
}
|
|
3156
5050
|
if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
|
|
3157
|
-
const extractDir2 =
|
|
5051
|
+
const extractDir2 = join4(localArchiveCacheDir(resolved, cacheDir), "model");
|
|
3158
5052
|
return {
|
|
3159
5053
|
modelPath: extractArchive(resolved, extractDir2, forceDownload),
|
|
3160
|
-
modelName:
|
|
5054
|
+
modelName: basename4(resolved).replace(/\.t(ar\.)?gz$/, ""),
|
|
3161
5055
|
source: "local-archive"
|
|
3162
5056
|
};
|
|
3163
5057
|
}
|
|
@@ -3166,9 +5060,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
3166
5060
|
const fullId = await resolveModelId(target, opts);
|
|
3167
5061
|
const { data: model } = await get(`/models/${fullId}`, void 0, opts);
|
|
3168
5062
|
const { data } = await get(`/models/${fullId}/download`, void 0, opts);
|
|
3169
|
-
const modelCacheDir =
|
|
3170
|
-
const archivePath =
|
|
3171
|
-
const extractDir =
|
|
5063
|
+
const modelCacheDir = join4(cacheDir, fullId);
|
|
5064
|
+
const archivePath = join4(modelCacheDir, data.filename);
|
|
5065
|
+
const extractDir = join4(modelCacheDir, "model");
|
|
3172
5066
|
if (!existsSync5(archivePath) || forceDownload) {
|
|
3173
5067
|
await downloadUrlToFile(data.url, archivePath);
|
|
3174
5068
|
}
|
|
@@ -3274,7 +5168,7 @@ function ollamaSlug(name) {
|
|
|
3274
5168
|
function firstExisting(candidates) {
|
|
3275
5169
|
for (const candidate of candidates) {
|
|
3276
5170
|
if (candidate && existsSync5(candidate) && statSync4(candidate).isFile()) {
|
|
3277
|
-
return
|
|
5171
|
+
return resolve5(candidate);
|
|
3278
5172
|
}
|
|
3279
5173
|
}
|
|
3280
5174
|
return null;
|
|
@@ -3284,14 +5178,14 @@ function llamaCppDir(explicit) {
|
|
|
3284
5178
|
}
|
|
3285
5179
|
function resolveConvertScript(opts, required) {
|
|
3286
5180
|
if (opts.convertScript) {
|
|
3287
|
-
const resolved =
|
|
5181
|
+
const resolved = resolve5(opts.convertScript);
|
|
3288
5182
|
if (required && !existsSync5(resolved)) {
|
|
3289
5183
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
3290
5184
|
}
|
|
3291
5185
|
return resolved;
|
|
3292
5186
|
}
|
|
3293
5187
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
3294
|
-
const candidates = dir ? [
|
|
5188
|
+
const candidates = dir ? [join4(dir, "convert_hf_to_gguf.py"), join4(dir, "convert-hf-to-gguf.py")] : [];
|
|
3295
5189
|
const found = firstExisting(candidates);
|
|
3296
5190
|
if (found) return found;
|
|
3297
5191
|
if (required) {
|
|
@@ -3299,11 +5193,11 @@ function resolveConvertScript(opts, required) {
|
|
|
3299
5193
|
"Could not find convert_hf_to_gguf.py. Pass --llama-cpp <dir> (a llama.cpp checkout) or --convert-script <path>. Get it from https://github.com/ggml-org/llama.cpp."
|
|
3300
5194
|
);
|
|
3301
5195
|
}
|
|
3302
|
-
return dir ?
|
|
5196
|
+
return dir ? join4(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
|
|
3303
5197
|
}
|
|
3304
5198
|
function resolveQuantizeBin(opts, required) {
|
|
3305
5199
|
if (opts.quantizeBin) {
|
|
3306
|
-
const resolved =
|
|
5200
|
+
const resolved = resolve5(opts.quantizeBin);
|
|
3307
5201
|
if (required && !existsSync5(resolved)) {
|
|
3308
5202
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
3309
5203
|
}
|
|
@@ -3312,10 +5206,10 @@ function resolveQuantizeBin(opts, required) {
|
|
|
3312
5206
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
3313
5207
|
if (dir) {
|
|
3314
5208
|
const candidates = [
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
5209
|
+
join4(dir, "build", "bin", "llama-quantize"),
|
|
5210
|
+
join4(dir, "build", "bin", "quantize"),
|
|
5211
|
+
join4(dir, "llama-quantize"),
|
|
5212
|
+
join4(dir, "quantize")
|
|
3319
5213
|
];
|
|
3320
5214
|
const found = firstExisting(candidates);
|
|
3321
5215
|
if (found) return found;
|
|
@@ -3463,7 +5357,7 @@ function registerModelsCommands(parent) {
|
|
|
3463
5357
|
}
|
|
3464
5358
|
const quantPlan = planQuant(String(cmdOpts.quant));
|
|
3465
5359
|
const printOnly = Boolean(cmdOpts.printCommand);
|
|
3466
|
-
const cacheDir =
|
|
5360
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3467
5361
|
mkdirSync3(cacheDir, { recursive: true });
|
|
3468
5362
|
const serveTarget = await resolveServeTarget(
|
|
3469
5363
|
target,
|
|
@@ -3472,9 +5366,9 @@ function registerModelsCommands(parent) {
|
|
|
3472
5366
|
Boolean(cmdOpts.forceDownload)
|
|
3473
5367
|
);
|
|
3474
5368
|
const slug = ollamaSlug(serveTarget.modelName);
|
|
3475
|
-
const outputDir =
|
|
3476
|
-
const finalPath =
|
|
3477
|
-
const intermediatePath = quantPlan.requiresQuantize ?
|
|
5369
|
+
const outputDir = resolve5(cmdOpts.output || join4(process.cwd(), `${slug}-gguf`));
|
|
5370
|
+
const finalPath = join4(outputDir, `${slug}.${quantPlan.quant}.gguf`);
|
|
5371
|
+
const intermediatePath = quantPlan.requiresQuantize ? join4(outputDir, `${slug}.f16.gguf`) : void 0;
|
|
3478
5372
|
const convertScript = resolveConvertScript(cmdOpts, !printOnly);
|
|
3479
5373
|
const convertOutfile = intermediatePath ?? finalPath;
|
|
3480
5374
|
const steps = [
|
|
@@ -3512,8 +5406,8 @@ function registerModelsCommands(parent) {
|
|
|
3512
5406
|
}
|
|
3513
5407
|
if (specPath) systemPrompt = loadSystemPromptFromSpec(specPath);
|
|
3514
5408
|
}
|
|
3515
|
-
modelfilePath =
|
|
3516
|
-
modelfileContent = buildModelfile(
|
|
5409
|
+
modelfilePath = join4(outputDir, "Modelfile");
|
|
5410
|
+
modelfileContent = buildModelfile(basename4(finalPath), systemPrompt);
|
|
3517
5411
|
const createModel = cmdOpts.ollamaCreate !== false;
|
|
3518
5412
|
if (createModel) {
|
|
3519
5413
|
steps.push(
|
|
@@ -3599,7 +5493,7 @@ function registerModelsCommands(parent) {
|
|
|
3599
5493
|
}
|
|
3600
5494
|
});
|
|
3601
5495
|
models.command("setup-runtime").description("Install an isolated Python runtime for local model serving").option("--python <path>", "Python 3.10-3.12 executable to create the runtime").option("--cache-dir <path>", "Cache directory for the managed runtime").option("-f, --force", "Recreate the runtime if it already exists").option("--print-command", "Print the setup commands without running them").action(async (cmdOpts) => {
|
|
3602
|
-
const cacheDir =
|
|
5496
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3603
5497
|
const venvDir = runtimeDir(cacheDir);
|
|
3604
5498
|
const venvPython = runtimePythonPath(cacheDir);
|
|
3605
5499
|
const python = pickRuntimePython(cmdOpts.python);
|
|
@@ -3645,7 +5539,7 @@ function registerModelsCommands(parent) {
|
|
|
3645
5539
|
});
|
|
3646
5540
|
models.command("serve").description("Serve a downloaded model with an OpenAI-compatible local API").argument("<target>", "Model ID/prefix, model directory, or .tar.gz artifact").option("--spec <path>", "Behavior spec JSON to apply as the default system prompt").option("--no-spec-prompt", "Do not auto-apply a behavior spec system prompt").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "8000").option("--python <path>", "Python executable").option("--cache-dir <path>", "Cache directory for downloaded/extracted models").option("--device <device>", "Inference device: auto, cpu, cuda, or mps", "auto").option("--force-download", "Re-download and re-extract model artifacts").option("--max-tokens <n>", "Default max completion tokens", "512").option("--temperature <n>", "Default sampling temperature", "0.7").option("--json-schema <path>", "JSON Schema file to enforce by default for chat completions").option("--json-repair-attempts <n>", "Number of JSON/schema repair retries", "1").option("--trust-remote-code", "Pass trust_remote_code=True to transformers").option("--managed", "Run a local lifecycle manager in front of the model server").option("--idle-timeout <seconds>", "Managed mode idle timeout before stopping the model", "300").option("--restart-after-requests <n>", "Managed mode restart threshold; 0 disables request-count restarts", "100").option("--gate-field <field>", "Response JSON field to log as the managed serving gate result", "should_process").option("--log-file <path>", "Write managed serving JSONL request logs to a file").option("--print-command", "Print the underlying Python command without starting it").action(async (target, cmdOpts) => {
|
|
3647
5541
|
const opts = parent.opts();
|
|
3648
|
-
const cacheDir =
|
|
5542
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3649
5543
|
mkdirSync3(cacheDir, { recursive: true });
|
|
3650
5544
|
const python = resolveServePython(cacheDir, cmdOpts.python);
|
|
3651
5545
|
if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
|
|
@@ -3694,7 +5588,7 @@ function registerModelsCommands(parent) {
|
|
|
3694
5588
|
idle_timeout_seconds: idleTimeoutSeconds,
|
|
3695
5589
|
restart_after_requests: restartAfterRequests,
|
|
3696
5590
|
gate_field: String(cmdOpts.gateField),
|
|
3697
|
-
log_file: cmdOpts.logFile ?
|
|
5591
|
+
log_file: cmdOpts.logFile ? resolve5(cmdOpts.logFile) : void 0
|
|
3698
5592
|
} : void 0;
|
|
3699
5593
|
if (cmdOpts.printCommand) return printServeCommand(python, args, env, managedConfig);
|
|
3700
5594
|
if (!isJsonMode()) {
|
|
@@ -3731,12 +5625,12 @@ function registerModelsCommands(parent) {
|
|
|
3731
5625
|
idleTimeoutSeconds,
|
|
3732
5626
|
restartAfterRequests,
|
|
3733
5627
|
gateField: String(cmdOpts.gateField),
|
|
3734
|
-
logFile: cmdOpts.logFile ?
|
|
5628
|
+
logFile: cmdOpts.logFile ? resolve5(cmdOpts.logFile) : void 0
|
|
3735
5629
|
});
|
|
3736
5630
|
return;
|
|
3737
5631
|
}
|
|
3738
5632
|
await new Promise((resolvePromise, reject) => {
|
|
3739
|
-
const child =
|
|
5633
|
+
const child = spawn3(python, args, {
|
|
3740
5634
|
stdio: "inherit",
|
|
3741
5635
|
env: { ...process.env, ...env }
|
|
3742
5636
|
});
|
|
@@ -3757,7 +5651,7 @@ function registerModelsCommands(parent) {
|
|
|
3757
5651
|
}
|
|
3758
5652
|
|
|
3759
5653
|
// src/commands/balance.ts
|
|
3760
|
-
import
|
|
5654
|
+
import chalk4 from "chalk";
|
|
3761
5655
|
var KIND_LABELS = {
|
|
3762
5656
|
signup_bonus: "Signup bonus",
|
|
3763
5657
|
topup: "Top-up",
|
|
@@ -3772,8 +5666,8 @@ function formatCents3(cents) {
|
|
|
3772
5666
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
3773
5667
|
}
|
|
3774
5668
|
function formatSigned(cents) {
|
|
3775
|
-
if (cents > 0) return
|
|
3776
|
-
if (cents < 0) return
|
|
5669
|
+
if (cents > 0) return chalk4.green(`+${formatCents3(cents)}`);
|
|
5670
|
+
if (cents < 0) return chalk4.red(formatCents3(cents));
|
|
3777
5671
|
return formatCents3(cents);
|
|
3778
5672
|
}
|
|
3779
5673
|
function registerBalanceCommands(parent) {
|
|
@@ -3788,7 +5682,7 @@ function registerBalanceCommands(parent) {
|
|
|
3788
5682
|
return printJson({ balance, transactions });
|
|
3789
5683
|
}
|
|
3790
5684
|
const lowBalance = balance.balance_cents < 100;
|
|
3791
|
-
const balanceLine = lowBalance ?
|
|
5685
|
+
const balanceLine = lowBalance ? chalk4.red(formatCents3(balance.balance_cents)) + chalk4.dim(" (low)") : chalk4.bold.green(formatCents3(balance.balance_cents));
|
|
3792
5686
|
printDetail([
|
|
3793
5687
|
["Credits", balanceLine],
|
|
3794
5688
|
[
|
|
@@ -3803,7 +5697,7 @@ function registerBalanceCommands(parent) {
|
|
|
3803
5697
|
]);
|
|
3804
5698
|
if (transactions && transactions.length > 0) {
|
|
3805
5699
|
console.log(`
|
|
3806
|
-
${
|
|
5700
|
+
${chalk4.bold("Recent transactions")}`);
|
|
3807
5701
|
printTable(
|
|
3808
5702
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
3809
5703
|
transactions.map((row) => [
|
|
@@ -3811,19 +5705,19 @@ ${chalk3.bold("Recent transactions")}`);
|
|
|
3811
5705
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
3812
5706
|
formatSigned(row.amount_cents),
|
|
3813
5707
|
formatCents3(row.balance_after_cents),
|
|
3814
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
5708
|
+
row.reference_id ? shortId(row.reference_id) : chalk4.dim("\u2014")
|
|
3815
5709
|
])
|
|
3816
5710
|
);
|
|
3817
5711
|
} else {
|
|
3818
5712
|
console.log(
|
|
3819
5713
|
`
|
|
3820
|
-
${
|
|
5714
|
+
${chalk4.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
3821
5715
|
);
|
|
3822
5716
|
}
|
|
3823
5717
|
if (lowBalance) {
|
|
3824
5718
|
console.log(
|
|
3825
5719
|
`
|
|
3826
|
-
${formatStatus("preparing")} ${
|
|
5720
|
+
${formatStatus("preparing")} ${chalk4.yellow(
|
|
3827
5721
|
"Run `tt topup` to add more credits before starting a run."
|
|
3828
5722
|
)}`
|
|
3829
5723
|
);
|
|
@@ -3832,7 +5726,7 @@ ${formatStatus("preparing")} ${chalk3.yellow(
|
|
|
3832
5726
|
}
|
|
3833
5727
|
|
|
3834
5728
|
// src/commands/topup.ts
|
|
3835
|
-
import
|
|
5729
|
+
import chalk5 from "chalk";
|
|
3836
5730
|
import open from "open";
|
|
3837
5731
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
3838
5732
|
var MIN_USD = 5;
|
|
@@ -3871,9 +5765,9 @@ function registerTopupCommands(parent) {
|
|
|
3871
5765
|
"amount is required in --json mode (use --amount <usd>)"
|
|
3872
5766
|
);
|
|
3873
5767
|
}
|
|
3874
|
-
console.log(
|
|
5768
|
+
console.log(chalk5.bold("Add credits via Stripe Checkout"));
|
|
3875
5769
|
console.log(
|
|
3876
|
-
|
|
5770
|
+
chalk5.dim(
|
|
3877
5771
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
3878
5772
|
)
|
|
3879
5773
|
);
|
|
@@ -3900,8 +5794,8 @@ function registerTopupCommands(parent) {
|
|
|
3900
5794
|
}
|
|
3901
5795
|
const url = data.checkout_url;
|
|
3902
5796
|
console.log(
|
|
3903
|
-
`${
|
|
3904
|
-
` +
|
|
5797
|
+
`${chalk5.bold("Checkout URL:")} ${chalk5.cyan(url)}
|
|
5798
|
+
` + chalk5.dim("Complete the payment to credit your account.")
|
|
3905
5799
|
);
|
|
3906
5800
|
if (options.open !== false) {
|
|
3907
5801
|
try {
|
|
@@ -3909,7 +5803,7 @@ function registerTopupCommands(parent) {
|
|
|
3909
5803
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
3910
5804
|
} catch {
|
|
3911
5805
|
console.log(
|
|
3912
|
-
|
|
5806
|
+
chalk5.yellow(
|
|
3913
5807
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
3914
5808
|
)
|
|
3915
5809
|
);
|
|
@@ -3919,7 +5813,7 @@ function registerTopupCommands(parent) {
|
|
|
3919
5813
|
}
|
|
3920
5814
|
|
|
3921
5815
|
// src/commands/eval.ts
|
|
3922
|
-
import
|
|
5816
|
+
import chalk6 from "chalk";
|
|
3923
5817
|
|
|
3924
5818
|
// src/eval/rules.ts
|
|
3925
5819
|
function validateSpec(spec) {
|
|
@@ -4054,12 +5948,12 @@ function checkExamplesAgainstConstraints(spec) {
|
|
|
4054
5948
|
}
|
|
4055
5949
|
return violations;
|
|
4056
5950
|
}
|
|
4057
|
-
function evaluateConstraint(
|
|
5951
|
+
function evaluateConstraint(text2, constraint) {
|
|
4058
5952
|
const lower = constraint.toLowerCase();
|
|
4059
5953
|
const neverMatch = lower.match(/^never (?:share|mention|include|reveal|use|say|output|return|give|provide|show|disclose|expose)\s+(.+)/);
|
|
4060
5954
|
if (neverMatch) {
|
|
4061
5955
|
const forbidden = neverMatch[1].replace(/['"]/g, "").trim();
|
|
4062
|
-
if (
|
|
5956
|
+
if (text2.toLowerCase().includes(forbidden.toLowerCase())) {
|
|
4063
5957
|
return { passed: false, message: `Output contains "${forbidden}"` };
|
|
4064
5958
|
}
|
|
4065
5959
|
return { passed: true };
|
|
@@ -4067,7 +5961,7 @@ function evaluateConstraint(text, constraint) {
|
|
|
4067
5961
|
const alwaysMatch = lower.match(/^always (?:include|use|start with|end with|contain|respond in|reply in)\s+(.+)/);
|
|
4068
5962
|
if (alwaysMatch) {
|
|
4069
5963
|
const required = alwaysMatch[1].replace(/['"]/g, "").trim();
|
|
4070
|
-
if (!
|
|
5964
|
+
if (!text2.toLowerCase().includes(required.toLowerCase())) {
|
|
4071
5965
|
return { passed: false, message: `Output missing "${required}"` };
|
|
4072
5966
|
}
|
|
4073
5967
|
return { passed: true };
|
|
@@ -4080,11 +5974,11 @@ function evaluateConstraint(text, constraint) {
|
|
|
4080
5974
|
|
|
4081
5975
|
// src/commands/eval.ts
|
|
4082
5976
|
function registerEvalCommand(parent) {
|
|
4083
|
-
parent.command("eval").description("Validate a
|
|
5977
|
+
parent.command("eval").description("Validate a behaviour spec project file").option("-m, --model <model>", "Deprecated; ignored because tt eval only validates the local spec").option("-f, --file <path>", "Spec file path", DEFAULT_SPEC_FILE).action(async (cmdOpts) => {
|
|
4084
5978
|
const spec = loadSpec(cmdOpts.file);
|
|
4085
5979
|
const validation = validateSpec(spec);
|
|
4086
5980
|
if (!isJsonMode()) {
|
|
4087
|
-
console.log(
|
|
5981
|
+
console.log(chalk6.dim(`
|
|
4088
5982
|
Spec: ${cmdOpts.file}
|
|
4089
5983
|
`));
|
|
4090
5984
|
}
|
|
@@ -4099,71 +5993,323 @@ Spec: ${cmdOpts.file}
|
|
|
4099
5993
|
});
|
|
4100
5994
|
}
|
|
4101
5995
|
function printValidation(checks) {
|
|
4102
|
-
console.log(
|
|
5996
|
+
console.log(chalk6.bold("Spec Validation"));
|
|
4103
5997
|
for (const check of checks) {
|
|
4104
|
-
const icon = check.passed ?
|
|
4105
|
-
const msg = check.message ?
|
|
5998
|
+
const icon = check.passed ? chalk6.green("\u2713") : chalk6.red("\u2717");
|
|
5999
|
+
const msg = check.message ? chalk6.dim(` \u2014 ${check.message}`) : "";
|
|
4106
6000
|
console.log(` ${icon} ${check.name}${msg}`);
|
|
4107
6001
|
}
|
|
4108
6002
|
}
|
|
4109
6003
|
|
|
4110
6004
|
// src/commands/push.ts
|
|
4111
6005
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4112
|
-
import { resolve as
|
|
6006
|
+
import { resolve as resolve6 } from "path";
|
|
6007
|
+
function persistRemoteId(filePath, id) {
|
|
6008
|
+
const raw = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
6009
|
+
raw.id = id;
|
|
6010
|
+
writeFileSync4(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
6011
|
+
}
|
|
4113
6012
|
function registerPushCommand(parent) {
|
|
4114
6013
|
parent.command("push").description("Push local spec to the Tuned Tensor API").option("-f, --file <path>", "Spec file path", DEFAULT_SPEC_FILE).action(async (cmdOpts) => {
|
|
4115
6014
|
const opts = parent.opts();
|
|
4116
|
-
const filePath =
|
|
6015
|
+
const filePath = resolve6(cmdOpts.file);
|
|
4117
6016
|
const spec = loadSpec(cmdOpts.file);
|
|
4118
6017
|
const evalCaseErrors = validateEvalCases(spec);
|
|
4119
6018
|
if (evalCaseErrors.length > 0) {
|
|
4120
6019
|
throw new Error(`Invalid eval_cases: ${evalCaseErrors.join("; ")}`);
|
|
4121
6020
|
}
|
|
4122
|
-
const
|
|
4123
|
-
const body =
|
|
6021
|
+
const rawSpec = spec;
|
|
6022
|
+
const { body } = projectCloudSpec(rawSpec);
|
|
6023
|
+
const id = spec.id;
|
|
6024
|
+
const canRecoverLocalId = Boolean(
|
|
6025
|
+
id && hasLocalOnlySpecFields(rawSpec)
|
|
6026
|
+
);
|
|
4124
6027
|
let data;
|
|
6028
|
+
let created = false;
|
|
4125
6029
|
if (id) {
|
|
4126
|
-
|
|
4127
|
-
|
|
6030
|
+
try {
|
|
6031
|
+
const res = await put(
|
|
6032
|
+
`/behavior-specs/${id}`,
|
|
6033
|
+
body,
|
|
6034
|
+
opts
|
|
6035
|
+
);
|
|
6036
|
+
data = res.data;
|
|
6037
|
+
} catch (error) {
|
|
6038
|
+
if (!canRecoverLocalId || !(error instanceof ApiError) || error.status !== 404) {
|
|
6039
|
+
throw error;
|
|
6040
|
+
}
|
|
6041
|
+
const res = await post("/behavior-specs", body, opts);
|
|
6042
|
+
data = res.data;
|
|
6043
|
+
created = true;
|
|
6044
|
+
persistRemoteId(filePath, data.id);
|
|
6045
|
+
}
|
|
4128
6046
|
} else {
|
|
4129
6047
|
const res = await post("/behavior-specs", body, opts);
|
|
4130
6048
|
data = res.data;
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
writeFileSync4(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
6049
|
+
created = true;
|
|
6050
|
+
persistRemoteId(filePath, data.id);
|
|
4134
6051
|
}
|
|
4135
6052
|
if (isJsonMode()) return printJson(data);
|
|
4136
6053
|
printSuccess(
|
|
4137
|
-
`Spec ${
|
|
6054
|
+
`Spec ${created ? "created" : "updated"}: ${data.name} (${shortId(data.id)})`
|
|
4138
6055
|
);
|
|
4139
6056
|
});
|
|
4140
6057
|
}
|
|
4141
6058
|
|
|
4142
|
-
// src/
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
6059
|
+
// src/cli.ts
|
|
6060
|
+
async function runSelfCommand(args, options = {}) {
|
|
6061
|
+
const entrypoint = options.entrypoint ?? process.argv[1];
|
|
6062
|
+
if (!entrypoint) {
|
|
6063
|
+
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
6064
|
+
}
|
|
6065
|
+
return await new Promise((resolve7, reject) => {
|
|
6066
|
+
const child = spawn4(process.execPath, [entrypoint, ...args], {
|
|
6067
|
+
cwd: options.cwd,
|
|
6068
|
+
env: options.env ?? process.env,
|
|
6069
|
+
stdio: "inherit",
|
|
6070
|
+
detached: process.platform !== "win32"
|
|
6071
|
+
});
|
|
6072
|
+
const forwardSignal = (signal) => {
|
|
6073
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
6074
|
+
if (child.pid && process.platform !== "win32") {
|
|
6075
|
+
try {
|
|
6076
|
+
process.kill(-child.pid, childSignal);
|
|
6077
|
+
return;
|
|
6078
|
+
} catch {
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
child.kill(childSignal);
|
|
6082
|
+
};
|
|
6083
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
6084
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
6085
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
6086
|
+
const cleanup = () => {
|
|
6087
|
+
process.off("SIGINT", onSigint);
|
|
6088
|
+
process.off("SIGTERM", onSigterm);
|
|
6089
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
6090
|
+
};
|
|
6091
|
+
process.on("SIGINT", onSigint);
|
|
6092
|
+
process.on("SIGTERM", onSigterm);
|
|
6093
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
6094
|
+
child.once("error", (error) => {
|
|
6095
|
+
cleanup();
|
|
6096
|
+
reject(error);
|
|
6097
|
+
});
|
|
6098
|
+
child.once("close", (exitCode, signal) => {
|
|
6099
|
+
cleanup();
|
|
6100
|
+
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
6101
|
+
resolve7({
|
|
6102
|
+
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
6103
|
+
signal
|
|
6104
|
+
});
|
|
6105
|
+
});
|
|
6106
|
+
});
|
|
6107
|
+
}
|
|
6108
|
+
function extractPassthroughOptions(args, root) {
|
|
6109
|
+
let json = Boolean(root.json);
|
|
6110
|
+
let color = root.color !== false;
|
|
6111
|
+
const forwarded = [];
|
|
6112
|
+
for (const arg of args) {
|
|
6113
|
+
if (arg === "--json") {
|
|
6114
|
+
json = true;
|
|
6115
|
+
} else if (arg === "--no-color") {
|
|
6116
|
+
color = false;
|
|
6117
|
+
} else {
|
|
6118
|
+
forwarded.push(arg);
|
|
6119
|
+
}
|
|
4152
6120
|
}
|
|
4153
|
-
}
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
6121
|
+
return { args: forwarded, json, color };
|
|
6122
|
+
}
|
|
6123
|
+
function childEnvironment(root, base) {
|
|
6124
|
+
return {
|
|
6125
|
+
...base,
|
|
6126
|
+
...root.apiKey ? { TUNED_TENSOR_API_KEY: root.apiKey } : {},
|
|
6127
|
+
...root.baseUrl ? { TUNED_TENSOR_URL: root.baseUrl } : {},
|
|
6128
|
+
...root.color === false ? { FORCE_COLOR: "0" } : {}
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
function applyExitCode(result) {
|
|
6132
|
+
if (result?.exitCode !== null && result?.exitCode !== void 0) {
|
|
6133
|
+
process.exitCode = result.exitCode;
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
function createProgram(version, runtime = {}) {
|
|
6137
|
+
const program = new Command();
|
|
6138
|
+
const env = runtime.env ?? process.env;
|
|
6139
|
+
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6140
|
+
const invokeLocal = runtime.runLocalCommand ?? executeLocalCommand;
|
|
6141
|
+
const launchShell = runtime.startShell ?? startInteractiveShell;
|
|
6142
|
+
const cwd = runtime.cwd ?? process.cwd();
|
|
6143
|
+
const shellRunner = async (request2) => {
|
|
6144
|
+
const root = program.opts();
|
|
6145
|
+
const args = request2.target === "local" ? ["local", ...request2.args] : request2.args;
|
|
6146
|
+
return await invokeSelf(args, {
|
|
6147
|
+
cwd: request2.cwd,
|
|
6148
|
+
env: childEnvironment(root, env),
|
|
6149
|
+
entrypoint: runtime.argv?.[1]
|
|
6150
|
+
});
|
|
6151
|
+
};
|
|
6152
|
+
const openShell = async () => {
|
|
6153
|
+
const root = program.opts();
|
|
6154
|
+
const shellEnvironment = childEnvironment(root, env);
|
|
6155
|
+
await launchShell({
|
|
6156
|
+
runner: shellRunner,
|
|
6157
|
+
input: runtime.stdin ?? process.stdin,
|
|
6158
|
+
output: runtime.stdout ?? process.stdout,
|
|
6159
|
+
error: runtime.stderr ?? process.stderr,
|
|
6160
|
+
cwd,
|
|
6161
|
+
env: shellEnvironment,
|
|
6162
|
+
version
|
|
6163
|
+
});
|
|
6164
|
+
};
|
|
6165
|
+
program.name("tt").description("Tuned Tensor \u2014 one terminal for cloud and local fine-tuning").version(version).option("-k, --api-key <key>", "API key (overrides stored key)").option(
|
|
6166
|
+
"-u, --base-url <url>",
|
|
6167
|
+
"API base URL (default: https://tunedtensor.com)"
|
|
6168
|
+
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
|
|
6169
|
+
const root = program.opts();
|
|
6170
|
+
if (root.json) setJsonMode(true);
|
|
6171
|
+
if (root.color === false) {
|
|
6172
|
+
process.env.FORCE_COLOR = "0";
|
|
6173
|
+
chalk7.level = 0;
|
|
6174
|
+
}
|
|
6175
|
+
});
|
|
6176
|
+
registerAuthCommands(program);
|
|
6177
|
+
registerSpecsCommands(program);
|
|
6178
|
+
registerRunsCommands(program);
|
|
6179
|
+
registerDatasetsCommands(program);
|
|
6180
|
+
registerLabelCommands(program);
|
|
6181
|
+
registerModelsCommands(program);
|
|
6182
|
+
registerBalanceCommands(program);
|
|
6183
|
+
registerTopupCommands(program);
|
|
6184
|
+
registerInitCommand(program);
|
|
6185
|
+
registerEvalCommand(program);
|
|
6186
|
+
registerPushCommand(program);
|
|
6187
|
+
program.command("status").description("Show read-only cloud, local, and project context").option("--target <target>", "Show cloud or local workflow status").action(async (commandOptions) => {
|
|
6188
|
+
const root = program.opts();
|
|
6189
|
+
const context = await discoverShellContext({
|
|
6190
|
+
cwd,
|
|
6191
|
+
env: childEnvironment(root, env)
|
|
6192
|
+
});
|
|
6193
|
+
const target = commandOptions.target ?? context.inferredTarget;
|
|
6194
|
+
if (target !== "cloud" && target !== "local") {
|
|
6195
|
+
throw new Error("--target must be cloud or local.");
|
|
6196
|
+
}
|
|
6197
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6198
|
+
if (isJsonMode()) {
|
|
6199
|
+
output.write(`${JSON.stringify({
|
|
6200
|
+
target,
|
|
6201
|
+
context
|
|
6202
|
+
}, null, 2)}
|
|
6203
|
+
`);
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
6206
|
+
output.write(`${chalk7.bold.cyan("Tuned Tensor status")}
|
|
6207
|
+
`);
|
|
6208
|
+
output.write(`${formatShellStatus(context, target).join("\n")}
|
|
6209
|
+
|
|
6210
|
+
`);
|
|
6211
|
+
output.write(`${chalk7.bold("Context")}
|
|
6212
|
+
`);
|
|
6213
|
+
output.write(
|
|
6214
|
+
`${formatShellContext(
|
|
6215
|
+
context,
|
|
6216
|
+
target,
|
|
6217
|
+
commandOptions.target ? "command-option" : context.targetSource
|
|
6218
|
+
).join("\n")}
|
|
6219
|
+
`
|
|
6220
|
+
);
|
|
6221
|
+
});
|
|
6222
|
+
program.command("local").description("Run the local CUDA training and model workflow").helpOption(false).allowUnknownOption().argument("[args...]", "TT Local command and arguments").action(async (args) => {
|
|
6223
|
+
const root = program.opts();
|
|
6224
|
+
const passthrough = extractPassthroughOptions(args, root);
|
|
6225
|
+
if (!passthrough.color) chalk7.level = 0;
|
|
6226
|
+
const result = await invokeLocal(
|
|
6227
|
+
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6228
|
+
{
|
|
6229
|
+
jsonMode: passthrough.json,
|
|
6230
|
+
cwd,
|
|
6231
|
+
env: childEnvironment(
|
|
6232
|
+
{ ...root, color: passthrough.color },
|
|
6233
|
+
env
|
|
6234
|
+
),
|
|
6235
|
+
stdin: runtime.stdin ?? process.stdin,
|
|
6236
|
+
stdout: runtime.stdout ?? process.stdout,
|
|
6237
|
+
stderr: runtime.stderr ?? process.stderr
|
|
6238
|
+
}
|
|
6239
|
+
);
|
|
6240
|
+
applyExitCode(result);
|
|
6241
|
+
});
|
|
6242
|
+
program.command("cloud").description("Run a hosted command explicitly").helpOption(false).allowUnknownOption().argument("[args...]", "Hosted tt command and arguments").action(async (args) => {
|
|
6243
|
+
const root = program.opts();
|
|
6244
|
+
const passthrough = extractPassthroughOptions(args, root);
|
|
6245
|
+
const forwarded = [
|
|
6246
|
+
...passthrough.json ? ["--json"] : [],
|
|
6247
|
+
...!passthrough.color ? ["--no-color"] : [],
|
|
6248
|
+
...passthrough.args.length > 0 ? passthrough.args : ["--help"]
|
|
6249
|
+
];
|
|
6250
|
+
const result = await invokeSelf(forwarded, {
|
|
6251
|
+
cwd,
|
|
6252
|
+
env: childEnvironment(
|
|
6253
|
+
{ ...root, color: passthrough.color },
|
|
6254
|
+
env
|
|
6255
|
+
),
|
|
6256
|
+
entrypoint: runtime.argv?.[1]
|
|
6257
|
+
});
|
|
6258
|
+
applyExitCode(result);
|
|
6259
|
+
});
|
|
6260
|
+
program.command("shell").description("Open the interactive cloud/local terminal").action(openShell);
|
|
6261
|
+
program.addHelpText(
|
|
6262
|
+
"after",
|
|
6263
|
+
`
|
|
6264
|
+
Workflows:
|
|
6265
|
+
tt Open the interactive terminal (TTY only)
|
|
6266
|
+
tt status Inspect cloud/local project context
|
|
6267
|
+
tt cloud <command> Run a hosted command explicitly
|
|
6268
|
+
tt local <command> Run a local GPU command
|
|
6269
|
+
|
|
6270
|
+
Examples:
|
|
6271
|
+
tt runs list
|
|
6272
|
+
tt local doctor tunedtensor.json
|
|
6273
|
+
tt local run tunedtensor.json --dry-run
|
|
6274
|
+
`
|
|
6275
|
+
);
|
|
6276
|
+
return program;
|
|
6277
|
+
}
|
|
6278
|
+
async function runCli(version, runtime = {}) {
|
|
6279
|
+
const argv = runtime.argv ?? process.argv;
|
|
6280
|
+
const env = runtime.env ?? process.env;
|
|
6281
|
+
const args = argv.slice(2);
|
|
6282
|
+
if (shouldStartInteractiveShell({
|
|
6283
|
+
args,
|
|
6284
|
+
stdinIsTTY: runtime.stdinIsTTY ?? process.stdin.isTTY,
|
|
6285
|
+
stdoutIsTTY: runtime.stdoutIsTTY ?? process.stdout.isTTY,
|
|
6286
|
+
env
|
|
6287
|
+
})) {
|
|
6288
|
+
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6289
|
+
await (runtime.startShell ?? startInteractiveShell)({
|
|
6290
|
+
runner: async (request2) => await invokeSelf(
|
|
6291
|
+
request2.target === "local" ? ["local", ...request2.args] : request2.args,
|
|
6292
|
+
{
|
|
6293
|
+
cwd: request2.cwd,
|
|
6294
|
+
env,
|
|
6295
|
+
entrypoint: argv[1]
|
|
6296
|
+
}
|
|
6297
|
+
),
|
|
6298
|
+
input: runtime.stdin ?? process.stdin,
|
|
6299
|
+
output: runtime.stdout ?? process.stdout,
|
|
6300
|
+
error: runtime.stderr ?? process.stderr,
|
|
6301
|
+
cwd: runtime.cwd ?? process.cwd(),
|
|
6302
|
+
env,
|
|
6303
|
+
version
|
|
6304
|
+
});
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
if (argv.includes("--json")) setJsonMode(true);
|
|
6308
|
+
await createProgram(version, { ...runtime, argv }).parseAsync(argv);
|
|
6309
|
+
}
|
|
6310
|
+
|
|
6311
|
+
// src/index.ts
|
|
6312
|
+
runCli("0.6.0").catch((err) => {
|
|
4167
6313
|
reportError(err);
|
|
4168
6314
|
process.exit(1);
|
|
4169
6315
|
});
|