@tuned-tensor/cli 0.4.23 → 0.5.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 +106 -11
- package/dist/index.js +2347 -315
- 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());
|
|
@@ -166,95 +332,1774 @@ function printDetail(fields) {
|
|
|
166
332
|
console.log(`${chalk.bold.cyan(paddedLabel)} ${value ?? chalk.dim("\u2014")}`);
|
|
167
333
|
}
|
|
168
334
|
}
|
|
169
|
-
function printSuccess(message) {
|
|
170
|
-
console.log(chalk.green("\u2713") + " " + message);
|
|
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: "/cd", description: "Change the shell's working directory." },
|
|
1294
|
+
{ path: "/clear", description: "Clear the terminal." },
|
|
1295
|
+
{ path: "/exit", description: "Exit the TT shell." }
|
|
1296
|
+
];
|
|
1297
|
+
function catalogForMode(mode) {
|
|
1298
|
+
return COMMAND_CATALOG.filter((command) => command.modes.includes(mode));
|
|
1299
|
+
}
|
|
1300
|
+
function commandPathsForMode(mode) {
|
|
1301
|
+
return [...new Set(catalogForMode(mode).map((command) => command.path))].sort((left, right) => left.localeCompare(right));
|
|
1302
|
+
}
|
|
1303
|
+
function groupedCatalog(mode, query) {
|
|
1304
|
+
const normalizedQuery = query?.trim().toLowerCase();
|
|
1305
|
+
const matches = catalogForMode(mode).filter((command) => {
|
|
1306
|
+
if (!normalizedQuery) return true;
|
|
1307
|
+
return command.path.toLowerCase().includes(normalizedQuery) || command.description.toLowerCase().includes(normalizedQuery) || command.group.toLowerCase().includes(normalizedQuery);
|
|
1308
|
+
});
|
|
1309
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1310
|
+
for (const command of matches) {
|
|
1311
|
+
const group = groups.get(command.group) ?? [];
|
|
1312
|
+
group.push(command);
|
|
1313
|
+
groups.set(command.group, group);
|
|
1314
|
+
}
|
|
1315
|
+
return groups;
|
|
1316
|
+
}
|
|
1317
|
+
function completionTarget(line, activeMode) {
|
|
1318
|
+
const leadingWhitespace = line.match(/^\s*/)?.[0] ?? "";
|
|
1319
|
+
const content = line.slice(leadingWhitespace.length);
|
|
1320
|
+
const override = content.match(/^(cloud|local)(?:\s+|$)/);
|
|
1321
|
+
if (!override) {
|
|
1322
|
+
return { prefix: leadingWhitespace, mode: activeMode, fragment: content };
|
|
1323
|
+
}
|
|
1324
|
+
const mode = override[1];
|
|
1325
|
+
const prefix = `${leadingWhitespace}${mode} `;
|
|
1326
|
+
return {
|
|
1327
|
+
prefix,
|
|
1328
|
+
mode,
|
|
1329
|
+
fragment: content.slice(override[0].length)
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
function createCommandCompleter(getMode) {
|
|
1333
|
+
return (line) => {
|
|
1334
|
+
const trimmed = line.trimStart();
|
|
1335
|
+
if (trimmed.startsWith("/")) {
|
|
1336
|
+
const candidates = SLASH_COMMANDS.map((command) => command.path);
|
|
1337
|
+
const matches2 = candidates.filter((candidate) => candidate.startsWith(trimmed));
|
|
1338
|
+
return [matches2.length > 0 ? matches2 : candidates, line];
|
|
1339
|
+
}
|
|
1340
|
+
if (trimmed.startsWith("?")) {
|
|
1341
|
+
return [["?"], line];
|
|
1342
|
+
}
|
|
1343
|
+
const { prefix, mode, fragment } = completionTarget(line, getMode());
|
|
1344
|
+
const paths = commandPathsForMode(mode);
|
|
1345
|
+
const targetCandidates = paths.map((path) => `${prefix}${path}`);
|
|
1346
|
+
if (!prefix.trim()) targetCandidates.unshift("cloud ", "local ");
|
|
1347
|
+
const normalizedLine = line.trimStart().toLowerCase();
|
|
1348
|
+
const normalizedFragment = fragment.toLowerCase();
|
|
1349
|
+
const matches = targetCandidates.filter((candidate) => {
|
|
1350
|
+
if (prefix.trim()) return candidate.toLowerCase().startsWith(`${prefix.toLowerCase()}${normalizedFragment}`);
|
|
1351
|
+
return candidate.toLowerCase().startsWith(normalizedLine);
|
|
1352
|
+
});
|
|
1353
|
+
return [matches.length > 0 ? matches : targetCandidates, line];
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/shell-context.ts
|
|
1358
|
+
import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
|
|
1359
|
+
import { homedir as homedir2 } from "os";
|
|
1360
|
+
import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
|
|
1361
|
+
async function readJsonObject(path) {
|
|
1362
|
+
try {
|
|
1363
|
+
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
1364
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1365
|
+
return { found: true, invalid: true };
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
found: true,
|
|
1369
|
+
value: parsed,
|
|
1370
|
+
invalid: false
|
|
1371
|
+
};
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
if (error.code === "ENOENT") {
|
|
1374
|
+
return { found: false, invalid: false };
|
|
1375
|
+
}
|
|
1376
|
+
return { found: true, invalid: true };
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function stringField(value, key) {
|
|
1380
|
+
const field = value?.[key];
|
|
1381
|
+
return typeof field === "string" && field.trim() ? field : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function expandPath(value, baseDirectory, homeDirectory) {
|
|
1384
|
+
if (value === "~") return homeDirectory;
|
|
1385
|
+
if (value.startsWith("~/")) return resolve2(homeDirectory, value.slice(2));
|
|
1386
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(baseDirectory, value);
|
|
1387
|
+
}
|
|
1388
|
+
function environmentHome(env) {
|
|
1389
|
+
return env.HOME ? resolve2(env.HOME) : homedir2();
|
|
1390
|
+
}
|
|
1391
|
+
function cloudConfigPath(env, homeDirectory) {
|
|
1392
|
+
const configHome = env.XDG_CONFIG_HOME ? resolve2(env.XDG_CONFIG_HOME) : join3(homeDirectory, ".config");
|
|
1393
|
+
return join3(configHome, "tuned-tensor", "config.json");
|
|
1394
|
+
}
|
|
1395
|
+
function redactApiKey(key) {
|
|
1396
|
+
if (!key) return void 0;
|
|
1397
|
+
if (key.length <= 4) return "\u2026";
|
|
1398
|
+
const prefixLength = key.length > 8 ? 8 : Math.max(1, Math.floor(key.length / 2));
|
|
1399
|
+
return `${key.slice(0, prefixLength)}\u2026`;
|
|
1400
|
+
}
|
|
1401
|
+
function targetFromEnvironment(env) {
|
|
1402
|
+
const value = env.TT_TARGET?.trim().toLowerCase();
|
|
1403
|
+
return value === "cloud" || value === "local" ? value : void 0;
|
|
1404
|
+
}
|
|
1405
|
+
async function adjacentFile(path) {
|
|
1406
|
+
try {
|
|
1407
|
+
return (await stat2(path)).isFile();
|
|
1408
|
+
} catch {
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async function readActiveModelId(storeRoot) {
|
|
1413
|
+
const result = await readJsonObject(join3(storeRoot, "active-model.json"));
|
|
1414
|
+
if (!result.found || result.invalid) return void 0;
|
|
1415
|
+
const modelId = result.value?.model_id;
|
|
1416
|
+
if (modelId === null) return "base";
|
|
1417
|
+
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
1418
|
+
}
|
|
1419
|
+
async function readLatestRun(storeRoot) {
|
|
1420
|
+
const runsRoot = join3(storeRoot, "runs");
|
|
1421
|
+
let names;
|
|
1422
|
+
try {
|
|
1423
|
+
names = (await readdir(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1424
|
+
} catch {
|
|
1425
|
+
return void 0;
|
|
1426
|
+
}
|
|
1427
|
+
const states = await Promise.all(names.map(async (name) => {
|
|
1428
|
+
const result = await readJsonObject(join3(runsRoot, name, "state.json"));
|
|
1429
|
+
if (!result.found || result.invalid || !result.value) return void 0;
|
|
1430
|
+
const id = stringField(result.value, "id") ?? name;
|
|
1431
|
+
const state = {
|
|
1432
|
+
id,
|
|
1433
|
+
status: stringField(result.value, "status"),
|
|
1434
|
+
specName: stringField(result.value, "spec_name"),
|
|
1435
|
+
updatedAt: stringField(result.value, "updated_at")
|
|
1436
|
+
};
|
|
1437
|
+
return state;
|
|
1438
|
+
}));
|
|
1439
|
+
return states.filter((state) => Boolean(state)).sort((left, right) => {
|
|
1440
|
+
const leftUpdated = left.updatedAt ?? "";
|
|
1441
|
+
const rightUpdated = right.updatedAt ?? "";
|
|
1442
|
+
if (leftUpdated !== rightUpdated) {
|
|
1443
|
+
return rightUpdated.localeCompare(leftUpdated);
|
|
1444
|
+
}
|
|
1445
|
+
return right.id.localeCompare(left.id);
|
|
1446
|
+
})[0];
|
|
1447
|
+
}
|
|
1448
|
+
async function discoverShellContext(options = {}) {
|
|
1449
|
+
const env = options.env ?? process.env;
|
|
1450
|
+
const cwd = resolve2(options.cwd ?? process.cwd());
|
|
1451
|
+
const homeDirectory = environmentHome(env);
|
|
1452
|
+
const warnings = [];
|
|
1453
|
+
const specPath = join3(cwd, "tunedtensor.json");
|
|
1454
|
+
const specJson = await readJsonObject(specPath);
|
|
1455
|
+
let spec;
|
|
1456
|
+
if (specJson.found) {
|
|
1457
|
+
if (specJson.invalid || !specJson.value) {
|
|
1458
|
+
spec = { path: specPath, parseError: true };
|
|
1459
|
+
warnings.push("tunedtensor.json could not be parsed.");
|
|
1460
|
+
} else {
|
|
1461
|
+
const examples = specJson.value.examples;
|
|
1462
|
+
spec = {
|
|
1463
|
+
path: specPath,
|
|
1464
|
+
id: stringField(specJson.value, "id"),
|
|
1465
|
+
name: stringField(specJson.value, "name"),
|
|
1466
|
+
baseModel: stringField(specJson.value, "base_model"),
|
|
1467
|
+
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
1468
|
+
parseError: false
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const localConfigPath = join3(cwd, "local-runner.json");
|
|
1473
|
+
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
1474
|
+
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
1475
|
+
if (localConfigJson.invalid) {
|
|
1476
|
+
warnings.push("local-runner.json could not be parsed.");
|
|
1477
|
+
}
|
|
1478
|
+
const localConfigDirectory = hasAdjacentLocalConfig ? dirname2(localConfigPath) : cwd;
|
|
1479
|
+
const configuredArtifactRoot = stringField(localConfigJson.value, "artifactRoot");
|
|
1480
|
+
const configuredStoreRoot = stringField(localConfigJson.value, "storeRoot");
|
|
1481
|
+
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, homeDirectory) : resolve2(cwd, ".tt-local", "artifacts");
|
|
1482
|
+
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, homeDirectory) : env.TT_LOCAL_HOME ? expandPath(env.TT_LOCAL_HOME, cwd, homeDirectory) : join3(homeDirectory, ".tuned-tensor-local");
|
|
1483
|
+
const cloudPath = cloudConfigPath(env, homeDirectory);
|
|
1484
|
+
const cloudConfigJson = await readJsonObject(cloudPath);
|
|
1485
|
+
if (cloudConfigJson.invalid) {
|
|
1486
|
+
warnings.push("The Tuned Tensor cloud config could not be parsed.");
|
|
1487
|
+
}
|
|
1488
|
+
const environmentApiKey = env.TUNED_TENSOR_API_KEY?.trim();
|
|
1489
|
+
const storedApiKey = stringField(cloudConfigJson.value, "api_key");
|
|
1490
|
+
const apiKey = environmentApiKey || storedApiKey;
|
|
1491
|
+
const baseUrl = env.TUNED_TENSOR_URL?.trim() || stringField(cloudConfigJson.value, "base_url") || "https://tunedtensor.com";
|
|
1492
|
+
const environmentTarget = targetFromEnvironment(env);
|
|
1493
|
+
if (env.TT_TARGET && !environmentTarget) {
|
|
1494
|
+
warnings.push(`Ignoring invalid TT_TARGET=${JSON.stringify(env.TT_TARGET)}; use cloud or local.`);
|
|
1495
|
+
}
|
|
1496
|
+
const inferredTarget = environmentTarget ?? (hasAdjacentLocalConfig ? "local" : "cloud");
|
|
1497
|
+
const targetSource = environmentTarget ? "environment" : hasAdjacentLocalConfig ? "adjacent-config" : "default-cloud";
|
|
1498
|
+
const [activeModelId, latestRun] = await Promise.all([
|
|
1499
|
+
readActiveModelId(storeRoot),
|
|
1500
|
+
readLatestRun(storeRoot)
|
|
1501
|
+
]);
|
|
1502
|
+
return {
|
|
1503
|
+
cwd,
|
|
1504
|
+
projectName: basename3(cwd) || cwd,
|
|
1505
|
+
inferredTarget,
|
|
1506
|
+
targetSource,
|
|
1507
|
+
spec,
|
|
1508
|
+
cloud: {
|
|
1509
|
+
authenticated: Boolean(apiKey),
|
|
1510
|
+
keyPrefix: redactApiKey(apiKey),
|
|
1511
|
+
baseUrl,
|
|
1512
|
+
configPath: cloudPath,
|
|
1513
|
+
configFound: cloudConfigJson.found
|
|
1514
|
+
},
|
|
1515
|
+
local: {
|
|
1516
|
+
configPath: hasAdjacentLocalConfig ? localConfigPath : void 0,
|
|
1517
|
+
artifactRoot,
|
|
1518
|
+
storeRoot,
|
|
1519
|
+
activeModelId,
|
|
1520
|
+
latestRun
|
|
1521
|
+
},
|
|
1522
|
+
warnings
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function valueOrDash(value) {
|
|
1526
|
+
return value === void 0 || value === "" ? "\u2014" : String(value);
|
|
1527
|
+
}
|
|
1528
|
+
function targetSourceLabel(source) {
|
|
1529
|
+
switch (source) {
|
|
1530
|
+
case "environment":
|
|
1531
|
+
return "TT_TARGET";
|
|
1532
|
+
case "adjacent-config":
|
|
1533
|
+
return "adjacent local-runner.json";
|
|
1534
|
+
default:
|
|
1535
|
+
return "default";
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function formatShellContext(context, selectedTarget, targetSource) {
|
|
1539
|
+
const specLabel = context.spec ? context.spec.parseError ? `${context.spec.path} (invalid JSON)` : context.spec.path : "not found";
|
|
1540
|
+
const sourceLabel = targetSource === "session" ? "session" : targetSource === "command-option" ? "--target" : targetSourceLabel(targetSource);
|
|
1541
|
+
const lines = [
|
|
1542
|
+
`Target ${selectedTarget} (${sourceLabel})`,
|
|
1543
|
+
`Project ${context.projectName}`,
|
|
1544
|
+
`Directory ${context.cwd}`,
|
|
1545
|
+
`Spec ${specLabel}`,
|
|
1546
|
+
`Spec ID ${valueOrDash(context.spec?.id)}`,
|
|
1547
|
+
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
1548
|
+
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
1549
|
+
`Cloud endpoint ${context.cloud.baseUrl}`,
|
|
1550
|
+
`Cloud auth ${context.cloud.authenticated ? `yes (${context.cloud.keyPrefix})` : "no"}`,
|
|
1551
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1552
|
+
`Artifact root ${context.local.artifactRoot}`,
|
|
1553
|
+
`Store root ${context.local.storeRoot}`
|
|
1554
|
+
];
|
|
1555
|
+
for (const warning of context.warnings) lines.push(`Warning ${warning}`);
|
|
1556
|
+
return lines;
|
|
1557
|
+
}
|
|
1558
|
+
function formatShellStatus(context, selectedTarget) {
|
|
1559
|
+
const lines = [
|
|
1560
|
+
`Workflow ${selectedTarget}`,
|
|
1561
|
+
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`
|
|
1562
|
+
];
|
|
1563
|
+
if (selectedTarget === "cloud") {
|
|
1564
|
+
lines.push(
|
|
1565
|
+
`Authentication ${context.cloud.authenticated ? `ready (${context.cloud.keyPrefix})` : "not configured"}`,
|
|
1566
|
+
`Endpoint ${context.cloud.baseUrl}`,
|
|
1567
|
+
"Remote status not queried"
|
|
1568
|
+
);
|
|
1569
|
+
} else {
|
|
1570
|
+
lines.push(
|
|
1571
|
+
`Local config ${context.local.configPath ?? "not found"}`,
|
|
1572
|
+
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
1573
|
+
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
1574
|
+
"Host checks not run (use doctor)"
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
return lines;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/shell.ts
|
|
1581
|
+
var ShellParseError = class extends Error {
|
|
1582
|
+
constructor(message) {
|
|
1583
|
+
super(message);
|
|
1584
|
+
this.name = "ShellParseError";
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
function operatorError(operator) {
|
|
1588
|
+
return new ShellParseError(
|
|
1589
|
+
`Shell operator ${JSON.stringify(operator)} is not supported. Run TT commands directly; pipes, redirects, and command substitution are disabled.`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
function tokenizeShellInput(input) {
|
|
1593
|
+
if (input.includes("\0")) {
|
|
1594
|
+
throw new ShellParseError("Command input must not contain NUL bytes.");
|
|
1595
|
+
}
|
|
1596
|
+
if (/[\r\n]/.test(input)) {
|
|
1597
|
+
throw new ShellParseError("Enter one command at a time.");
|
|
1598
|
+
}
|
|
1599
|
+
if (input.trimStart().startsWith("!")) {
|
|
1600
|
+
throw new ShellParseError("Shell escapes are disabled; enter a TT command instead.");
|
|
1601
|
+
}
|
|
1602
|
+
const tokens = [];
|
|
1603
|
+
let token = "";
|
|
1604
|
+
let tokenStarted = false;
|
|
1605
|
+
let quote = "none";
|
|
1606
|
+
const finishToken = () => {
|
|
1607
|
+
if (!tokenStarted) return;
|
|
1608
|
+
tokens.push(token);
|
|
1609
|
+
token = "";
|
|
1610
|
+
tokenStarted = false;
|
|
1611
|
+
};
|
|
1612
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1613
|
+
const character = input[index];
|
|
1614
|
+
if (quote === "single") {
|
|
1615
|
+
if (character === "'") {
|
|
1616
|
+
quote = "none";
|
|
1617
|
+
} else {
|
|
1618
|
+
token += character;
|
|
1619
|
+
}
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (quote === "double") {
|
|
1623
|
+
if (character === '"') {
|
|
1624
|
+
quote = "none";
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
if (character === "\\") {
|
|
1628
|
+
const escaped = input[index + 1];
|
|
1629
|
+
if (escaped === void 0) {
|
|
1630
|
+
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1631
|
+
}
|
|
1632
|
+
token += escaped;
|
|
1633
|
+
tokenStarted = true;
|
|
1634
|
+
index += 1;
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
1638
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
1639
|
+
throw operatorError("$(\u2026)");
|
|
1640
|
+
}
|
|
1641
|
+
token += character;
|
|
1642
|
+
tokenStarted = true;
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
if (/\s/.test(character)) {
|
|
1646
|
+
finishToken();
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (character === "'") {
|
|
1650
|
+
quote = "single";
|
|
1651
|
+
tokenStarted = true;
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (character === '"') {
|
|
1655
|
+
quote = "double";
|
|
1656
|
+
tokenStarted = true;
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (character === "\\") {
|
|
1660
|
+
const escaped = input[index + 1];
|
|
1661
|
+
if (escaped === void 0) {
|
|
1662
|
+
throw new ShellParseError("Command ends with an unfinished escape.");
|
|
1663
|
+
}
|
|
1664
|
+
token += escaped;
|
|
1665
|
+
tokenStarted = true;
|
|
1666
|
+
index += 1;
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
if (character === "`") throw operatorError("`\u2026`");
|
|
1670
|
+
if (character === "$" && input[index + 1] === "(") {
|
|
1671
|
+
throw operatorError("$(\u2026)");
|
|
1672
|
+
}
|
|
1673
|
+
if (character === "|" || character === ">" || character === "<" || character === ";" || character === "&") {
|
|
1674
|
+
const doubled = input[index + 1] === character && (character === "|" || character === "&");
|
|
1675
|
+
throw operatorError(doubled ? character.repeat(2) : character);
|
|
1676
|
+
}
|
|
1677
|
+
token += character;
|
|
1678
|
+
tokenStarted = true;
|
|
1679
|
+
}
|
|
1680
|
+
if (quote !== "none") {
|
|
1681
|
+
throw new ShellParseError(
|
|
1682
|
+
`Command has an unterminated ${quote === "single" ? "single" : "double"} quote.`
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
finishToken();
|
|
1686
|
+
return tokens;
|
|
1687
|
+
}
|
|
1688
|
+
function routeShellCommand(input, activeMode) {
|
|
1689
|
+
let args = tokenizeShellInput(input);
|
|
1690
|
+
if (args.length === 0) return null;
|
|
1691
|
+
if (args[0] === "tt") {
|
|
1692
|
+
args = args.slice(1);
|
|
1693
|
+
if (args.length === 0) {
|
|
1694
|
+
throw new ShellParseError("The TT shell is already open; enter a command such as `runs list`.");
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
if (args[0]?.startsWith("/")) {
|
|
1698
|
+
throw new ShellParseError("Slash commands are session commands and cannot be routed to a workflow.");
|
|
1699
|
+
}
|
|
1700
|
+
const override = args[0];
|
|
1701
|
+
if (override === "cloud" || override === "local") {
|
|
1702
|
+
if (args.length === 1) {
|
|
1703
|
+
throw new ShellParseError(`Add a ${override} command, or use \`/mode ${override}\` to switch workflows.`);
|
|
1704
|
+
}
|
|
1705
|
+
return { target: override, args: args.slice(1) };
|
|
1706
|
+
}
|
|
1707
|
+
return { target: activeMode, args };
|
|
1708
|
+
}
|
|
1709
|
+
var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
1710
|
+
"help",
|
|
1711
|
+
"status",
|
|
1712
|
+
"context",
|
|
1713
|
+
"mode",
|
|
1714
|
+
"clear",
|
|
1715
|
+
"cd",
|
|
1716
|
+
"exit"
|
|
1717
|
+
]);
|
|
1718
|
+
function parseSlashCommand(input) {
|
|
1719
|
+
const trimmed = input.trim();
|
|
1720
|
+
if (!trimmed) return null;
|
|
1721
|
+
if (trimmed === "?") return { name: "help", args: [] };
|
|
1722
|
+
if (trimmed.startsWith("? ")) {
|
|
1723
|
+
return {
|
|
1724
|
+
name: "help",
|
|
1725
|
+
args: tokenizeShellInput(trimmed.slice(1))
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
if (!trimmed.startsWith("/")) return null;
|
|
1729
|
+
if (trimmed === "/") return { name: "palette", args: [] };
|
|
1730
|
+
const tokens = tokenizeShellInput(trimmed);
|
|
1731
|
+
const rawName = tokens[0].slice(1).toLowerCase();
|
|
1732
|
+
if (!SLASH_NAMES.has(rawName)) {
|
|
1733
|
+
throw new ShellParseError(`Unknown session command: /${rawName}. Use /help to see available commands.`);
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
name: rawName,
|
|
1737
|
+
args: tokens.slice(1)
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function errorMessage(error) {
|
|
1741
|
+
return error instanceof Error ? error.message : String(error);
|
|
1742
|
+
}
|
|
1743
|
+
function assertNoArgs(command, args) {
|
|
1744
|
+
if (args.length > 0) {
|
|
1745
|
+
throw new ShellParseError(`/${command} does not accept arguments.`);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function expandDirectory(value, cwd, env) {
|
|
1749
|
+
const home = env.HOME ? resolve3(env.HOME) : homedir3();
|
|
1750
|
+
if (value === "~") return home;
|
|
1751
|
+
if (value.startsWith("~/")) return resolve3(home, value.slice(2));
|
|
1752
|
+
return isAbsolute3(value) ? resolve3(value) : resolve3(cwd, value);
|
|
1753
|
+
}
|
|
1754
|
+
function helpText(mode, query, palette = false) {
|
|
1755
|
+
const groups = groupedCatalog(mode, query);
|
|
1756
|
+
const lines = [];
|
|
1757
|
+
lines.push(palette ? `Commands for ${mode} \u2014 type a command or use cloud/local as a one-shot prefix` : `TT ${mode} commands${query ? ` matching ${JSON.stringify(query)}` : ""}`);
|
|
1758
|
+
if (groups.size === 0) {
|
|
1759
|
+
lines.push(" No matching commands.");
|
|
1760
|
+
} else {
|
|
1761
|
+
for (const [group, commands] of groups) {
|
|
1762
|
+
lines.push(`
|
|
1763
|
+
${group}`);
|
|
1764
|
+
for (const command of commands) {
|
|
1765
|
+
lines.push(` ${command.path.padEnd(22)} ${command.description}`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (!query) {
|
|
1770
|
+
lines.push("\nSession");
|
|
1771
|
+
for (const command of SLASH_COMMANDS) {
|
|
1772
|
+
lines.push(` ${command.path.padEnd(22)} ${command.description}`);
|
|
1773
|
+
}
|
|
1774
|
+
lines.push(" ? Alias for /help.");
|
|
1775
|
+
lines.push("\nUse Tab to complete commands. Shell operators and shell escapes are disabled.");
|
|
1776
|
+
}
|
|
1777
|
+
return `${lines.join("\n")}
|
|
1778
|
+
`;
|
|
1779
|
+
}
|
|
1780
|
+
function renderShellBanner(snapshot) {
|
|
1781
|
+
const spec = snapshot.context.spec?.name ?? snapshot.context.spec?.path ?? "no spec";
|
|
1782
|
+
return [
|
|
1783
|
+
chalk2.bold.cyan("Tuned Tensor"),
|
|
1784
|
+
` ${chalk2.bold(snapshot.mode)} \xB7 ${snapshot.context.projectName} \xB7 ${spec}`,
|
|
1785
|
+
chalk2.dim(" /help for commands \xB7 /mode cloud|local to switch workflows"),
|
|
1786
|
+
""
|
|
1787
|
+
].join("\n");
|
|
1788
|
+
}
|
|
1789
|
+
function renderShellPrompt(snapshot) {
|
|
1790
|
+
return `${chalk2.cyan("tt")} ${chalk2.bold(snapshot.mode)} ${chalk2.dim(snapshot.context.projectName)} \u203A `;
|
|
1791
|
+
}
|
|
1792
|
+
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
1793
|
+
constructor(runner, io, env, contextProvider, initialContext) {
|
|
1794
|
+
this.runner = runner;
|
|
1795
|
+
this.io = io;
|
|
1796
|
+
this.env = env;
|
|
1797
|
+
this.contextProvider = contextProvider;
|
|
1798
|
+
this.mode = initialContext.inferredTarget;
|
|
1799
|
+
this.modeSource = initialContext.targetSource;
|
|
1800
|
+
this.cwd = initialContext.cwd;
|
|
1801
|
+
this.context = initialContext;
|
|
1802
|
+
}
|
|
1803
|
+
mode;
|
|
1804
|
+
modeSource;
|
|
1805
|
+
cwd;
|
|
1806
|
+
context;
|
|
1807
|
+
static async create(options) {
|
|
1808
|
+
const env = options.env ?? process.env;
|
|
1809
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
1810
|
+
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
1811
|
+
const initialContext = await contextProvider({ cwd, env });
|
|
1812
|
+
return new _TunedTensorShellSession(
|
|
1813
|
+
options.runner,
|
|
1814
|
+
options.io,
|
|
1815
|
+
env,
|
|
1816
|
+
contextProvider,
|
|
1817
|
+
initialContext
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
snapshot() {
|
|
1821
|
+
return {
|
|
1822
|
+
mode: this.mode,
|
|
1823
|
+
modeSource: this.modeSource,
|
|
1824
|
+
cwd: this.cwd,
|
|
1825
|
+
context: this.context
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
prompt() {
|
|
1829
|
+
return renderShellPrompt(this.snapshot());
|
|
1830
|
+
}
|
|
1831
|
+
banner() {
|
|
1832
|
+
return renderShellBanner(this.snapshot());
|
|
1833
|
+
}
|
|
1834
|
+
async refreshContext() {
|
|
1835
|
+
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
1836
|
+
this.cwd = this.context.cwd;
|
|
1837
|
+
if (this.modeSource !== "session" && this.modeSource !== "environment") {
|
|
1838
|
+
this.mode = this.context.inferredTarget;
|
|
1839
|
+
this.modeSource = this.context.targetSource;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
writeLines(lines) {
|
|
1843
|
+
this.io.write(`${lines.join("\n")}
|
|
1844
|
+
`);
|
|
1845
|
+
}
|
|
1846
|
+
async handleSlash(command) {
|
|
1847
|
+
switch (command.name) {
|
|
1848
|
+
case "palette":
|
|
1849
|
+
this.io.write(helpText(this.mode, void 0, true));
|
|
1850
|
+
return "continue";
|
|
1851
|
+
case "help":
|
|
1852
|
+
this.io.write(helpText(this.mode, command.args.join(" ") || void 0));
|
|
1853
|
+
return "continue";
|
|
1854
|
+
case "status":
|
|
1855
|
+
assertNoArgs("status", command.args);
|
|
1856
|
+
await this.refreshContext();
|
|
1857
|
+
this.writeLines(formatShellStatus(this.context, this.mode));
|
|
1858
|
+
return "continue";
|
|
1859
|
+
case "context":
|
|
1860
|
+
assertNoArgs("context", command.args);
|
|
1861
|
+
await this.refreshContext();
|
|
1862
|
+
this.writeLines(formatShellContext(this.context, this.mode, this.modeSource));
|
|
1863
|
+
return "continue";
|
|
1864
|
+
case "mode": {
|
|
1865
|
+
if (command.args.length === 0) {
|
|
1866
|
+
this.io.write(`Current workflow: ${this.mode}
|
|
1867
|
+
`);
|
|
1868
|
+
return "continue";
|
|
1869
|
+
}
|
|
1870
|
+
if (command.args.length !== 1 || !["cloud", "local"].includes(command.args[0])) {
|
|
1871
|
+
throw new ShellParseError("Usage: /mode cloud|local");
|
|
1872
|
+
}
|
|
1873
|
+
this.mode = command.args[0];
|
|
1874
|
+
this.modeSource = "session";
|
|
1875
|
+
this.io.write(`Workflow switched to ${this.mode}.
|
|
1876
|
+
`);
|
|
1877
|
+
return "continue";
|
|
1878
|
+
}
|
|
1879
|
+
case "clear":
|
|
1880
|
+
assertNoArgs("clear", command.args);
|
|
1881
|
+
this.io.clear();
|
|
1882
|
+
return "continue";
|
|
1883
|
+
case "cd": {
|
|
1884
|
+
if (command.args.length === 0) {
|
|
1885
|
+
this.io.write(`${this.cwd}
|
|
1886
|
+
`);
|
|
1887
|
+
return "continue";
|
|
1888
|
+
}
|
|
1889
|
+
if (command.args.length !== 1) {
|
|
1890
|
+
throw new ShellParseError("Usage: /cd <directory>; quote paths containing spaces.");
|
|
1891
|
+
}
|
|
1892
|
+
const nextDirectory = expandDirectory(command.args[0], this.cwd, this.env);
|
|
1893
|
+
const metadata = await stat3(nextDirectory).catch(() => null);
|
|
1894
|
+
if (!metadata?.isDirectory()) {
|
|
1895
|
+
throw new ShellParseError(`Not a directory: ${nextDirectory}`);
|
|
1896
|
+
}
|
|
1897
|
+
this.cwd = nextDirectory;
|
|
1898
|
+
await this.refreshContext();
|
|
1899
|
+
this.io.write(`Directory: ${this.cwd}
|
|
1900
|
+
`);
|
|
1901
|
+
return "continue";
|
|
1902
|
+
}
|
|
1903
|
+
case "exit":
|
|
1904
|
+
assertNoArgs("exit", command.args);
|
|
1905
|
+
return "exit";
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
async handleLine(input) {
|
|
1909
|
+
try {
|
|
1910
|
+
const slash = parseSlashCommand(input);
|
|
1911
|
+
if (slash) return await this.handleSlash(slash);
|
|
1912
|
+
const routed = routeShellCommand(input, this.mode);
|
|
1913
|
+
if (!routed) return "continue";
|
|
1914
|
+
await this.runner({
|
|
1915
|
+
target: routed.target,
|
|
1916
|
+
args: [...routed.args],
|
|
1917
|
+
cwd: this.cwd
|
|
1918
|
+
});
|
|
1919
|
+
await this.refreshContext();
|
|
1920
|
+
return "continue";
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
this.io.writeError(`${chalk2.red("Error:")} ${errorMessage(error)}
|
|
1923
|
+
`);
|
|
1924
|
+
return "continue";
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
async function createShellSession(options) {
|
|
1929
|
+
return TunedTensorShellSession.create(options);
|
|
1930
|
+
}
|
|
1931
|
+
function streamIsTTY(stream) {
|
|
1932
|
+
return Boolean(stream.isTTY);
|
|
171
1933
|
}
|
|
172
|
-
function
|
|
173
|
-
|
|
1934
|
+
async function withForegroundSignalHandoff(control, run) {
|
|
1935
|
+
const signals = control.signals ?? process;
|
|
1936
|
+
const restoreRawMode = control.input.isRaw === true && typeof control.input.setRawMode === "function";
|
|
1937
|
+
const keepParentAlive = () => {
|
|
1938
|
+
};
|
|
1939
|
+
control.pauseReadline();
|
|
1940
|
+
if (restoreRawMode) control.input.setRawMode(false);
|
|
1941
|
+
signals.on("SIGINT", keepParentAlive);
|
|
1942
|
+
try {
|
|
1943
|
+
return await run();
|
|
1944
|
+
} finally {
|
|
1945
|
+
signals.off("SIGINT", keepParentAlive);
|
|
1946
|
+
if (restoreRawMode) control.input.setRawMode(true);
|
|
1947
|
+
control.resumeReadline();
|
|
1948
|
+
}
|
|
174
1949
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
1950
|
+
function streamIO(output, error) {
|
|
1951
|
+
return {
|
|
1952
|
+
write(text2) {
|
|
1953
|
+
output.write(text2);
|
|
1954
|
+
},
|
|
1955
|
+
writeError(text2) {
|
|
1956
|
+
error.write(text2);
|
|
1957
|
+
},
|
|
1958
|
+
clear() {
|
|
1959
|
+
output.write("\x1B[2J\x1B[H");
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
177
1962
|
}
|
|
178
|
-
function
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
1963
|
+
async function startInteractiveShell(options) {
|
|
1964
|
+
const input = options.input ?? process.stdin;
|
|
1965
|
+
const output = options.output ?? process.stdout;
|
|
1966
|
+
const error = options.error ?? process.stderr;
|
|
1967
|
+
if (options.requireTTY !== false && (!streamIsTTY(input) || !streamIsTTY(output))) {
|
|
1968
|
+
throw new Error("The interactive TT shell requires a TTY.");
|
|
1969
|
+
}
|
|
1970
|
+
let readline;
|
|
1971
|
+
const terminalInput = input;
|
|
1972
|
+
const foregroundRunner = (request2) => withForegroundSignalHandoff(
|
|
1973
|
+
{
|
|
1974
|
+
input: terminalInput,
|
|
1975
|
+
pauseReadline: () => readline?.pause(),
|
|
1976
|
+
resumeReadline: () => readline?.resume()
|
|
1977
|
+
},
|
|
1978
|
+
() => options.runner(request2)
|
|
1979
|
+
);
|
|
1980
|
+
const session = await createShellSession({
|
|
1981
|
+
runner: foregroundRunner,
|
|
1982
|
+
io: streamIO(output, error),
|
|
1983
|
+
cwd: options.cwd,
|
|
1984
|
+
env: options.env
|
|
1985
|
+
});
|
|
1986
|
+
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
1987
|
+
readline = createInterface({
|
|
1988
|
+
input,
|
|
1989
|
+
output,
|
|
1990
|
+
terminal: true,
|
|
1991
|
+
historySize: 100,
|
|
1992
|
+
removeHistoryDuplicates: true,
|
|
1993
|
+
completer
|
|
1994
|
+
});
|
|
1995
|
+
readline.on("SIGINT", () => {
|
|
1996
|
+
output.write("\n");
|
|
1997
|
+
readline?.setPrompt(session.prompt());
|
|
1998
|
+
readline?.write(null, { ctrl: true, name: "u" });
|
|
1999
|
+
});
|
|
2000
|
+
output.write(session.banner());
|
|
2001
|
+
readline.setPrompt(session.prompt());
|
|
2002
|
+
readline.prompt();
|
|
2003
|
+
try {
|
|
2004
|
+
for await (const line of readline) {
|
|
2005
|
+
const action = await session.handleLine(line);
|
|
2006
|
+
if (action === "exit") {
|
|
2007
|
+
readline.close();
|
|
2008
|
+
break;
|
|
187
2009
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
2010
|
+
readline.setPrompt(session.prompt());
|
|
2011
|
+
readline.prompt();
|
|
2012
|
+
}
|
|
2013
|
+
} finally {
|
|
2014
|
+
readline.close();
|
|
193
2015
|
}
|
|
194
2016
|
}
|
|
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";
|
|
2017
|
+
function ciEnabled(value) {
|
|
2018
|
+
if (!value) return false;
|
|
2019
|
+
return !["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
219
2020
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
2021
|
+
function shouldStartInteractiveShell(options) {
|
|
2022
|
+
const env = options.env ?? process.env;
|
|
2023
|
+
return options.args.length === 0 && options.stdinIsTTY === true && options.stdoutIsTTY === true && !ciEnabled(env.CI) && env.TERM !== "dumb";
|
|
222
2024
|
}
|
|
223
2025
|
|
|
224
2026
|
// src/commands/auth.ts
|
|
225
|
-
import { createInterface } from "readline/promises";
|
|
2027
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
226
2028
|
import { stdin, stdout } from "process";
|
|
227
|
-
import
|
|
2029
|
+
import { Writable } from "stream";
|
|
2030
|
+
import chalk3 from "chalk";
|
|
2031
|
+
async function promptForApiKey(input = stdin, output = stdout) {
|
|
2032
|
+
if (input.isTTY !== true || output.isTTY !== true) {
|
|
2033
|
+
throw new Error(
|
|
2034
|
+
"An API key is required in non-interactive mode. Pass it to `tt auth login <key>` or set TUNED_TENSOR_API_KEY."
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
let muted = false;
|
|
2038
|
+
const maskedOutput = new Writable({
|
|
2039
|
+
write(chunk, _encoding, callback) {
|
|
2040
|
+
if (!muted) output.write(chunk);
|
|
2041
|
+
callback();
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
const rl = createInterface2({
|
|
2045
|
+
input,
|
|
2046
|
+
output: maskedOutput,
|
|
2047
|
+
terminal: true
|
|
2048
|
+
});
|
|
2049
|
+
try {
|
|
2050
|
+
const pending = rl.question("Enter your API key (tt_...): ");
|
|
2051
|
+
muted = true;
|
|
2052
|
+
return await pending;
|
|
2053
|
+
} finally {
|
|
2054
|
+
muted = false;
|
|
2055
|
+
output.write("\n");
|
|
2056
|
+
rl.close();
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
228
2059
|
function registerAuthCommands(parent) {
|
|
229
2060
|
const auth = parent.command("auth").description("Manage authentication");
|
|
230
2061
|
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
2062
|
let apiKey = key;
|
|
232
2063
|
if (!apiKey) {
|
|
2064
|
+
if (isJsonMode()) {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
"An API key argument is required in JSON mode. Use `tt --json auth login <key>`."
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
233
2069
|
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
234
2070
|
console.log();
|
|
235
2071
|
console.log(
|
|
236
|
-
` To get your API key, go to ${
|
|
2072
|
+
` To get your API key, go to ${chalk3.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
237
2073
|
);
|
|
238
|
-
console.log(` ${
|
|
2074
|
+
console.log(` ${chalk3.cyan.underline(settingsUrl)}`);
|
|
239
2075
|
console.log();
|
|
240
|
-
|
|
241
|
-
apiKey = await rl.question("Enter your API key (tt_...): ");
|
|
242
|
-
rl.close();
|
|
2076
|
+
apiKey = await promptForApiKey();
|
|
243
2077
|
}
|
|
244
2078
|
apiKey = apiKey.trim();
|
|
245
2079
|
if (!apiKey.startsWith("tt_") || apiKey.length !== 51) {
|
|
246
|
-
|
|
2080
|
+
throw new Error(
|
|
247
2081
|
"Invalid API key format. Keys start with tt_ and are 51 characters long."
|
|
248
2082
|
);
|
|
249
|
-
process.exit(1);
|
|
250
2083
|
}
|
|
251
2084
|
updateConfig({ api_key: apiKey });
|
|
2085
|
+
if (isJsonMode()) {
|
|
2086
|
+
printJson({
|
|
2087
|
+
authenticated: true,
|
|
2088
|
+
key_prefix: `${apiKey.slice(0, 8)}...`,
|
|
2089
|
+
base_url: getBaseUrl(parent.opts())
|
|
2090
|
+
});
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
252
2093
|
printSuccess(
|
|
253
2094
|
`API key stored (${apiKey.slice(0, 8)}...). You're ready to go.`
|
|
254
2095
|
);
|
|
255
2096
|
});
|
|
256
2097
|
auth.command("logout").description("Remove stored credentials").action(() => {
|
|
257
2098
|
clearConfig();
|
|
2099
|
+
if (isJsonMode()) {
|
|
2100
|
+
printJson({ authenticated: false });
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
258
2103
|
printSuccess("Credentials removed.");
|
|
259
2104
|
});
|
|
260
2105
|
auth.command("status").description("Show current authentication status").action(() => {
|
|
@@ -382,87 +2227,7 @@ function resolveLabelingJobId(prefix, opts) {
|
|
|
382
2227
|
);
|
|
383
2228
|
}
|
|
384
2229
|
|
|
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
2230
|
// 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
2231
|
var RUN_INPUT_KEYS = ["run_id", "behavior_spec_id", "spec_snapshot", "run_number"];
|
|
467
2232
|
var SpecBodyError = class extends Error {
|
|
468
2233
|
};
|
|
@@ -493,15 +2258,13 @@ function loadSpecBody(filePath, mode) {
|
|
|
493
2258
|
`${filePath} is missing required field "name" (string).`
|
|
494
2259
|
);
|
|
495
2260
|
}
|
|
496
|
-
const unknown =
|
|
497
|
-
(k) => !SPEC_BODY_KEYS.has(k) && k !== "id" && k !== "eval_cases"
|
|
498
|
-
);
|
|
2261
|
+
const unknown = unknownProjectSpecKeys(body);
|
|
499
2262
|
if (unknown.length && !isJsonMode()) {
|
|
500
2263
|
printWarning(
|
|
501
|
-
`Unknown spec field(s) in ${filePath}: ${unknown.join(", ")}. They will be
|
|
2264
|
+
`Unknown spec field(s) in ${filePath}: ${unknown.join(", ")}. They will be ignored by the cloud API.`
|
|
502
2265
|
);
|
|
503
2266
|
}
|
|
504
|
-
return
|
|
2267
|
+
return projectCloudSpec(body).body;
|
|
505
2268
|
}
|
|
506
2269
|
function registerSpecsCommands(parent) {
|
|
507
2270
|
const specs = parent.command("specs").description("Manage behaviour specs");
|
|
@@ -951,19 +2714,23 @@ async function buildRunRequestBody(cmdOpts, opts) {
|
|
|
951
2714
|
}
|
|
952
2715
|
function registerRunsCommands(parent) {
|
|
953
2716
|
const runs = parent.command("runs").description("Manage runs");
|
|
954
|
-
runs.command("list").description("List runs").option("-s, --spec <id>", "Filter by spec ID (full UUID or 8+ char prefix)").option("-p, --page <n>", "Page number", "1").option("--per-page <n>", "Results per page", "20").action(async (cmdOpts) => {
|
|
2717
|
+
runs.command("list").description("List runs").option("-s, --spec <id>", "Filter by spec ID (full UUID or 8+ char prefix)").option("-p, --page <n>", "Page number", "1").option("--per-page <n>", "Results per page", "20").option("--summary", "Request compact run summaries without detailed eval payloads or events").action(async (cmdOpts) => {
|
|
955
2718
|
const opts = parent.opts();
|
|
956
2719
|
let path = "/runs";
|
|
957
2720
|
const query = {
|
|
958
2721
|
page: cmdOpts.page,
|
|
959
2722
|
per_page: cmdOpts.perPage
|
|
960
2723
|
};
|
|
2724
|
+
if (cmdOpts.summary) query.view = "summary";
|
|
961
2725
|
if (cmdOpts.spec) {
|
|
962
2726
|
const fullSpecId = await resolveSpecId(cmdOpts.spec, opts);
|
|
963
2727
|
path = `/behavior-specs/${fullSpecId}/runs`;
|
|
964
2728
|
}
|
|
965
|
-
const
|
|
966
|
-
|
|
2729
|
+
const response = await get(path, query, opts);
|
|
2730
|
+
const { data, meta } = response;
|
|
2731
|
+
if (isJsonMode()) {
|
|
2732
|
+
return printJson(cmdOpts.summary ? response : { data, meta });
|
|
2733
|
+
}
|
|
967
2734
|
printTable(
|
|
968
2735
|
["ID", "Spec", "#", "Status", "Score", "Started", "Completed"],
|
|
969
2736
|
data.map((r) => [
|
|
@@ -1191,18 +2958,18 @@ function findControlChar(value) {
|
|
|
1191
2958
|
function formatCodepoint(code) {
|
|
1192
2959
|
return `U+${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
1193
2960
|
}
|
|
1194
|
-
function detectRowFormat(
|
|
1195
|
-
const input =
|
|
1196
|
-
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof
|
|
2961
|
+
function detectRowFormat(record2) {
|
|
2962
|
+
const input = record2.input;
|
|
2963
|
+
if (input && typeof input === "object" && typeof input.prompt === "string" && Array.isArray(input.assets) && input.assets.length > 0 && typeof record2.output === "string") {
|
|
1197
2964
|
return "document_ocr_jsonl";
|
|
1198
2965
|
}
|
|
1199
|
-
if (typeof
|
|
2966
|
+
if (typeof record2.input === "string" || typeof record2.output === "string" || !("input" in record2) || !("output" in record2)) {
|
|
1200
2967
|
return "jsonl";
|
|
1201
2968
|
}
|
|
1202
2969
|
return null;
|
|
1203
2970
|
}
|
|
1204
|
-
function validateDocumentOcrRow(
|
|
1205
|
-
const input =
|
|
2971
|
+
function validateDocumentOcrRow(record2, rowNumber, errors) {
|
|
2972
|
+
const input = record2.input;
|
|
1206
2973
|
const assets = input.assets;
|
|
1207
2974
|
if (typeof input.prompt !== "string" || input.prompt.trim().length === 0) {
|
|
1208
2975
|
errors.push(`Row ${rowNumber}: OCR input.prompt must be a non-empty string`);
|
|
@@ -1214,10 +2981,10 @@ function validateDocumentOcrRow(record, rowNumber, errors) {
|
|
|
1214
2981
|
);
|
|
1215
2982
|
}
|
|
1216
2983
|
}
|
|
1217
|
-
if (typeof
|
|
2984
|
+
if (typeof record2.output !== "string" || record2.output.trim().length === 0) {
|
|
1218
2985
|
errors.push(`Row ${rowNumber}: OCR output must be a non-empty string`);
|
|
1219
2986
|
} else {
|
|
1220
|
-
const hit = findControlChar(
|
|
2987
|
+
const hit = findControlChar(record2.output);
|
|
1221
2988
|
if (hit) {
|
|
1222
2989
|
errors.push(
|
|
1223
2990
|
`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`
|
|
@@ -1286,14 +3053,14 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
1286
3053
|
errors.push(`Row ${rowNumber}: expected an object with "input" and "output" fields`);
|
|
1287
3054
|
continue;
|
|
1288
3055
|
}
|
|
1289
|
-
const
|
|
1290
|
-
if ("messages" in
|
|
3056
|
+
const record2 = row;
|
|
3057
|
+
if ("messages" in record2 && !("input" in record2) && !("output" in record2)) {
|
|
1291
3058
|
errors.push(
|
|
1292
3059
|
`Row ${rowNumber}: found OpenAI SFT-style "messages"; Tuned Tensor datasets require flat "input" and "output" strings`
|
|
1293
3060
|
);
|
|
1294
3061
|
continue;
|
|
1295
3062
|
}
|
|
1296
|
-
const rowFormat = detectRowFormat(
|
|
3063
|
+
const rowFormat = detectRowFormat(record2);
|
|
1297
3064
|
if (!rowFormat) {
|
|
1298
3065
|
errors.push(
|
|
1299
3066
|
`Row ${rowNumber}: expected text {"input": string, "output": string} or OCR {"input":{"prompt": string, "assets": [...]}, "output": string}`
|
|
@@ -1310,17 +3077,17 @@ function validateDatasetFile(file, requestedFormat) {
|
|
|
1310
3077
|
continue;
|
|
1311
3078
|
}
|
|
1312
3079
|
if (rowFormat === "document_ocr_jsonl") {
|
|
1313
|
-
validateDocumentOcrRow(
|
|
3080
|
+
validateDocumentOcrRow(record2, rowNumber, errors);
|
|
1314
3081
|
continue;
|
|
1315
3082
|
}
|
|
1316
|
-
if (typeof
|
|
3083
|
+
if (typeof record2.input !== "string") {
|
|
1317
3084
|
errors.push(`Row ${rowNumber}: missing string "input" field`);
|
|
1318
3085
|
}
|
|
1319
|
-
if (typeof
|
|
3086
|
+
if (typeof record2.output !== "string") {
|
|
1320
3087
|
errors.push(`Row ${rowNumber}: missing string "output" field`);
|
|
1321
3088
|
}
|
|
1322
3089
|
for (const field of ["input", "output"]) {
|
|
1323
|
-
const value =
|
|
3090
|
+
const value = record2[field];
|
|
1324
3091
|
if (typeof value !== "string") continue;
|
|
1325
3092
|
const hit = findControlChar(value);
|
|
1326
3093
|
if (hit) {
|
|
@@ -1448,7 +3215,7 @@ function formatCents2(cents) {
|
|
|
1448
3215
|
return `$${(cents / 100).toFixed(2)}`;
|
|
1449
3216
|
}
|
|
1450
3217
|
function sleep(ms) {
|
|
1451
|
-
return new Promise((
|
|
3218
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1452
3219
|
}
|
|
1453
3220
|
function parseCsvHeader(line) {
|
|
1454
3221
|
const fields = [];
|
|
@@ -1841,7 +3608,7 @@ function registerLabelCommands(parent) {
|
|
|
1841
3608
|
}
|
|
1842
3609
|
|
|
1843
3610
|
// src/commands/models.ts
|
|
1844
|
-
import { spawn as
|
|
3611
|
+
import { spawn as spawn3, execFileSync } from "child_process";
|
|
1845
3612
|
import { createHash } from "crypto";
|
|
1846
3613
|
import {
|
|
1847
3614
|
existsSync as existsSync5,
|
|
@@ -1854,14 +3621,14 @@ import {
|
|
|
1854
3621
|
readdirSync,
|
|
1855
3622
|
rmSync
|
|
1856
3623
|
} from "fs";
|
|
1857
|
-
import { homedir as
|
|
1858
|
-
import { dirname as
|
|
3624
|
+
import { homedir as homedir4 } from "os";
|
|
3625
|
+
import { dirname as dirname4, join as join4, resolve as resolve5, basename as basename4, normalize, isAbsolute as isAbsolute4 } from "path";
|
|
1859
3626
|
import { Readable, Transform } from "stream";
|
|
1860
3627
|
import { pipeline } from "stream/promises";
|
|
1861
3628
|
|
|
1862
3629
|
// src/commands/init.ts
|
|
1863
3630
|
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
1864
|
-
import { resolve } from "path";
|
|
3631
|
+
import { resolve as resolve4 } from "path";
|
|
1865
3632
|
var DEFAULT_SPEC_FILE = "tunedtensor.json";
|
|
1866
3633
|
var SCAFFOLD = {
|
|
1867
3634
|
name: "My Agent",
|
|
@@ -1871,30 +3638,45 @@ var SCAFFOLD = {
|
|
|
1871
3638
|
guidelines: [],
|
|
1872
3639
|
constraints: [],
|
|
1873
3640
|
examples: [
|
|
1874
|
-
{ input: "Hello", output: "Hi! How can I help you today?" }
|
|
1875
|
-
|
|
1876
|
-
|
|
3641
|
+
{ input: "Hello", output: "Hi! How can I help you today?" },
|
|
3642
|
+
{
|
|
3643
|
+
input: "Summarize this update: The launch moved to Friday.",
|
|
3644
|
+
output: "The launch is now scheduled for Friday."
|
|
3645
|
+
}
|
|
3646
|
+
]
|
|
1877
3647
|
};
|
|
1878
3648
|
function registerInitCommand(parent) {
|
|
1879
|
-
parent.command("init").description("Create a
|
|
1880
|
-
const filePath =
|
|
3649
|
+
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) => {
|
|
3650
|
+
const filePath = resolve4(cmdOpts.file);
|
|
1881
3651
|
if (existsSync4(filePath)) {
|
|
3652
|
+
if (isJsonMode()) {
|
|
3653
|
+
printJson({ created: false, path: filePath });
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
1882
3656
|
printWarning(`${cmdOpts.file} already exists. Use tt eval to validate it or edit it directly.`);
|
|
1883
3657
|
return;
|
|
1884
3658
|
}
|
|
1885
|
-
const spec = {
|
|
3659
|
+
const spec = {
|
|
3660
|
+
...SCAFFOLD,
|
|
3661
|
+
examples: SCAFFOLD.examples.map((example) => ({ ...example }))
|
|
3662
|
+
};
|
|
1886
3663
|
if (cmdOpts.name) spec.name = cmdOpts.name;
|
|
1887
3664
|
if (cmdOpts.model) spec.base_model = canonicalizeBaseModel(cmdOpts.model);
|
|
1888
3665
|
writeFileSync2(filePath, JSON.stringify(spec, null, 2) + "\n");
|
|
3666
|
+
if (isJsonMode()) {
|
|
3667
|
+
printJson({ created: true, path: filePath, spec });
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
1889
3670
|
printSuccess(`Created ${cmdOpts.file}`);
|
|
1890
3671
|
console.log("\nNext steps:");
|
|
1891
3672
|
console.log(" 1. Edit the spec: system_prompt, guidelines, examples");
|
|
1892
|
-
console.log(" 2. Validate
|
|
1893
|
-
console.log(" 3. Push to
|
|
3673
|
+
console.log(" 2. Validate for cloud: tt eval");
|
|
3674
|
+
console.log(" 3. Push to cloud: tt push");
|
|
3675
|
+
console.log(" Or validate locally: tt local validate");
|
|
1894
3676
|
});
|
|
1895
3677
|
}
|
|
1896
3678
|
function loadSpec(filePath) {
|
|
1897
|
-
const resolved =
|
|
3679
|
+
const resolved = resolve4(filePath || DEFAULT_SPEC_FILE);
|
|
1898
3680
|
if (!existsSync4(resolved)) {
|
|
1899
3681
|
printError(
|
|
1900
3682
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE}
|
|
@@ -1907,30 +3689,30 @@ Run \`tt init\` to create one.`
|
|
|
1907
3689
|
}
|
|
1908
3690
|
|
|
1909
3691
|
// src/serve-manager.ts
|
|
1910
|
-
import { spawn } from "child_process";
|
|
1911
|
-
import { randomUUID } from "crypto";
|
|
3692
|
+
import { spawn as spawn2 } from "child_process";
|
|
3693
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1912
3694
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1913
3695
|
import { createServer } from "http";
|
|
1914
3696
|
import { createServer as createNetServer } from "net";
|
|
1915
|
-
import { dirname } from "path";
|
|
3697
|
+
import { dirname as dirname3 } from "path";
|
|
1916
3698
|
var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
|
|
1917
3699
|
function parsePositiveInteger(value, fallback) {
|
|
1918
3700
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
1919
3701
|
}
|
|
1920
3702
|
function sleep2(ms) {
|
|
1921
|
-
return new Promise((
|
|
3703
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1922
3704
|
}
|
|
1923
3705
|
function readRequestBody(req) {
|
|
1924
|
-
return new Promise((
|
|
3706
|
+
return new Promise((resolve7, reject) => {
|
|
1925
3707
|
const chunks = [];
|
|
1926
3708
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
1927
|
-
req.on("end", () =>
|
|
3709
|
+
req.on("end", () => resolve7(Buffer.concat(chunks)));
|
|
1928
3710
|
req.on("error", reject);
|
|
1929
3711
|
});
|
|
1930
3712
|
}
|
|
1931
|
-
function sendJson(res,
|
|
3713
|
+
function sendJson(res, status2, payload) {
|
|
1932
3714
|
const body = Buffer.from(JSON.stringify(payload), "utf8");
|
|
1933
|
-
res.writeHead(
|
|
3715
|
+
res.writeHead(status2, {
|
|
1934
3716
|
"content-type": "application/json",
|
|
1935
3717
|
"content-length": String(body.byteLength),
|
|
1936
3718
|
"access-control-allow-origin": "*",
|
|
@@ -1992,7 +3774,7 @@ function errorSummary(response, fallback) {
|
|
|
1992
3774
|
return typeof message === "string" ? message : fallback;
|
|
1993
3775
|
}
|
|
1994
3776
|
async function allocatePort(host) {
|
|
1995
|
-
return new Promise((
|
|
3777
|
+
return new Promise((resolve7, reject) => {
|
|
1996
3778
|
const server = createNetServer();
|
|
1997
3779
|
server.on("error", reject);
|
|
1998
3780
|
server.listen(0, host, () => {
|
|
@@ -2002,14 +3784,14 @@ async function allocatePort(host) {
|
|
|
2002
3784
|
return;
|
|
2003
3785
|
}
|
|
2004
3786
|
const port = address.port;
|
|
2005
|
-
server.close(() =>
|
|
3787
|
+
server.close(() => resolve7(port));
|
|
2006
3788
|
});
|
|
2007
3789
|
});
|
|
2008
3790
|
}
|
|
2009
3791
|
var ManagedModelServer = class {
|
|
2010
3792
|
constructor(options) {
|
|
2011
3793
|
this.options = options;
|
|
2012
|
-
this.spawnModelServer = options.spawnModelServer ??
|
|
3794
|
+
this.spawnModelServer = options.spawnModelServer ?? spawn2;
|
|
2013
3795
|
this.idleTimeoutMs = parsePositiveInteger(options.idleTimeoutSeconds, 300) * 1e3;
|
|
2014
3796
|
this.restartAfterRequests = parsePositiveInteger(options.restartAfterRequests, 100);
|
|
2015
3797
|
this.logRecordOverride = options.logRecord;
|
|
@@ -2042,11 +3824,11 @@ var ManagedModelServer = class {
|
|
|
2042
3824
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
2043
3825
|
});
|
|
2044
3826
|
});
|
|
2045
|
-
await new Promise((
|
|
3827
|
+
await new Promise((resolve7, reject) => {
|
|
2046
3828
|
this.server?.once("error", reject);
|
|
2047
3829
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
2048
3830
|
this.server?.off("error", reject);
|
|
2049
|
-
|
|
3831
|
+
resolve7();
|
|
2050
3832
|
});
|
|
2051
3833
|
});
|
|
2052
3834
|
}
|
|
@@ -2056,15 +3838,15 @@ var ManagedModelServer = class {
|
|
|
2056
3838
|
if (!this.server) return;
|
|
2057
3839
|
const server = this.server;
|
|
2058
3840
|
this.server = null;
|
|
2059
|
-
await new Promise((
|
|
2060
|
-
server.close((err) => err ? reject(err) :
|
|
3841
|
+
await new Promise((resolve7, reject) => {
|
|
3842
|
+
server.close((err) => err ? reject(err) : resolve7());
|
|
2061
3843
|
});
|
|
2062
3844
|
}
|
|
2063
3845
|
async run() {
|
|
2064
3846
|
await this.start();
|
|
2065
|
-
await new Promise((
|
|
3847
|
+
await new Promise((resolve7, reject) => {
|
|
2066
3848
|
this.server?.once("error", reject);
|
|
2067
|
-
this.server?.once("close",
|
|
3849
|
+
this.server?.once("close", resolve7);
|
|
2068
3850
|
});
|
|
2069
3851
|
}
|
|
2070
3852
|
async handle(req, res) {
|
|
@@ -2132,20 +3914,20 @@ var ManagedModelServer = class {
|
|
|
2132
3914
|
});
|
|
2133
3915
|
}
|
|
2134
3916
|
async processGeneration(route, body, res, receivedAt) {
|
|
2135
|
-
const requestId = `ttreq-${
|
|
3917
|
+
const requestId = `ttreq-${randomUUID2()}`;
|
|
2136
3918
|
const startedAt = Date.now();
|
|
2137
3919
|
const queuedMs = startedAt - receivedAt;
|
|
2138
3920
|
this.activeRequests += 1;
|
|
2139
3921
|
this.clearIdleTimer();
|
|
2140
|
-
let
|
|
3922
|
+
let status2 = 500;
|
|
2141
3923
|
let parsed = null;
|
|
2142
3924
|
let failure = null;
|
|
2143
3925
|
try {
|
|
2144
3926
|
await this.ensureModelReady();
|
|
2145
3927
|
const forwarded = await this.forwardGeneration(route, body);
|
|
2146
|
-
|
|
3928
|
+
status2 = forwarded.status;
|
|
2147
3929
|
parsed = forwarded.parsed;
|
|
2148
|
-
res.writeHead(
|
|
3930
|
+
res.writeHead(status2, {
|
|
2149
3931
|
...copyResponseHeaders(forwarded.headers),
|
|
2150
3932
|
"content-length": String(Buffer.byteLength(forwarded.body))
|
|
2151
3933
|
});
|
|
@@ -2154,8 +3936,8 @@ var ManagedModelServer = class {
|
|
|
2154
3936
|
failure = errorSummary(parsed, null);
|
|
2155
3937
|
} catch (err) {
|
|
2156
3938
|
failure = err instanceof Error ? err.message : String(err);
|
|
2157
|
-
|
|
2158
|
-
sendJson(res,
|
|
3939
|
+
status2 = 502;
|
|
3940
|
+
sendJson(res, status2, { error: { message: failure } });
|
|
2159
3941
|
} finally {
|
|
2160
3942
|
const finishedAt = Date.now();
|
|
2161
3943
|
this.activeRequests -= 1;
|
|
@@ -2168,13 +3950,13 @@ var ManagedModelServer = class {
|
|
|
2168
3950
|
path: this.options.modelPath
|
|
2169
3951
|
},
|
|
2170
3952
|
request_bytes: body.byteLength,
|
|
2171
|
-
response_status:
|
|
3953
|
+
response_status: status2,
|
|
2172
3954
|
latency_ms: finishedAt - receivedAt,
|
|
2173
3955
|
queued_ms: queuedMs,
|
|
2174
3956
|
prompt_tokens: usageNumber(parsed, "prompt_tokens"),
|
|
2175
3957
|
completion_tokens: usageNumber(parsed, "completion_tokens"),
|
|
2176
3958
|
total_tokens: usageNumber(parsed, "total_tokens"),
|
|
2177
|
-
schema_validity:
|
|
3959
|
+
schema_validity: status2 < 400 ? true : status2 === 422 ? false : null,
|
|
2178
3960
|
gate_field: this.options.gateField,
|
|
2179
3961
|
gate_result: this.extractGateResult(parsed),
|
|
2180
3962
|
restart_count: this.restartCount,
|
|
@@ -2238,7 +4020,7 @@ var ManagedModelServer = class {
|
|
|
2238
4020
|
try {
|
|
2239
4021
|
child.kill("SIGTERM");
|
|
2240
4022
|
await Promise.race([
|
|
2241
|
-
new Promise((
|
|
4023
|
+
new Promise((resolve7) => child.once("exit", () => resolve7())),
|
|
2242
4024
|
sleep2(5e3).then(() => {
|
|
2243
4025
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
2244
4026
|
})
|
|
@@ -2255,8 +4037,8 @@ var ManagedModelServer = class {
|
|
|
2255
4037
|
const response = await fetch(`http://127.0.0.1:${this.internalPort}/health`, {
|
|
2256
4038
|
signal: controller.signal
|
|
2257
4039
|
});
|
|
2258
|
-
const
|
|
2259
|
-
return { ok: response.ok, body: tryJsonParse(
|
|
4040
|
+
const text2 = await response.text();
|
|
4041
|
+
return { ok: response.ok, body: tryJsonParse(text2) };
|
|
2260
4042
|
} catch {
|
|
2261
4043
|
return { ok: false, body: null };
|
|
2262
4044
|
} finally {
|
|
@@ -2280,12 +4062,12 @@ var ManagedModelServer = class {
|
|
|
2280
4062
|
headers: { "content-type": "application/json" },
|
|
2281
4063
|
body
|
|
2282
4064
|
});
|
|
2283
|
-
const
|
|
4065
|
+
const text2 = await response.text();
|
|
2284
4066
|
return {
|
|
2285
4067
|
status: response.status,
|
|
2286
4068
|
headers: response.headers,
|
|
2287
|
-
body:
|
|
2288
|
-
parsed: tryJsonParse(
|
|
4069
|
+
body: text2,
|
|
4070
|
+
parsed: tryJsonParse(text2)
|
|
2289
4071
|
};
|
|
2290
4072
|
}
|
|
2291
4073
|
extractGateResult(response) {
|
|
@@ -2308,15 +4090,15 @@ var ManagedModelServer = class {
|
|
|
2308
4090
|
clearTimeout(this.idleTimer);
|
|
2309
4091
|
this.idleTimer = null;
|
|
2310
4092
|
}
|
|
2311
|
-
writeLog(
|
|
4093
|
+
writeLog(record2) {
|
|
2312
4094
|
if (this.logRecordOverride) {
|
|
2313
|
-
this.logRecordOverride(
|
|
4095
|
+
this.logRecordOverride(record2);
|
|
2314
4096
|
return;
|
|
2315
4097
|
}
|
|
2316
|
-
const line = `${JSON.stringify(
|
|
4098
|
+
const line = `${JSON.stringify(record2)}
|
|
2317
4099
|
`;
|
|
2318
4100
|
if (this.options.logFile) {
|
|
2319
|
-
mkdirSync2(
|
|
4101
|
+
mkdirSync2(dirname3(this.options.logFile), { recursive: true });
|
|
2320
4102
|
appendFileSync(this.options.logFile, line, "utf8");
|
|
2321
4103
|
return;
|
|
2322
4104
|
}
|
|
@@ -2864,7 +4646,7 @@ if __name__ == "__main__":
|
|
|
2864
4646
|
function resolveOutputPath(output, filename) {
|
|
2865
4647
|
if (!output) return filename;
|
|
2866
4648
|
if (existsSync5(output) && statSync4(output).isDirectory()) {
|
|
2867
|
-
return
|
|
4649
|
+
return join4(output, filename);
|
|
2868
4650
|
}
|
|
2869
4651
|
return output;
|
|
2870
4652
|
}
|
|
@@ -2940,7 +4722,7 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
2940
4722
|
if (!res.body) {
|
|
2941
4723
|
throw new Error("Download failed: response body was empty");
|
|
2942
4724
|
}
|
|
2943
|
-
mkdirSync3(
|
|
4725
|
+
mkdirSync3(dirname4(outputPath), { recursive: true });
|
|
2944
4726
|
const file = createWriteStream(outputPath);
|
|
2945
4727
|
const contentLength = parseContentLength(res.headers.get("content-length"));
|
|
2946
4728
|
const progress = createDownloadProgress(
|
|
@@ -2971,15 +4753,15 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
2971
4753
|
return contentLength;
|
|
2972
4754
|
}
|
|
2973
4755
|
function defaultCacheDir() {
|
|
2974
|
-
const root = process.env.XDG_CACHE_HOME ||
|
|
2975
|
-
return
|
|
4756
|
+
const root = process.env.XDG_CACHE_HOME || join4(homedir4(), ".cache");
|
|
4757
|
+
return join4(root, "tuned-tensor", "models");
|
|
2976
4758
|
}
|
|
2977
4759
|
function runtimeDir(cacheDir) {
|
|
2978
|
-
return
|
|
4760
|
+
return join4(cacheDir, "_runtime");
|
|
2979
4761
|
}
|
|
2980
4762
|
function runtimePythonPath(cacheDir) {
|
|
2981
4763
|
const dir = runtimeDir(cacheDir);
|
|
2982
|
-
return process.platform === "win32" ?
|
|
4764
|
+
return process.platform === "win32" ? join4(dir, "Scripts", "python.exe") : join4(dir, "bin", "python");
|
|
2983
4765
|
}
|
|
2984
4766
|
function resolveServePython(cacheDir, explicitPython) {
|
|
2985
4767
|
if (explicitPython) return explicitPython;
|
|
@@ -3052,7 +4834,7 @@ print(json.dumps({"missing": missing}))
|
|
|
3052
4834
|
}
|
|
3053
4835
|
}
|
|
3054
4836
|
function setupRuntimeCommands(python, venvDir, deps) {
|
|
3055
|
-
const venvPython = process.platform === "win32" ?
|
|
4837
|
+
const venvPython = process.platform === "win32" ? join4(venvDir, "Scripts", "python.exe") : join4(venvDir, "bin", "python");
|
|
3056
4838
|
return [
|
|
3057
4839
|
[python, "-m", "venv", venvDir],
|
|
3058
4840
|
[venvPython, "-m", "pip", "install", "--upgrade", "pip"],
|
|
@@ -3067,7 +4849,7 @@ function directoryHasFiles(path) {
|
|
|
3067
4849
|
}
|
|
3068
4850
|
function validateArchiveEntryPath(entry) {
|
|
3069
4851
|
const normalized = normalize(entry).replace(/^[\\/]+/, "");
|
|
3070
|
-
if (!entry.trim() ||
|
|
4852
|
+
if (!entry.trim() || isAbsolute4(entry) || normalized === ".." || normalized.startsWith(`..${"/"}`) || normalized.startsWith(`..${"\\"}`)) {
|
|
3071
4853
|
throw new Error(`Unsafe archive entry path: ${entry}`);
|
|
3072
4854
|
}
|
|
3073
4855
|
return normalized;
|
|
@@ -3090,9 +4872,9 @@ function extractArchive(archivePath, targetDir, force) {
|
|
|
3090
4872
|
return targetDir;
|
|
3091
4873
|
}
|
|
3092
4874
|
function localArchiveCacheDir(archivePath, cacheDir) {
|
|
3093
|
-
const resolved =
|
|
4875
|
+
const resolved = resolve5(archivePath);
|
|
3094
4876
|
const digest = createHash("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
3095
|
-
return
|
|
4877
|
+
return join4(cacheDir, `local-${safeSegment(basename4(archivePath))}-${digest}`);
|
|
3096
4878
|
}
|
|
3097
4879
|
function buildSystemPrompt(spec) {
|
|
3098
4880
|
const parts = [];
|
|
@@ -3108,9 +4890,9 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
|
3108
4890
|
return parts.join("\n\n");
|
|
3109
4891
|
}
|
|
3110
4892
|
function findSpecPath(explicitPath, modelPath) {
|
|
3111
|
-
const candidates = explicitPath ? [explicitPath] : [
|
|
4893
|
+
const candidates = explicitPath ? [explicitPath] : [join4(process.cwd(), DEFAULT_SPEC_FILE), join4(modelPath, DEFAULT_SPEC_FILE)];
|
|
3112
4894
|
for (const candidate of candidates) {
|
|
3113
|
-
const resolved =
|
|
4895
|
+
const resolved = resolve5(candidate);
|
|
3114
4896
|
if (existsSync5(resolved) && statSync4(resolved).isFile()) return resolved;
|
|
3115
4897
|
}
|
|
3116
4898
|
return null;
|
|
@@ -3120,7 +4902,7 @@ function loadSystemPromptFromSpec(specPath) {
|
|
|
3120
4902
|
return buildSystemPrompt(spec);
|
|
3121
4903
|
}
|
|
3122
4904
|
function loadJsonSchemaForServe(schemaPath) {
|
|
3123
|
-
const resolved =
|
|
4905
|
+
const resolved = resolve5(schemaPath);
|
|
3124
4906
|
if (!existsSync5(resolved) || !statSync4(resolved).isFile()) {
|
|
3125
4907
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
3126
4908
|
}
|
|
@@ -3132,28 +4914,28 @@ function loadJsonSchemaForServe(schemaPath) {
|
|
|
3132
4914
|
}
|
|
3133
4915
|
}
|
|
3134
4916
|
function writeReferenceServerScript(cacheDir) {
|
|
3135
|
-
const scriptDir =
|
|
4917
|
+
const scriptDir = join4(cacheDir, "_server");
|
|
3136
4918
|
mkdirSync3(scriptDir, { recursive: true });
|
|
3137
|
-
const scriptPath =
|
|
4919
|
+
const scriptPath = join4(scriptDir, "openai_reference_server.py");
|
|
3138
4920
|
writeFileSync3(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
|
|
3139
4921
|
return scriptPath;
|
|
3140
4922
|
}
|
|
3141
4923
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
3142
4924
|
if (existsSync5(target)) {
|
|
3143
|
-
const resolved =
|
|
4925
|
+
const resolved = resolve5(target);
|
|
3144
4926
|
const stats = statSync4(resolved);
|
|
3145
4927
|
if (stats.isDirectory()) {
|
|
3146
4928
|
return {
|
|
3147
4929
|
modelPath: resolved,
|
|
3148
|
-
modelName:
|
|
4930
|
+
modelName: basename4(resolved),
|
|
3149
4931
|
source: "local-directory"
|
|
3150
4932
|
};
|
|
3151
4933
|
}
|
|
3152
4934
|
if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
|
|
3153
|
-
const extractDir2 =
|
|
4935
|
+
const extractDir2 = join4(localArchiveCacheDir(resolved, cacheDir), "model");
|
|
3154
4936
|
return {
|
|
3155
4937
|
modelPath: extractArchive(resolved, extractDir2, forceDownload),
|
|
3156
|
-
modelName:
|
|
4938
|
+
modelName: basename4(resolved).replace(/\.t(ar\.)?gz$/, ""),
|
|
3157
4939
|
source: "local-archive"
|
|
3158
4940
|
};
|
|
3159
4941
|
}
|
|
@@ -3162,9 +4944,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
3162
4944
|
const fullId = await resolveModelId(target, opts);
|
|
3163
4945
|
const { data: model } = await get(`/models/${fullId}`, void 0, opts);
|
|
3164
4946
|
const { data } = await get(`/models/${fullId}/download`, void 0, opts);
|
|
3165
|
-
const modelCacheDir =
|
|
3166
|
-
const archivePath =
|
|
3167
|
-
const extractDir =
|
|
4947
|
+
const modelCacheDir = join4(cacheDir, fullId);
|
|
4948
|
+
const archivePath = join4(modelCacheDir, data.filename);
|
|
4949
|
+
const extractDir = join4(modelCacheDir, "model");
|
|
3168
4950
|
if (!existsSync5(archivePath) || forceDownload) {
|
|
3169
4951
|
await downloadUrlToFile(data.url, archivePath);
|
|
3170
4952
|
}
|
|
@@ -3270,7 +5052,7 @@ function ollamaSlug(name) {
|
|
|
3270
5052
|
function firstExisting(candidates) {
|
|
3271
5053
|
for (const candidate of candidates) {
|
|
3272
5054
|
if (candidate && existsSync5(candidate) && statSync4(candidate).isFile()) {
|
|
3273
|
-
return
|
|
5055
|
+
return resolve5(candidate);
|
|
3274
5056
|
}
|
|
3275
5057
|
}
|
|
3276
5058
|
return null;
|
|
@@ -3280,14 +5062,14 @@ function llamaCppDir(explicit) {
|
|
|
3280
5062
|
}
|
|
3281
5063
|
function resolveConvertScript(opts, required) {
|
|
3282
5064
|
if (opts.convertScript) {
|
|
3283
|
-
const resolved =
|
|
5065
|
+
const resolved = resolve5(opts.convertScript);
|
|
3284
5066
|
if (required && !existsSync5(resolved)) {
|
|
3285
5067
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
3286
5068
|
}
|
|
3287
5069
|
return resolved;
|
|
3288
5070
|
}
|
|
3289
5071
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
3290
|
-
const candidates = dir ? [
|
|
5072
|
+
const candidates = dir ? [join4(dir, "convert_hf_to_gguf.py"), join4(dir, "convert-hf-to-gguf.py")] : [];
|
|
3291
5073
|
const found = firstExisting(candidates);
|
|
3292
5074
|
if (found) return found;
|
|
3293
5075
|
if (required) {
|
|
@@ -3295,11 +5077,11 @@ function resolveConvertScript(opts, required) {
|
|
|
3295
5077
|
"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."
|
|
3296
5078
|
);
|
|
3297
5079
|
}
|
|
3298
|
-
return dir ?
|
|
5080
|
+
return dir ? join4(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
|
|
3299
5081
|
}
|
|
3300
5082
|
function resolveQuantizeBin(opts, required) {
|
|
3301
5083
|
if (opts.quantizeBin) {
|
|
3302
|
-
const resolved =
|
|
5084
|
+
const resolved = resolve5(opts.quantizeBin);
|
|
3303
5085
|
if (required && !existsSync5(resolved)) {
|
|
3304
5086
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
3305
5087
|
}
|
|
@@ -3308,10 +5090,10 @@ function resolveQuantizeBin(opts, required) {
|
|
|
3308
5090
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
3309
5091
|
if (dir) {
|
|
3310
5092
|
const candidates = [
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
5093
|
+
join4(dir, "build", "bin", "llama-quantize"),
|
|
5094
|
+
join4(dir, "build", "bin", "quantize"),
|
|
5095
|
+
join4(dir, "llama-quantize"),
|
|
5096
|
+
join4(dir, "quantize")
|
|
3315
5097
|
];
|
|
3316
5098
|
const found = firstExisting(candidates);
|
|
3317
5099
|
if (found) return found;
|
|
@@ -3459,7 +5241,7 @@ function registerModelsCommands(parent) {
|
|
|
3459
5241
|
}
|
|
3460
5242
|
const quantPlan = planQuant(String(cmdOpts.quant));
|
|
3461
5243
|
const printOnly = Boolean(cmdOpts.printCommand);
|
|
3462
|
-
const cacheDir =
|
|
5244
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3463
5245
|
mkdirSync3(cacheDir, { recursive: true });
|
|
3464
5246
|
const serveTarget = await resolveServeTarget(
|
|
3465
5247
|
target,
|
|
@@ -3468,9 +5250,9 @@ function registerModelsCommands(parent) {
|
|
|
3468
5250
|
Boolean(cmdOpts.forceDownload)
|
|
3469
5251
|
);
|
|
3470
5252
|
const slug = ollamaSlug(serveTarget.modelName);
|
|
3471
|
-
const outputDir =
|
|
3472
|
-
const finalPath =
|
|
3473
|
-
const intermediatePath = quantPlan.requiresQuantize ?
|
|
5253
|
+
const outputDir = resolve5(cmdOpts.output || join4(process.cwd(), `${slug}-gguf`));
|
|
5254
|
+
const finalPath = join4(outputDir, `${slug}.${quantPlan.quant}.gguf`);
|
|
5255
|
+
const intermediatePath = quantPlan.requiresQuantize ? join4(outputDir, `${slug}.f16.gguf`) : void 0;
|
|
3474
5256
|
const convertScript = resolveConvertScript(cmdOpts, !printOnly);
|
|
3475
5257
|
const convertOutfile = intermediatePath ?? finalPath;
|
|
3476
5258
|
const steps = [
|
|
@@ -3508,8 +5290,8 @@ function registerModelsCommands(parent) {
|
|
|
3508
5290
|
}
|
|
3509
5291
|
if (specPath) systemPrompt = loadSystemPromptFromSpec(specPath);
|
|
3510
5292
|
}
|
|
3511
|
-
modelfilePath =
|
|
3512
|
-
modelfileContent = buildModelfile(
|
|
5293
|
+
modelfilePath = join4(outputDir, "Modelfile");
|
|
5294
|
+
modelfileContent = buildModelfile(basename4(finalPath), systemPrompt);
|
|
3513
5295
|
const createModel = cmdOpts.ollamaCreate !== false;
|
|
3514
5296
|
if (createModel) {
|
|
3515
5297
|
steps.push(
|
|
@@ -3595,7 +5377,7 @@ function registerModelsCommands(parent) {
|
|
|
3595
5377
|
}
|
|
3596
5378
|
});
|
|
3597
5379
|
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) => {
|
|
3598
|
-
const cacheDir =
|
|
5380
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3599
5381
|
const venvDir = runtimeDir(cacheDir);
|
|
3600
5382
|
const venvPython = runtimePythonPath(cacheDir);
|
|
3601
5383
|
const python = pickRuntimePython(cmdOpts.python);
|
|
@@ -3641,7 +5423,7 @@ function registerModelsCommands(parent) {
|
|
|
3641
5423
|
});
|
|
3642
5424
|
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) => {
|
|
3643
5425
|
const opts = parent.opts();
|
|
3644
|
-
const cacheDir =
|
|
5426
|
+
const cacheDir = resolve5(cmdOpts.cacheDir || defaultCacheDir());
|
|
3645
5427
|
mkdirSync3(cacheDir, { recursive: true });
|
|
3646
5428
|
const python = resolveServePython(cacheDir, cmdOpts.python);
|
|
3647
5429
|
if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
|
|
@@ -3690,7 +5472,7 @@ function registerModelsCommands(parent) {
|
|
|
3690
5472
|
idle_timeout_seconds: idleTimeoutSeconds,
|
|
3691
5473
|
restart_after_requests: restartAfterRequests,
|
|
3692
5474
|
gate_field: String(cmdOpts.gateField),
|
|
3693
|
-
log_file: cmdOpts.logFile ?
|
|
5475
|
+
log_file: cmdOpts.logFile ? resolve5(cmdOpts.logFile) : void 0
|
|
3694
5476
|
} : void 0;
|
|
3695
5477
|
if (cmdOpts.printCommand) return printServeCommand(python, args, env, managedConfig);
|
|
3696
5478
|
if (!isJsonMode()) {
|
|
@@ -3727,12 +5509,12 @@ function registerModelsCommands(parent) {
|
|
|
3727
5509
|
idleTimeoutSeconds,
|
|
3728
5510
|
restartAfterRequests,
|
|
3729
5511
|
gateField: String(cmdOpts.gateField),
|
|
3730
|
-
logFile: cmdOpts.logFile ?
|
|
5512
|
+
logFile: cmdOpts.logFile ? resolve5(cmdOpts.logFile) : void 0
|
|
3731
5513
|
});
|
|
3732
5514
|
return;
|
|
3733
5515
|
}
|
|
3734
5516
|
await new Promise((resolvePromise, reject) => {
|
|
3735
|
-
const child =
|
|
5517
|
+
const child = spawn3(python, args, {
|
|
3736
5518
|
stdio: "inherit",
|
|
3737
5519
|
env: { ...process.env, ...env }
|
|
3738
5520
|
});
|
|
@@ -3753,7 +5535,7 @@ function registerModelsCommands(parent) {
|
|
|
3753
5535
|
}
|
|
3754
5536
|
|
|
3755
5537
|
// src/commands/balance.ts
|
|
3756
|
-
import
|
|
5538
|
+
import chalk4 from "chalk";
|
|
3757
5539
|
var KIND_LABELS = {
|
|
3758
5540
|
signup_bonus: "Signup bonus",
|
|
3759
5541
|
topup: "Top-up",
|
|
@@ -3768,8 +5550,8 @@ function formatCents3(cents) {
|
|
|
3768
5550
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
3769
5551
|
}
|
|
3770
5552
|
function formatSigned(cents) {
|
|
3771
|
-
if (cents > 0) return
|
|
3772
|
-
if (cents < 0) return
|
|
5553
|
+
if (cents > 0) return chalk4.green(`+${formatCents3(cents)}`);
|
|
5554
|
+
if (cents < 0) return chalk4.red(formatCents3(cents));
|
|
3773
5555
|
return formatCents3(cents);
|
|
3774
5556
|
}
|
|
3775
5557
|
function registerBalanceCommands(parent) {
|
|
@@ -3784,7 +5566,7 @@ function registerBalanceCommands(parent) {
|
|
|
3784
5566
|
return printJson({ balance, transactions });
|
|
3785
5567
|
}
|
|
3786
5568
|
const lowBalance = balance.balance_cents < 100;
|
|
3787
|
-
const balanceLine = lowBalance ?
|
|
5569
|
+
const balanceLine = lowBalance ? chalk4.red(formatCents3(balance.balance_cents)) + chalk4.dim(" (low)") : chalk4.bold.green(formatCents3(balance.balance_cents));
|
|
3788
5570
|
printDetail([
|
|
3789
5571
|
["Credits", balanceLine],
|
|
3790
5572
|
[
|
|
@@ -3799,7 +5581,7 @@ function registerBalanceCommands(parent) {
|
|
|
3799
5581
|
]);
|
|
3800
5582
|
if (transactions && transactions.length > 0) {
|
|
3801
5583
|
console.log(`
|
|
3802
|
-
${
|
|
5584
|
+
${chalk4.bold("Recent transactions")}`);
|
|
3803
5585
|
printTable(
|
|
3804
5586
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
3805
5587
|
transactions.map((row) => [
|
|
@@ -3807,19 +5589,19 @@ ${chalk3.bold("Recent transactions")}`);
|
|
|
3807
5589
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
3808
5590
|
formatSigned(row.amount_cents),
|
|
3809
5591
|
formatCents3(row.balance_after_cents),
|
|
3810
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
5592
|
+
row.reference_id ? shortId(row.reference_id) : chalk4.dim("\u2014")
|
|
3811
5593
|
])
|
|
3812
5594
|
);
|
|
3813
5595
|
} else {
|
|
3814
5596
|
console.log(
|
|
3815
5597
|
`
|
|
3816
|
-
${
|
|
5598
|
+
${chalk4.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
3817
5599
|
);
|
|
3818
5600
|
}
|
|
3819
5601
|
if (lowBalance) {
|
|
3820
5602
|
console.log(
|
|
3821
5603
|
`
|
|
3822
|
-
${formatStatus("preparing")} ${
|
|
5604
|
+
${formatStatus("preparing")} ${chalk4.yellow(
|
|
3823
5605
|
"Run `tt topup` to add more credits before starting a run."
|
|
3824
5606
|
)}`
|
|
3825
5607
|
);
|
|
@@ -3828,7 +5610,7 @@ ${formatStatus("preparing")} ${chalk3.yellow(
|
|
|
3828
5610
|
}
|
|
3829
5611
|
|
|
3830
5612
|
// src/commands/topup.ts
|
|
3831
|
-
import
|
|
5613
|
+
import chalk5 from "chalk";
|
|
3832
5614
|
import open from "open";
|
|
3833
5615
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
3834
5616
|
var MIN_USD = 5;
|
|
@@ -3867,9 +5649,9 @@ function registerTopupCommands(parent) {
|
|
|
3867
5649
|
"amount is required in --json mode (use --amount <usd>)"
|
|
3868
5650
|
);
|
|
3869
5651
|
}
|
|
3870
|
-
console.log(
|
|
5652
|
+
console.log(chalk5.bold("Add credits via Stripe Checkout"));
|
|
3871
5653
|
console.log(
|
|
3872
|
-
|
|
5654
|
+
chalk5.dim(
|
|
3873
5655
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
3874
5656
|
)
|
|
3875
5657
|
);
|
|
@@ -3896,8 +5678,8 @@ function registerTopupCommands(parent) {
|
|
|
3896
5678
|
}
|
|
3897
5679
|
const url = data.checkout_url;
|
|
3898
5680
|
console.log(
|
|
3899
|
-
`${
|
|
3900
|
-
` +
|
|
5681
|
+
`${chalk5.bold("Checkout URL:")} ${chalk5.cyan(url)}
|
|
5682
|
+
` + chalk5.dim("Complete the payment to credit your account.")
|
|
3901
5683
|
);
|
|
3902
5684
|
if (options.open !== false) {
|
|
3903
5685
|
try {
|
|
@@ -3905,7 +5687,7 @@ function registerTopupCommands(parent) {
|
|
|
3905
5687
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
3906
5688
|
} catch {
|
|
3907
5689
|
console.log(
|
|
3908
|
-
|
|
5690
|
+
chalk5.yellow(
|
|
3909
5691
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
3910
5692
|
)
|
|
3911
5693
|
);
|
|
@@ -3915,7 +5697,7 @@ function registerTopupCommands(parent) {
|
|
|
3915
5697
|
}
|
|
3916
5698
|
|
|
3917
5699
|
// src/commands/eval.ts
|
|
3918
|
-
import
|
|
5700
|
+
import chalk6 from "chalk";
|
|
3919
5701
|
|
|
3920
5702
|
// src/eval/rules.ts
|
|
3921
5703
|
function validateSpec(spec) {
|
|
@@ -4050,12 +5832,12 @@ function checkExamplesAgainstConstraints(spec) {
|
|
|
4050
5832
|
}
|
|
4051
5833
|
return violations;
|
|
4052
5834
|
}
|
|
4053
|
-
function evaluateConstraint(
|
|
5835
|
+
function evaluateConstraint(text2, constraint) {
|
|
4054
5836
|
const lower = constraint.toLowerCase();
|
|
4055
5837
|
const neverMatch = lower.match(/^never (?:share|mention|include|reveal|use|say|output|return|give|provide|show|disclose|expose)\s+(.+)/);
|
|
4056
5838
|
if (neverMatch) {
|
|
4057
5839
|
const forbidden = neverMatch[1].replace(/['"]/g, "").trim();
|
|
4058
|
-
if (
|
|
5840
|
+
if (text2.toLowerCase().includes(forbidden.toLowerCase())) {
|
|
4059
5841
|
return { passed: false, message: `Output contains "${forbidden}"` };
|
|
4060
5842
|
}
|
|
4061
5843
|
return { passed: true };
|
|
@@ -4063,7 +5845,7 @@ function evaluateConstraint(text, constraint) {
|
|
|
4063
5845
|
const alwaysMatch = lower.match(/^always (?:include|use|start with|end with|contain|respond in|reply in)\s+(.+)/);
|
|
4064
5846
|
if (alwaysMatch) {
|
|
4065
5847
|
const required = alwaysMatch[1].replace(/['"]/g, "").trim();
|
|
4066
|
-
if (!
|
|
5848
|
+
if (!text2.toLowerCase().includes(required.toLowerCase())) {
|
|
4067
5849
|
return { passed: false, message: `Output missing "${required}"` };
|
|
4068
5850
|
}
|
|
4069
5851
|
return { passed: true };
|
|
@@ -4076,11 +5858,11 @@ function evaluateConstraint(text, constraint) {
|
|
|
4076
5858
|
|
|
4077
5859
|
// src/commands/eval.ts
|
|
4078
5860
|
function registerEvalCommand(parent) {
|
|
4079
|
-
parent.command("eval").description("Validate a
|
|
5861
|
+
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) => {
|
|
4080
5862
|
const spec = loadSpec(cmdOpts.file);
|
|
4081
5863
|
const validation = validateSpec(spec);
|
|
4082
5864
|
if (!isJsonMode()) {
|
|
4083
|
-
console.log(
|
|
5865
|
+
console.log(chalk6.dim(`
|
|
4084
5866
|
Spec: ${cmdOpts.file}
|
|
4085
5867
|
`));
|
|
4086
5868
|
}
|
|
@@ -4095,71 +5877,321 @@ Spec: ${cmdOpts.file}
|
|
|
4095
5877
|
});
|
|
4096
5878
|
}
|
|
4097
5879
|
function printValidation(checks) {
|
|
4098
|
-
console.log(
|
|
5880
|
+
console.log(chalk6.bold("Spec Validation"));
|
|
4099
5881
|
for (const check of checks) {
|
|
4100
|
-
const icon = check.passed ?
|
|
4101
|
-
const msg = check.message ?
|
|
5882
|
+
const icon = check.passed ? chalk6.green("\u2713") : chalk6.red("\u2717");
|
|
5883
|
+
const msg = check.message ? chalk6.dim(` \u2014 ${check.message}`) : "";
|
|
4102
5884
|
console.log(` ${icon} ${check.name}${msg}`);
|
|
4103
5885
|
}
|
|
4104
5886
|
}
|
|
4105
5887
|
|
|
4106
5888
|
// src/commands/push.ts
|
|
4107
5889
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4108
|
-
import { resolve as
|
|
5890
|
+
import { resolve as resolve6 } from "path";
|
|
5891
|
+
function persistRemoteId(filePath, id) {
|
|
5892
|
+
const raw = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
5893
|
+
raw.id = id;
|
|
5894
|
+
writeFileSync4(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
5895
|
+
}
|
|
4109
5896
|
function registerPushCommand(parent) {
|
|
4110
5897
|
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) => {
|
|
4111
5898
|
const opts = parent.opts();
|
|
4112
|
-
const filePath =
|
|
5899
|
+
const filePath = resolve6(cmdOpts.file);
|
|
4113
5900
|
const spec = loadSpec(cmdOpts.file);
|
|
4114
5901
|
const evalCaseErrors = validateEvalCases(spec);
|
|
4115
5902
|
if (evalCaseErrors.length > 0) {
|
|
4116
5903
|
throw new Error(`Invalid eval_cases: ${evalCaseErrors.join("; ")}`);
|
|
4117
5904
|
}
|
|
4118
|
-
const
|
|
4119
|
-
const body =
|
|
5905
|
+
const rawSpec = spec;
|
|
5906
|
+
const { body } = projectCloudSpec(rawSpec);
|
|
5907
|
+
const id = spec.id;
|
|
5908
|
+
const canRecoverLocalId = Boolean(
|
|
5909
|
+
id && hasLocalOnlySpecFields(rawSpec)
|
|
5910
|
+
);
|
|
4120
5911
|
let data;
|
|
5912
|
+
let created = false;
|
|
4121
5913
|
if (id) {
|
|
4122
|
-
|
|
4123
|
-
|
|
5914
|
+
try {
|
|
5915
|
+
const res = await put(
|
|
5916
|
+
`/behavior-specs/${id}`,
|
|
5917
|
+
body,
|
|
5918
|
+
opts
|
|
5919
|
+
);
|
|
5920
|
+
data = res.data;
|
|
5921
|
+
} catch (error) {
|
|
5922
|
+
if (!canRecoverLocalId || !(error instanceof ApiError) || error.status !== 404) {
|
|
5923
|
+
throw error;
|
|
5924
|
+
}
|
|
5925
|
+
const res = await post("/behavior-specs", body, opts);
|
|
5926
|
+
data = res.data;
|
|
5927
|
+
created = true;
|
|
5928
|
+
persistRemoteId(filePath, data.id);
|
|
5929
|
+
}
|
|
4124
5930
|
} else {
|
|
4125
5931
|
const res = await post("/behavior-specs", body, opts);
|
|
4126
5932
|
data = res.data;
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
writeFileSync4(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
5933
|
+
created = true;
|
|
5934
|
+
persistRemoteId(filePath, data.id);
|
|
4130
5935
|
}
|
|
4131
5936
|
if (isJsonMode()) return printJson(data);
|
|
4132
5937
|
printSuccess(
|
|
4133
|
-
`Spec ${
|
|
5938
|
+
`Spec ${created ? "created" : "updated"}: ${data.name} (${shortId(data.id)})`
|
|
4134
5939
|
);
|
|
4135
5940
|
});
|
|
4136
5941
|
}
|
|
4137
5942
|
|
|
4138
|
-
// src/
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
5943
|
+
// src/cli.ts
|
|
5944
|
+
async function runSelfCommand(args, options = {}) {
|
|
5945
|
+
const entrypoint = options.entrypoint ?? process.argv[1];
|
|
5946
|
+
if (!entrypoint) {
|
|
5947
|
+
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
5948
|
+
}
|
|
5949
|
+
return await new Promise((resolve7, reject) => {
|
|
5950
|
+
const child = spawn4(process.execPath, [entrypoint, ...args], {
|
|
5951
|
+
cwd: options.cwd,
|
|
5952
|
+
env: options.env ?? process.env,
|
|
5953
|
+
stdio: "inherit",
|
|
5954
|
+
detached: process.platform !== "win32"
|
|
5955
|
+
});
|
|
5956
|
+
const forwardSignal = (signal) => {
|
|
5957
|
+
const childSignal = signal === "SIGHUP" ? "SIGTERM" : signal;
|
|
5958
|
+
if (child.pid && process.platform !== "win32") {
|
|
5959
|
+
try {
|
|
5960
|
+
process.kill(-child.pid, childSignal);
|
|
5961
|
+
return;
|
|
5962
|
+
} catch {
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
child.kill(childSignal);
|
|
5966
|
+
};
|
|
5967
|
+
const onSigint = () => forwardSignal("SIGINT");
|
|
5968
|
+
const onSigterm = () => forwardSignal("SIGTERM");
|
|
5969
|
+
const onSighup = () => forwardSignal("SIGHUP");
|
|
5970
|
+
const cleanup = () => {
|
|
5971
|
+
process.off("SIGINT", onSigint);
|
|
5972
|
+
process.off("SIGTERM", onSigterm);
|
|
5973
|
+
if (process.platform !== "win32") process.off("SIGHUP", onSighup);
|
|
5974
|
+
};
|
|
5975
|
+
process.on("SIGINT", onSigint);
|
|
5976
|
+
process.on("SIGTERM", onSigterm);
|
|
5977
|
+
if (process.platform !== "win32") process.on("SIGHUP", onSighup);
|
|
5978
|
+
child.once("error", (error) => {
|
|
5979
|
+
cleanup();
|
|
5980
|
+
reject(error);
|
|
5981
|
+
});
|
|
5982
|
+
child.once("close", (exitCode, signal) => {
|
|
5983
|
+
cleanup();
|
|
5984
|
+
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
5985
|
+
resolve7({
|
|
5986
|
+
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
5987
|
+
signal
|
|
5988
|
+
});
|
|
5989
|
+
});
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
function extractPassthroughOptions(args, root) {
|
|
5993
|
+
let json = Boolean(root.json);
|
|
5994
|
+
let color = root.color !== false;
|
|
5995
|
+
const forwarded = [];
|
|
5996
|
+
for (const arg of args) {
|
|
5997
|
+
if (arg === "--json") {
|
|
5998
|
+
json = true;
|
|
5999
|
+
} else if (arg === "--no-color") {
|
|
6000
|
+
color = false;
|
|
6001
|
+
} else {
|
|
6002
|
+
forwarded.push(arg);
|
|
6003
|
+
}
|
|
4148
6004
|
}
|
|
4149
|
-
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
6005
|
+
return { args: forwarded, json, color };
|
|
6006
|
+
}
|
|
6007
|
+
function childEnvironment(root, base) {
|
|
6008
|
+
return {
|
|
6009
|
+
...base,
|
|
6010
|
+
...root.apiKey ? { TUNED_TENSOR_API_KEY: root.apiKey } : {},
|
|
6011
|
+
...root.baseUrl ? { TUNED_TENSOR_URL: root.baseUrl } : {},
|
|
6012
|
+
...root.color === false ? { FORCE_COLOR: "0" } : {}
|
|
6013
|
+
};
|
|
6014
|
+
}
|
|
6015
|
+
function applyExitCode(result) {
|
|
6016
|
+
if (result?.exitCode !== null && result?.exitCode !== void 0) {
|
|
6017
|
+
process.exitCode = result.exitCode;
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
function createProgram(version, runtime = {}) {
|
|
6021
|
+
const program = new Command();
|
|
6022
|
+
const env = runtime.env ?? process.env;
|
|
6023
|
+
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6024
|
+
const invokeLocal = runtime.runLocalCommand ?? executeLocalCommand;
|
|
6025
|
+
const launchShell = runtime.startShell ?? startInteractiveShell;
|
|
6026
|
+
const cwd = runtime.cwd ?? process.cwd();
|
|
6027
|
+
const shellRunner = async (request2) => {
|
|
6028
|
+
const root = program.opts();
|
|
6029
|
+
const args = request2.target === "local" ? ["local", ...request2.args] : request2.args;
|
|
6030
|
+
return await invokeSelf(args, {
|
|
6031
|
+
cwd: request2.cwd,
|
|
6032
|
+
env: childEnvironment(root, env),
|
|
6033
|
+
entrypoint: runtime.argv?.[1]
|
|
6034
|
+
});
|
|
6035
|
+
};
|
|
6036
|
+
const openShell = async () => {
|
|
6037
|
+
const root = program.opts();
|
|
6038
|
+
const shellEnvironment = childEnvironment(root, env);
|
|
6039
|
+
await launchShell({
|
|
6040
|
+
runner: shellRunner,
|
|
6041
|
+
input: runtime.stdin ?? process.stdin,
|
|
6042
|
+
output: runtime.stdout ?? process.stdout,
|
|
6043
|
+
error: runtime.stderr ?? process.stderr,
|
|
6044
|
+
cwd,
|
|
6045
|
+
env: shellEnvironment
|
|
6046
|
+
});
|
|
6047
|
+
};
|
|
6048
|
+
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(
|
|
6049
|
+
"-u, --base-url <url>",
|
|
6050
|
+
"API base URL (default: https://tunedtensor.com)"
|
|
6051
|
+
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").showSuggestionAfterError().hook("preAction", () => {
|
|
6052
|
+
const root = program.opts();
|
|
6053
|
+
if (root.json) setJsonMode(true);
|
|
6054
|
+
if (root.color === false) {
|
|
6055
|
+
process.env.FORCE_COLOR = "0";
|
|
6056
|
+
chalk7.level = 0;
|
|
6057
|
+
}
|
|
6058
|
+
});
|
|
6059
|
+
registerAuthCommands(program);
|
|
6060
|
+
registerSpecsCommands(program);
|
|
6061
|
+
registerRunsCommands(program);
|
|
6062
|
+
registerDatasetsCommands(program);
|
|
6063
|
+
registerLabelCommands(program);
|
|
6064
|
+
registerModelsCommands(program);
|
|
6065
|
+
registerBalanceCommands(program);
|
|
6066
|
+
registerTopupCommands(program);
|
|
6067
|
+
registerInitCommand(program);
|
|
6068
|
+
registerEvalCommand(program);
|
|
6069
|
+
registerPushCommand(program);
|
|
6070
|
+
program.command("status").description("Show read-only cloud, local, and project context").option("--target <target>", "Show cloud or local workflow status").action(async (commandOptions) => {
|
|
6071
|
+
const root = program.opts();
|
|
6072
|
+
const context = await discoverShellContext({
|
|
6073
|
+
cwd,
|
|
6074
|
+
env: childEnvironment(root, env)
|
|
6075
|
+
});
|
|
6076
|
+
const target = commandOptions.target ?? context.inferredTarget;
|
|
6077
|
+
if (target !== "cloud" && target !== "local") {
|
|
6078
|
+
throw new Error("--target must be cloud or local.");
|
|
6079
|
+
}
|
|
6080
|
+
const output = runtime.stdout ?? process.stdout;
|
|
6081
|
+
if (isJsonMode()) {
|
|
6082
|
+
output.write(`${JSON.stringify({
|
|
6083
|
+
target,
|
|
6084
|
+
context
|
|
6085
|
+
}, null, 2)}
|
|
6086
|
+
`);
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
output.write(`${chalk7.bold.cyan("Tuned Tensor status")}
|
|
6090
|
+
`);
|
|
6091
|
+
output.write(`${formatShellStatus(context, target).join("\n")}
|
|
6092
|
+
|
|
6093
|
+
`);
|
|
6094
|
+
output.write(`${chalk7.bold("Context")}
|
|
6095
|
+
`);
|
|
6096
|
+
output.write(
|
|
6097
|
+
`${formatShellContext(
|
|
6098
|
+
context,
|
|
6099
|
+
target,
|
|
6100
|
+
commandOptions.target ? "command-option" : context.targetSource
|
|
6101
|
+
).join("\n")}
|
|
6102
|
+
`
|
|
6103
|
+
);
|
|
6104
|
+
});
|
|
6105
|
+
program.command("local").description("Run the local CUDA training and model workflow").helpOption(false).allowUnknownOption().argument("[args...]", "TT Local command and arguments").action(async (args) => {
|
|
6106
|
+
const root = program.opts();
|
|
6107
|
+
const passthrough = extractPassthroughOptions(args, root);
|
|
6108
|
+
if (!passthrough.color) chalk7.level = 0;
|
|
6109
|
+
const result = await invokeLocal(
|
|
6110
|
+
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6111
|
+
{
|
|
6112
|
+
jsonMode: passthrough.json,
|
|
6113
|
+
cwd,
|
|
6114
|
+
env: childEnvironment(
|
|
6115
|
+
{ ...root, color: passthrough.color },
|
|
6116
|
+
env
|
|
6117
|
+
),
|
|
6118
|
+
stdin: runtime.stdin ?? process.stdin,
|
|
6119
|
+
stdout: runtime.stdout ?? process.stdout,
|
|
6120
|
+
stderr: runtime.stderr ?? process.stderr
|
|
6121
|
+
}
|
|
6122
|
+
);
|
|
6123
|
+
applyExitCode(result);
|
|
6124
|
+
});
|
|
6125
|
+
program.command("cloud").description("Run a hosted command explicitly").helpOption(false).allowUnknownOption().argument("[args...]", "Hosted tt command and arguments").action(async (args) => {
|
|
6126
|
+
const root = program.opts();
|
|
6127
|
+
const passthrough = extractPassthroughOptions(args, root);
|
|
6128
|
+
const forwarded = [
|
|
6129
|
+
...passthrough.json ? ["--json"] : [],
|
|
6130
|
+
...!passthrough.color ? ["--no-color"] : [],
|
|
6131
|
+
...passthrough.args.length > 0 ? passthrough.args : ["--help"]
|
|
6132
|
+
];
|
|
6133
|
+
const result = await invokeSelf(forwarded, {
|
|
6134
|
+
cwd,
|
|
6135
|
+
env: childEnvironment(
|
|
6136
|
+
{ ...root, color: passthrough.color },
|
|
6137
|
+
env
|
|
6138
|
+
),
|
|
6139
|
+
entrypoint: runtime.argv?.[1]
|
|
6140
|
+
});
|
|
6141
|
+
applyExitCode(result);
|
|
6142
|
+
});
|
|
6143
|
+
program.command("shell").description("Open the interactive cloud/local terminal").action(openShell);
|
|
6144
|
+
program.addHelpText(
|
|
6145
|
+
"after",
|
|
6146
|
+
`
|
|
6147
|
+
Workflows:
|
|
6148
|
+
tt Open the interactive terminal (TTY only)
|
|
6149
|
+
tt status Inspect cloud/local project context
|
|
6150
|
+
tt cloud <command> Run a hosted command explicitly
|
|
6151
|
+
tt local <command> Run a local GPU command
|
|
6152
|
+
|
|
6153
|
+
Examples:
|
|
6154
|
+
tt runs list
|
|
6155
|
+
tt local doctor tunedtensor.json
|
|
6156
|
+
tt local run tunedtensor.json --dry-run
|
|
6157
|
+
`
|
|
6158
|
+
);
|
|
6159
|
+
return program;
|
|
6160
|
+
}
|
|
6161
|
+
async function runCli(version, runtime = {}) {
|
|
6162
|
+
const argv = runtime.argv ?? process.argv;
|
|
6163
|
+
const env = runtime.env ?? process.env;
|
|
6164
|
+
const args = argv.slice(2);
|
|
6165
|
+
if (shouldStartInteractiveShell({
|
|
6166
|
+
args,
|
|
6167
|
+
stdinIsTTY: runtime.stdinIsTTY ?? process.stdin.isTTY,
|
|
6168
|
+
stdoutIsTTY: runtime.stdoutIsTTY ?? process.stdout.isTTY,
|
|
6169
|
+
env
|
|
6170
|
+
})) {
|
|
6171
|
+
const invokeSelf = runtime.runSelfCommand ?? runSelfCommand;
|
|
6172
|
+
await (runtime.startShell ?? startInteractiveShell)({
|
|
6173
|
+
runner: async (request2) => await invokeSelf(
|
|
6174
|
+
request2.target === "local" ? ["local", ...request2.args] : request2.args,
|
|
6175
|
+
{
|
|
6176
|
+
cwd: request2.cwd,
|
|
6177
|
+
env,
|
|
6178
|
+
entrypoint: argv[1]
|
|
6179
|
+
}
|
|
6180
|
+
),
|
|
6181
|
+
input: runtime.stdin ?? process.stdin,
|
|
6182
|
+
output: runtime.stdout ?? process.stdout,
|
|
6183
|
+
error: runtime.stderr ?? process.stderr,
|
|
6184
|
+
cwd: runtime.cwd ?? process.cwd(),
|
|
6185
|
+
env
|
|
6186
|
+
});
|
|
6187
|
+
return;
|
|
6188
|
+
}
|
|
6189
|
+
if (argv.includes("--json")) setJsonMode(true);
|
|
6190
|
+
await createProgram(version, { ...runtime, argv }).parseAsync(argv);
|
|
6191
|
+
}
|
|
6192
|
+
|
|
6193
|
+
// src/index.ts
|
|
6194
|
+
runCli("0.5.0").catch((err) => {
|
|
4163
6195
|
reportError(err);
|
|
4164
6196
|
process.exit(1);
|
|
4165
6197
|
});
|