@tuned-tensor/cli 0.13.0-beta.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -9
- package/dist/{chunk-2VJ3VVZF.js → chunk-YJYCGSHJ.js} +537 -536
- package/dist/chunk-YJYCGSHJ.js.map +1 -0
- package/dist/index.js +53 -401
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +39 -10
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +6 -5
- package/docs/local-runtime/spark.md +23 -17
- package/package.json +1 -1
- package/training/local-runner/src/evaluate.py +4 -1
- package/training/local-runner/src/model_contract.py +5 -3
- package/training/local-runner/src/serve.py +1 -1
- package/dist/chunk-2VJ3VVZF.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,19 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
TRAINING_MODELS,
|
|
4
4
|
assertLocalRunInputReady,
|
|
5
|
-
createLocalStore,
|
|
6
|
-
defaultLocalHome,
|
|
7
5
|
loadLocalRunInput,
|
|
8
6
|
loadLocalRunnerConfig,
|
|
9
|
-
runLocalPipeline
|
|
10
|
-
|
|
11
|
-
} from "./chunk-2VJ3VVZF.js";
|
|
7
|
+
runLocalPipeline
|
|
8
|
+
} from "./chunk-YJYCGSHJ.js";
|
|
12
9
|
|
|
13
10
|
// src/cli.ts
|
|
14
11
|
import { spawn as spawn2 } from "child_process";
|
|
15
12
|
import { constants as osConstants2 } from "os";
|
|
16
13
|
import { Command } from "commander";
|
|
17
|
-
import
|
|
14
|
+
import chalk6 from "chalk";
|
|
18
15
|
|
|
19
16
|
// src/local-agent-client.ts
|
|
20
17
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -1588,10 +1585,6 @@ function updateConfig(partial) {
|
|
|
1588
1585
|
const current = readConfig();
|
|
1589
1586
|
writeConfig({ ...current, ...partial });
|
|
1590
1587
|
}
|
|
1591
|
-
var DEFAULT_BASE_URL = "https://tunedtensor.com";
|
|
1592
|
-
function getBaseUrl(opts) {
|
|
1593
|
-
return opts?.baseUrl || process.env.TUNED_TENSOR_URL || readConfig().base_url || DEFAULT_BASE_URL;
|
|
1594
|
-
}
|
|
1595
1588
|
function getApiKey(opts) {
|
|
1596
1589
|
return opts?.apiKey || process.env.TUNED_TENSOR_API_KEY || readConfig().api_key;
|
|
1597
1590
|
}
|
|
@@ -1615,7 +1608,7 @@ function getAgentSelection(env = process.env) {
|
|
|
1615
1608
|
}
|
|
1616
1609
|
|
|
1617
1610
|
// src/cli.ts
|
|
1618
|
-
import { join as
|
|
1611
|
+
import { join as join7 } from "path";
|
|
1619
1612
|
|
|
1620
1613
|
// src/agent.ts
|
|
1621
1614
|
import chalk3 from "chalk";
|
|
@@ -1636,6 +1629,11 @@ var COMMAND_CATALOG = [
|
|
|
1636
1629
|
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
1637
1630
|
{ path: "doctor", description: "Check the local host and run prerequisites.", group: "Workflow", modes: LOCAL },
|
|
1638
1631
|
{ path: "run", description: "Run the local fine-tuning and evaluation workflow.", group: "Workflow", modes: LOCAL },
|
|
1632
|
+
{ path: "pipeline init", description: "Write a canonical v1 pipeline recipe.", group: "Workflow", modes: LOCAL },
|
|
1633
|
+
{ path: "pipeline validate", description: "Validate a pipeline without executing it.", group: "Workflow", modes: LOCAL },
|
|
1634
|
+
{ path: "pipeline plan", description: "Resolve pipeline steps and required artifacts.", group: "Workflow", modes: LOCAL },
|
|
1635
|
+
{ path: "pipeline run", description: "Run or dry-run a local pipeline.", group: "Workflow", modes: LOCAL },
|
|
1636
|
+
{ path: "shell", description: "Open the conversational terminal.", group: "Workflow", modes: LOCAL },
|
|
1639
1637
|
{ path: "runs list", description: "List local runs.", group: "Inspect", modes: LOCAL },
|
|
1640
1638
|
{ path: "runs get", description: "Show a local run.", group: "Inspect", modes: LOCAL },
|
|
1641
1639
|
{ path: "runs report", description: "Show a local run report.", group: "Inspect", modes: LOCAL },
|
|
@@ -1652,10 +1650,10 @@ var COMMAND_CATALOG = [
|
|
|
1652
1650
|
{ path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
|
|
1653
1651
|
{ path: "serve", description: "Serve a local adapter, active model, or base model.", group: "Serving", modes: LOCAL },
|
|
1654
1652
|
{ path: "info", description: "Show local runtime package information.", group: "Inspect", modes: LOCAL },
|
|
1655
|
-
{ path: "
|
|
1656
|
-
{ path: "
|
|
1657
|
-
{ path: "
|
|
1658
|
-
{ path: "
|
|
1653
|
+
{ path: "status", description: "Show local project context.", group: "Inspect", modes: LOCAL },
|
|
1654
|
+
{ path: "agent models", description: "List provider models for the laptop-local TT agent.", group: "Inspect", modes: LOCAL },
|
|
1655
|
+
{ path: "agent configure", description: "Select the laptop-local TT agent model.", group: "Inspect", modes: LOCAL },
|
|
1656
|
+
{ path: "agent status", description: "Show the laptop-local TT agent selection.", group: "Inspect", modes: LOCAL }
|
|
1659
1657
|
];
|
|
1660
1658
|
var SLASH_COMMANDS = [
|
|
1661
1659
|
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
@@ -2655,13 +2653,13 @@ async function startInteractiveShell(options) {
|
|
|
2655
2653
|
}
|
|
2656
2654
|
let readline;
|
|
2657
2655
|
const terminalInput = input;
|
|
2658
|
-
const foregroundRunner = (
|
|
2656
|
+
const foregroundRunner = (request) => withForegroundSignalHandoff(
|
|
2659
2657
|
{
|
|
2660
2658
|
input: terminalInput,
|
|
2661
2659
|
pauseReadline: () => readline?.pause(),
|
|
2662
2660
|
resumeReadline: () => readline?.resume()
|
|
2663
2661
|
},
|
|
2664
|
-
() => options.runner(
|
|
2662
|
+
() => options.runner(request)
|
|
2665
2663
|
);
|
|
2666
2664
|
const session = await createShellSession({
|
|
2667
2665
|
runner: foregroundRunner,
|
|
@@ -2975,10 +2973,10 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
2975
2973
|
`);
|
|
2976
2974
|
if (action.summary) this.options.io.write(`${sanitizeTerminalText2(action.summary)}
|
|
2977
2975
|
`);
|
|
2978
|
-
const
|
|
2976
|
+
const request = [action.method, action.path].filter(Boolean).join(" ");
|
|
2979
2977
|
const technical = {
|
|
2980
2978
|
operation: action.operation,
|
|
2981
|
-
...
|
|
2979
|
+
...request ? { request } : {},
|
|
2982
2980
|
arguments: action.arguments ?? null,
|
|
2983
2981
|
preview: action.preview ?? null
|
|
2984
2982
|
};
|
|
@@ -3361,59 +3359,6 @@ var ApiError = class extends Error {
|
|
|
3361
3359
|
this.name = "ApiError";
|
|
3362
3360
|
}
|
|
3363
3361
|
};
|
|
3364
|
-
function resolveKey(opts) {
|
|
3365
|
-
const key = getApiKey(opts);
|
|
3366
|
-
if (!key) {
|
|
3367
|
-
throw new ApiError(
|
|
3368
|
-
401,
|
|
3369
|
-
"NO_API_KEY",
|
|
3370
|
-
"No API key found. Run `tt auth login` or set TUNED_TENSOR_API_KEY."
|
|
3371
|
-
);
|
|
3372
|
-
}
|
|
3373
|
-
return key;
|
|
3374
|
-
}
|
|
3375
|
-
function buildUrl(path, query, opts) {
|
|
3376
|
-
const base = getBaseUrl(opts);
|
|
3377
|
-
const url = new URL(`/api/v1${path}`, base);
|
|
3378
|
-
if (query) {
|
|
3379
|
-
for (const [k, v] of Object.entries(query)) {
|
|
3380
|
-
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
3381
|
-
}
|
|
3382
|
-
}
|
|
3383
|
-
return url.toString();
|
|
3384
|
-
}
|
|
3385
|
-
async function request(method, path, options = {}) {
|
|
3386
|
-
const key = resolveKey(options.opts);
|
|
3387
|
-
const url = buildUrl(path, options.query, options.opts);
|
|
3388
|
-
const headers = {
|
|
3389
|
-
...options.headers,
|
|
3390
|
-
Authorization: `Bearer ${key}`
|
|
3391
|
-
};
|
|
3392
|
-
let bodyPayload;
|
|
3393
|
-
if (options.body) {
|
|
3394
|
-
headers["Content-Type"] = "application/json";
|
|
3395
|
-
bodyPayload = JSON.stringify(options.body);
|
|
3396
|
-
}
|
|
3397
|
-
const res = await fetch(url, { method, headers, body: bodyPayload });
|
|
3398
|
-
if (!res.ok) {
|
|
3399
|
-
let errBody;
|
|
3400
|
-
try {
|
|
3401
|
-
errBody = await res.json();
|
|
3402
|
-
} catch {
|
|
3403
|
-
throw new ApiError(res.status, "UNKNOWN", res.statusText);
|
|
3404
|
-
}
|
|
3405
|
-
throw new ApiError(
|
|
3406
|
-
res.status,
|
|
3407
|
-
errBody.error?.code || "UNKNOWN",
|
|
3408
|
-
errBody.error?.message || res.statusText
|
|
3409
|
-
);
|
|
3410
|
-
}
|
|
3411
|
-
if (res.status === 204) return { data: null };
|
|
3412
|
-
return await res.json();
|
|
3413
|
-
}
|
|
3414
|
-
function post(path, body, opts, headers) {
|
|
3415
|
-
return request("POST", path, { body, opts, headers });
|
|
3416
|
-
}
|
|
3417
3362
|
|
|
3418
3363
|
// src/output.ts
|
|
3419
3364
|
var labelStyle = chalk4.bold.hex("#A78BFA");
|
|
@@ -3530,7 +3475,9 @@ function rows(value) {
|
|
|
3530
3475
|
}
|
|
3531
3476
|
function displayId(value) {
|
|
3532
3477
|
const id = text(value);
|
|
3533
|
-
|
|
3478
|
+
if (!id) return "\u2014";
|
|
3479
|
+
if (id.startsWith("local-")) return `local-${shortId(id.slice("local-".length))}`;
|
|
3480
|
+
return shortId(id);
|
|
3534
3481
|
}
|
|
3535
3482
|
function displayNumber(value, digits = 4) {
|
|
3536
3483
|
const parsed = number(value);
|
|
@@ -4160,10 +4107,10 @@ function localCommandStreamsStdout(args2) {
|
|
|
4160
4107
|
const serves = args2[0] === "serve" || args2[0] === "models" && args2[1] === "serve";
|
|
4161
4108
|
return serves && !args2.includes("--print-command") && !args2.includes("--help") && !args2.includes("-h");
|
|
4162
4109
|
}
|
|
4163
|
-
function parseJsonOutput(
|
|
4164
|
-
if (!
|
|
4110
|
+
function parseJsonOutput(stdout) {
|
|
4111
|
+
if (!stdout.trim()) return { hasJson: false, json: void 0 };
|
|
4165
4112
|
try {
|
|
4166
|
-
return { hasJson: true, json: JSON.parse(
|
|
4113
|
+
return { hasJson: true, json: JSON.parse(stdout) };
|
|
4167
4114
|
} catch {
|
|
4168
4115
|
return { hasJson: false, json: void 0 };
|
|
4169
4116
|
}
|
|
@@ -4209,7 +4156,7 @@ async function spawnLocalCli(args2) {
|
|
|
4209
4156
|
}
|
|
4210
4157
|
);
|
|
4211
4158
|
if (pipeInput && args2.stdin && child.stdin) args2.stdin.pipe(child.stdin);
|
|
4212
|
-
let
|
|
4159
|
+
let stdout = "";
|
|
4213
4160
|
let stderr = "";
|
|
4214
4161
|
child.stdout?.setEncoding("utf8");
|
|
4215
4162
|
child.stderr?.setEncoding("utf8");
|
|
@@ -4219,7 +4166,7 @@ async function spawnLocalCli(args2) {
|
|
|
4219
4166
|
args2.pathReplacements
|
|
4220
4167
|
);
|
|
4221
4168
|
if (args2.streamingStdout) writeStream(output, normalized);
|
|
4222
|
-
else
|
|
4169
|
+
else stdout += normalized;
|
|
4223
4170
|
});
|
|
4224
4171
|
child.stderr?.on("data", (chunk) => {
|
|
4225
4172
|
const normalized = normalizeProjectedPaths(
|
|
@@ -4264,7 +4211,7 @@ async function spawnLocalCli(args2) {
|
|
|
4264
4211
|
resolveChild({
|
|
4265
4212
|
exitCode: spawnError ? 1 : code2 ?? signalExitCode(signal),
|
|
4266
4213
|
signal,
|
|
4267
|
-
stdout
|
|
4214
|
+
stdout,
|
|
4268
4215
|
stderr,
|
|
4269
4216
|
...spawnError ? { errorMessage: spawnError.message } : {}
|
|
4270
4217
|
});
|
|
@@ -4478,294 +4425,9 @@ function registerAgentCommands(parent, options) {
|
|
|
4478
4425
|
});
|
|
4479
4426
|
}
|
|
4480
4427
|
|
|
4481
|
-
// src/commands/auth.ts
|
|
4482
|
-
import { createInterface as createInterface2 } from "readline/promises";
|
|
4483
|
-
import { stdin, stdout } from "process";
|
|
4484
|
-
import { Writable } from "stream";
|
|
4485
|
-
import chalk6 from "chalk";
|
|
4486
|
-
async function promptForApiKey(input = stdin, output = stdout) {
|
|
4487
|
-
if (input.isTTY !== true || output.isTTY !== true) {
|
|
4488
|
-
throw new Error(
|
|
4489
|
-
"An API key is required in non-interactive mode. Pass it to `tt auth login <key>` or set TUNED_TENSOR_API_KEY."
|
|
4490
|
-
);
|
|
4491
|
-
}
|
|
4492
|
-
let muted = false;
|
|
4493
|
-
const maskedOutput = new Writable({
|
|
4494
|
-
write(chunk, _encoding, callback) {
|
|
4495
|
-
if (!muted) output.write(chunk);
|
|
4496
|
-
callback();
|
|
4497
|
-
}
|
|
4498
|
-
});
|
|
4499
|
-
const rl = createInterface2({
|
|
4500
|
-
input,
|
|
4501
|
-
output: maskedOutput,
|
|
4502
|
-
terminal: true
|
|
4503
|
-
});
|
|
4504
|
-
try {
|
|
4505
|
-
const pending = rl.question("Enter your API key (tt_...): ");
|
|
4506
|
-
muted = true;
|
|
4507
|
-
return await pending;
|
|
4508
|
-
} finally {
|
|
4509
|
-
muted = false;
|
|
4510
|
-
output.write("\n");
|
|
4511
|
-
rl.close();
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
function registerAuthCommands(parent) {
|
|
4515
|
-
const auth = parent.command("auth").description("Manage authentication");
|
|
4516
|
-
auth.command("login").description("Store an API key for authentication").argument("[key]", "API key (tt_...). If omitted, you will be prompted.").action(async (key) => {
|
|
4517
|
-
let apiKey = key;
|
|
4518
|
-
if (!apiKey) {
|
|
4519
|
-
if (isJsonMode()) {
|
|
4520
|
-
throw new Error(
|
|
4521
|
-
"An API key argument is required in JSON mode. Use `tt --json auth login <key>`."
|
|
4522
|
-
);
|
|
4523
|
-
}
|
|
4524
|
-
const settingsUrl = `${DEFAULT_BASE_URL}/dashboard/settings`;
|
|
4525
|
-
console.log();
|
|
4526
|
-
console.log(
|
|
4527
|
-
` To get your API key, go to ${chalk6.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
4528
|
-
);
|
|
4529
|
-
console.log(` ${chalk6.hex("#A78BFA").underline(settingsUrl)}`);
|
|
4530
|
-
console.log();
|
|
4531
|
-
apiKey = await promptForApiKey();
|
|
4532
|
-
}
|
|
4533
|
-
apiKey = apiKey.trim();
|
|
4534
|
-
if (!apiKey.startsWith("tt_") || apiKey.length !== 51) {
|
|
4535
|
-
throw new Error(
|
|
4536
|
-
"Invalid API key format. Keys start with tt_ and are 51 characters long."
|
|
4537
|
-
);
|
|
4538
|
-
}
|
|
4539
|
-
updateConfig({ api_key: apiKey });
|
|
4540
|
-
if (isJsonMode()) {
|
|
4541
|
-
printJson({
|
|
4542
|
-
authenticated: true,
|
|
4543
|
-
key_prefix: `${apiKey.slice(0, 8)}...`,
|
|
4544
|
-
base_url: getBaseUrl(parent.opts())
|
|
4545
|
-
});
|
|
4546
|
-
return;
|
|
4547
|
-
}
|
|
4548
|
-
printSuccess(
|
|
4549
|
-
`API key stored (${apiKey.slice(0, 8)}...). You're ready to go.`
|
|
4550
|
-
);
|
|
4551
|
-
});
|
|
4552
|
-
auth.command("logout").description("Remove stored credentials").action(() => {
|
|
4553
|
-
const current = readConfig();
|
|
4554
|
-
const { api_key: _removed, ...rest } = current;
|
|
4555
|
-
writeConfig(rest);
|
|
4556
|
-
if (isJsonMode()) {
|
|
4557
|
-
printJson({ authenticated: false });
|
|
4558
|
-
return;
|
|
4559
|
-
}
|
|
4560
|
-
printSuccess("Credentials removed.");
|
|
4561
|
-
});
|
|
4562
|
-
auth.command("status").description("Show current authentication status").action(() => {
|
|
4563
|
-
const opts = parent.opts();
|
|
4564
|
-
const apiKey = getApiKey(opts);
|
|
4565
|
-
const baseUrl = getBaseUrl(opts);
|
|
4566
|
-
if (isJsonMode()) {
|
|
4567
|
-
printJson({
|
|
4568
|
-
authenticated: !!apiKey,
|
|
4569
|
-
key_prefix: apiKey ? apiKey.slice(0, 8) + "..." : null,
|
|
4570
|
-
base_url: baseUrl
|
|
4571
|
-
});
|
|
4572
|
-
return;
|
|
4573
|
-
}
|
|
4574
|
-
if (apiKey) {
|
|
4575
|
-
printDetail([
|
|
4576
|
-
["Authenticated", "Yes"],
|
|
4577
|
-
["API Key", apiKey.slice(0, 8) + "..."],
|
|
4578
|
-
["Base URL", baseUrl]
|
|
4579
|
-
]);
|
|
4580
|
-
} else {
|
|
4581
|
-
printWarning(
|
|
4582
|
-
"Not authenticated. Run `tt auth login` to store an API key."
|
|
4583
|
-
);
|
|
4584
|
-
printDetail([["Base URL", baseUrl]]);
|
|
4585
|
-
}
|
|
4586
|
-
});
|
|
4587
|
-
}
|
|
4588
|
-
|
|
4589
|
-
// src/commands/publish.ts
|
|
4590
|
-
import { createInterface as createInterface3 } from "readline/promises";
|
|
4591
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
4592
|
-
import { homedir as homedir4 } from "os";
|
|
4593
|
-
import { isAbsolute as isAbsolute4, join as join7, resolve as resolve4 } from "path";
|
|
4594
|
-
import { stdin as defaultStdin, stdout as defaultStdout } from "process";
|
|
4595
|
-
var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
|
|
4596
|
-
function expandPath2(value, baseDirectory, homeDirectory) {
|
|
4597
|
-
if (value === "~") return homeDirectory;
|
|
4598
|
-
if (value.startsWith("~/")) return resolve4(homeDirectory, value.slice(2));
|
|
4599
|
-
return isAbsolute4(value) ? resolve4(value) : resolve4(baseDirectory, value);
|
|
4600
|
-
}
|
|
4601
|
-
function resolvePublishStoreRoot(options) {
|
|
4602
|
-
const cwd = options?.cwd ?? process.cwd();
|
|
4603
|
-
const env = options?.env ?? process.env;
|
|
4604
|
-
const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve4(env.HOME) : homedir4());
|
|
4605
|
-
const localConfigPath = join7(cwd, "local-runner.json");
|
|
4606
|
-
if (existsSync4(localConfigPath)) {
|
|
4607
|
-
try {
|
|
4608
|
-
const raw = JSON.parse(readFileSync4(localConfigPath, "utf-8"));
|
|
4609
|
-
if (typeof raw.storeRoot === "string" && raw.storeRoot.trim()) {
|
|
4610
|
-
return expandPath2(raw.storeRoot, cwd, homeDirectory);
|
|
4611
|
-
}
|
|
4612
|
-
} catch {
|
|
4613
|
-
}
|
|
4614
|
-
}
|
|
4615
|
-
if (env.TT_LOCAL_HOME && env.TT_LOCAL_HOME.trim()) {
|
|
4616
|
-
return expandPath2(env.TT_LOCAL_HOME, cwd, homeDirectory);
|
|
4617
|
-
}
|
|
4618
|
-
return defaultLocalHome();
|
|
4619
|
-
}
|
|
4620
|
-
async function confirmPublish(question, input = defaultStdin, output = defaultStdout) {
|
|
4621
|
-
if (input.isTTY !== true || output.isTTY !== true) {
|
|
4622
|
-
throw new Error(
|
|
4623
|
-
"Publishing requires confirmation in interactive mode. Re-run with --yes."
|
|
4624
|
-
);
|
|
4625
|
-
}
|
|
4626
|
-
const rl = createInterface3({ input, output, terminal: true });
|
|
4627
|
-
try {
|
|
4628
|
-
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
4629
|
-
return answer === "y" || answer === "yes";
|
|
4630
|
-
} finally {
|
|
4631
|
-
rl.close();
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
function pickPublishableRun(runs, runId) {
|
|
4635
|
-
if (runId) {
|
|
4636
|
-
const exact = runs.find((run) => run.id === runId);
|
|
4637
|
-
const prefixMatches = exact ? [exact] : runs.filter((run) => run.id.startsWith(runId));
|
|
4638
|
-
if (prefixMatches.length === 0) {
|
|
4639
|
-
throw new Error(`Run not found: ${runId}`);
|
|
4640
|
-
}
|
|
4641
|
-
if (prefixMatches.length > 1) {
|
|
4642
|
-
throw new Error(
|
|
4643
|
-
`Ambiguous run id prefix ${JSON.stringify(runId)}; matches ${prefixMatches.map((run) => shortId(run.id)).join(", ")}. Pass a longer prefix or the full id.`
|
|
4644
|
-
);
|
|
4645
|
-
}
|
|
4646
|
-
const match = prefixMatches[0];
|
|
4647
|
-
if (match.status !== "completed" && match.status !== "failed") {
|
|
4648
|
-
throw new Error(
|
|
4649
|
-
`Run ${shortId(match.id)} is still ${match.status}; only completed or failed runs can be published.`
|
|
4650
|
-
);
|
|
4651
|
-
}
|
|
4652
|
-
return match;
|
|
4653
|
-
}
|
|
4654
|
-
const candidates = runs.filter((run) => run.status === "completed" || run.status === "failed").sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
4655
|
-
const latest = candidates[0];
|
|
4656
|
-
if (!latest) {
|
|
4657
|
-
throw new Error(
|
|
4658
|
-
"No completed or failed local runs found. Run `tt run` first, then publish."
|
|
4659
|
-
);
|
|
4660
|
-
}
|
|
4661
|
-
return latest;
|
|
4662
|
-
}
|
|
4663
|
-
function assertPublishPayloadSize(payload) {
|
|
4664
|
-
const bytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
4665
|
-
if (bytes > MAX_PUBLISH_BYTES) {
|
|
4666
|
-
throw new Error(
|
|
4667
|
-
`Publish payload is ${bytes} bytes; maximum is ${MAX_PUBLISH_BYTES} bytes. Reduce eval examples or publish a smaller run.`
|
|
4668
|
-
);
|
|
4669
|
-
}
|
|
4670
|
-
}
|
|
4671
|
-
function registerPublishCommand(parent) {
|
|
4672
|
-
parent.command("publish").description("Publish local run evidence to the Tuned Tensor dashboard").argument("[runId]", "Local run id or prefix (defaults to latest completed/failed run)").option("-k, --api-key <key>", "API key (tt_...)").option("-u, --base-url <url>", "API base URL").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be published without uploading").action(async (runId, cmdOpts) => {
|
|
4673
|
-
const opts = {
|
|
4674
|
-
apiKey: cmdOpts.apiKey,
|
|
4675
|
-
baseUrl: cmdOpts.baseUrl
|
|
4676
|
-
};
|
|
4677
|
-
if (!cmdOpts.dryRun && !getApiKey(opts)) {
|
|
4678
|
-
throw new Error(
|
|
4679
|
-
"No API key found. Run `tt auth login` or set TUNED_TENSOR_API_KEY."
|
|
4680
|
-
);
|
|
4681
|
-
}
|
|
4682
|
-
const storeRoot = resolvePublishStoreRoot();
|
|
4683
|
-
const store = createLocalStore(storeRoot);
|
|
4684
|
-
const runs = await store.listRuns();
|
|
4685
|
-
const run = pickPublishableRun(runs, runId);
|
|
4686
|
-
const rawReport = await store.getRunReport(run.id);
|
|
4687
|
-
const report = runReportSchema.parse(rawReport);
|
|
4688
|
-
const specRecord = await store.getSpec(run.behavior_spec_id);
|
|
4689
|
-
const exampleCount = report.candidate.results.length || report.baseline.results.length;
|
|
4690
|
-
const payload = {
|
|
4691
|
-
spec: {
|
|
4692
|
-
id: specRecord.id,
|
|
4693
|
-
...specRecord.spec
|
|
4694
|
-
},
|
|
4695
|
-
report
|
|
4696
|
-
};
|
|
4697
|
-
assertPublishPayloadSize(payload);
|
|
4698
|
-
if (isJsonMode() && cmdOpts.dryRun) {
|
|
4699
|
-
printJson({
|
|
4700
|
-
dry_run: true,
|
|
4701
|
-
store_root: storeRoot,
|
|
4702
|
-
run_id: run.id,
|
|
4703
|
-
spec_id: specRecord.id,
|
|
4704
|
-
spec_name: specRecord.spec.name,
|
|
4705
|
-
run_number: run.run_number,
|
|
4706
|
-
status: run.status,
|
|
4707
|
-
example_count: exampleCount,
|
|
4708
|
-
payload
|
|
4709
|
-
});
|
|
4710
|
-
return;
|
|
4711
|
-
}
|
|
4712
|
-
if (!isJsonMode()) {
|
|
4713
|
-
printDetail([
|
|
4714
|
-
["Spec", specRecord.spec.name],
|
|
4715
|
-
["Run", `${shortId(run.id)} (#${run.run_number})`],
|
|
4716
|
-
["Status", run.status],
|
|
4717
|
-
["Examples", String(exampleCount)],
|
|
4718
|
-
["Store", storeRoot]
|
|
4719
|
-
]);
|
|
4720
|
-
printWarning(
|
|
4721
|
-
"Prompts and model outputs from the run report will be uploaded to your Tuned Tensor account."
|
|
4722
|
-
);
|
|
4723
|
-
}
|
|
4724
|
-
if (cmdOpts.dryRun) {
|
|
4725
|
-
if (isJsonMode()) {
|
|
4726
|
-
printJson({
|
|
4727
|
-
dry_run: true,
|
|
4728
|
-
run_id: run.id,
|
|
4729
|
-
spec_id: specRecord.id,
|
|
4730
|
-
example_count: exampleCount
|
|
4731
|
-
});
|
|
4732
|
-
return;
|
|
4733
|
-
}
|
|
4734
|
-
printSuccess("Dry run only \u2014 nothing was uploaded.");
|
|
4735
|
-
return;
|
|
4736
|
-
}
|
|
4737
|
-
if (!cmdOpts.yes && !isJsonMode()) {
|
|
4738
|
-
const ok = await confirmPublish("Publish this run evidence? [y/N] ");
|
|
4739
|
-
if (!ok) {
|
|
4740
|
-
printWarning("Publish cancelled.");
|
|
4741
|
-
return;
|
|
4742
|
-
}
|
|
4743
|
-
}
|
|
4744
|
-
if (!cmdOpts.yes && isJsonMode()) {
|
|
4745
|
-
throw new Error(
|
|
4746
|
-
"JSON mode requires --yes to publish without an interactive prompt."
|
|
4747
|
-
);
|
|
4748
|
-
}
|
|
4749
|
-
const { data: data3 } = await post("/publish/runs", payload, opts);
|
|
4750
|
-
const baseUrl = getBaseUrl(opts).replace(/\/$/, "");
|
|
4751
|
-
const dashboardUrl = `${baseUrl}/dashboard/runs/${data3.id}`;
|
|
4752
|
-
if (isJsonMode()) {
|
|
4753
|
-
printJson({ ...data3, dashboard_url: dashboardUrl });
|
|
4754
|
-
return;
|
|
4755
|
-
}
|
|
4756
|
-
printSuccess(
|
|
4757
|
-
`Published run #${data3.run_number} (${shortId(data3.id)}) for ${specRecord.spec.name}`
|
|
4758
|
-
);
|
|
4759
|
-
printDetail([
|
|
4760
|
-
["Dashboard", dashboardUrl],
|
|
4761
|
-
["Spec ID", shortId(data3.spec_id)]
|
|
4762
|
-
]);
|
|
4763
|
-
});
|
|
4764
|
-
}
|
|
4765
|
-
|
|
4766
4428
|
// src/commands/pipeline.ts
|
|
4767
|
-
import { existsSync as
|
|
4768
|
-
import { resolve as
|
|
4429
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
4430
|
+
import { resolve as resolve4 } from "path";
|
|
4769
4431
|
var DEFAULT_PIPELINE_FILE = "tunedtensor.pipeline.json";
|
|
4770
4432
|
function parseList(value) {
|
|
4771
4433
|
if (!value) return void 0;
|
|
@@ -4774,10 +4436,10 @@ function parseList(value) {
|
|
|
4774
4436
|
return ids;
|
|
4775
4437
|
}
|
|
4776
4438
|
function loadPipeline(path) {
|
|
4777
|
-
const resolved =
|
|
4778
|
-
if (!
|
|
4439
|
+
const resolved = resolve4(path);
|
|
4440
|
+
if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
|
|
4779
4441
|
try {
|
|
4780
|
-
return JSON.parse(
|
|
4442
|
+
return JSON.parse(readFileSync4(resolved, "utf8"));
|
|
4781
4443
|
} catch (error) {
|
|
4782
4444
|
throw new Error(`Pipeline file must be JSON: ${error.message}`);
|
|
4783
4445
|
}
|
|
@@ -4793,8 +4455,8 @@ function outputPlan(plan) {
|
|
|
4793
4455
|
function registerPipelineCommands(parent) {
|
|
4794
4456
|
const pipeline = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
|
|
4795
4457
|
pipeline.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Output file", DEFAULT_PIPELINE_FILE).action((options) => {
|
|
4796
|
-
const path =
|
|
4797
|
-
if (
|
|
4458
|
+
const path = resolve4(options.file);
|
|
4459
|
+
if (existsSync4(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
|
|
4798
4460
|
const recipe = canonicalPipeline("local");
|
|
4799
4461
|
writeFileSync3(path, `${JSON.stringify(recipe, null, 2)}
|
|
4800
4462
|
`);
|
|
@@ -4823,9 +4485,9 @@ function registerPipelineCommands(parent) {
|
|
|
4823
4485
|
if (remote) {
|
|
4824
4486
|
throw new Error(`Step "${remote.id}" targets cloud execution. This CLI is local-only; rewrite that step to local or use --dry-run.`);
|
|
4825
4487
|
}
|
|
4826
|
-
const input = await loadLocalRunInput(
|
|
4488
|
+
const input = await loadLocalRunInput(resolve4(options.spec));
|
|
4827
4489
|
assertLocalRunInputReady(input.request);
|
|
4828
|
-
const config = await loadLocalRunnerConfig(options.config ?
|
|
4490
|
+
const config = await loadLocalRunnerConfig(options.config ? resolve4(options.config) : void 0);
|
|
4829
4491
|
const localPipeline = {
|
|
4830
4492
|
version: 1,
|
|
4831
4493
|
...plan.name ? { name: plan.name } : {},
|
|
@@ -4888,7 +4550,7 @@ async function checkForCliUpdate(currentVersion, options = {}) {
|
|
|
4888
4550
|
const controller = new AbortController();
|
|
4889
4551
|
const timeoutMs = options.timeoutMs ?? 750;
|
|
4890
4552
|
let timeout;
|
|
4891
|
-
const
|
|
4553
|
+
const request = (async () => {
|
|
4892
4554
|
try {
|
|
4893
4555
|
const response = await (options.fetchImpl ?? fetch)(PACKAGE_LATEST_URL, {
|
|
4894
4556
|
headers: { accept: "application/json" },
|
|
@@ -4907,14 +4569,14 @@ async function checkForCliUpdate(currentVersion, options = {}) {
|
|
|
4907
4569
|
return null;
|
|
4908
4570
|
}
|
|
4909
4571
|
})();
|
|
4910
|
-
const deadline = new Promise((
|
|
4572
|
+
const deadline = new Promise((resolve5) => {
|
|
4911
4573
|
timeout = setTimeout(() => {
|
|
4912
4574
|
controller.abort();
|
|
4913
|
-
|
|
4575
|
+
resolve5(null);
|
|
4914
4576
|
}, timeoutMs);
|
|
4915
4577
|
});
|
|
4916
4578
|
try {
|
|
4917
|
-
return await Promise.race([
|
|
4579
|
+
return await Promise.race([request, deadline]);
|
|
4918
4580
|
} finally {
|
|
4919
4581
|
if (timeout) clearTimeout(timeout);
|
|
4920
4582
|
}
|
|
@@ -4958,7 +4620,7 @@ async function createDefaultAgentClient(runtime, env, workspaceRoot, getModelRun
|
|
|
4958
4620
|
const secret = getApiKey({ apiKey: env.TUNED_TENSOR_API_KEY });
|
|
4959
4621
|
const providerSecrets = Object.entries(env).filter(([name, value]) => value && /(?:API_KEY|TOKEN|SECRET)$/i.test(name)).map(([, value]) => value).filter((value) => value.length >= 8 && value !== secret);
|
|
4960
4622
|
return createLocalAgentClient({
|
|
4961
|
-
store: runtime.agentStore ?? new LocalAgentStore(
|
|
4623
|
+
store: runtime.agentStore ?? new LocalAgentStore(join7(getConfigDir(), "agent"), {
|
|
4962
4624
|
secretValues: [...secret ? [secret] : [], ...providerSecrets]
|
|
4963
4625
|
}),
|
|
4964
4626
|
workspaceRoot,
|
|
@@ -5007,7 +4669,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
5007
4669
|
if (!entrypoint) {
|
|
5008
4670
|
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
5009
4671
|
}
|
|
5010
|
-
return await new Promise((
|
|
4672
|
+
return await new Promise((resolve5, reject) => {
|
|
5011
4673
|
const child = spawn2(process.execPath, [entrypoint, ...args2], {
|
|
5012
4674
|
cwd: options.cwd,
|
|
5013
4675
|
env: options.env ?? process.env,
|
|
@@ -5043,7 +4705,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
5043
4705
|
child.once("close", (exitCode, signal) => {
|
|
5044
4706
|
cleanup();
|
|
5045
4707
|
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
5046
|
-
|
|
4708
|
+
resolve5({
|
|
5047
4709
|
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
5048
4710
|
signal
|
|
5049
4711
|
});
|
|
@@ -5064,10 +4726,10 @@ function createProgram(version, runtime = {}) {
|
|
|
5064
4726
|
const launchShell = runtime.startShell ?? startInteractiveShell;
|
|
5065
4727
|
const cwd = runtime.cwd ?? process.cwd();
|
|
5066
4728
|
const getModelRuntime = createModelRuntimeGetter(runtime);
|
|
5067
|
-
const shellRunner = async (
|
|
4729
|
+
const shellRunner = async (request) => {
|
|
5068
4730
|
const root = program.opts();
|
|
5069
|
-
return await invokeSelf(
|
|
5070
|
-
cwd:
|
|
4731
|
+
return await invokeSelf(request.args, {
|
|
4732
|
+
cwd: request.cwd,
|
|
5071
4733
|
env: childEnvironment2(root, env),
|
|
5072
4734
|
entrypoint: runtime.argv?.[1]
|
|
5073
4735
|
});
|
|
@@ -5104,7 +4766,7 @@ function createProgram(version, runtime = {}) {
|
|
|
5104
4766
|
if (root.json) setJsonMode(true);
|
|
5105
4767
|
if (root.color === false) {
|
|
5106
4768
|
process.env.FORCE_COLOR = "0";
|
|
5107
|
-
|
|
4769
|
+
chalk6.level = 0;
|
|
5108
4770
|
}
|
|
5109
4771
|
});
|
|
5110
4772
|
registerLocalCommands(program, {
|
|
@@ -5115,8 +4777,6 @@ function createProgram(version, runtime = {}) {
|
|
|
5115
4777
|
stdout: runtime.stdout ?? process.stdout,
|
|
5116
4778
|
stderr: runtime.stderr ?? process.stderr
|
|
5117
4779
|
});
|
|
5118
|
-
registerAuthCommands(program);
|
|
5119
|
-
registerPublishCommand(program);
|
|
5120
4780
|
registerPipelineCommands(program);
|
|
5121
4781
|
registerAgentCommands(program, {
|
|
5122
4782
|
env,
|
|
@@ -5135,12 +4795,12 @@ function createProgram(version, runtime = {}) {
|
|
|
5135
4795
|
`);
|
|
5136
4796
|
return;
|
|
5137
4797
|
}
|
|
5138
|
-
output.write(`${
|
|
4798
|
+
output.write(`${chalk6.bold.hex("#8B5CF6")("Tuned Tensor status")}
|
|
5139
4799
|
`);
|
|
5140
4800
|
output.write(`${formatShellStatus(context).join("\n")}
|
|
5141
4801
|
|
|
5142
4802
|
`);
|
|
5143
|
-
output.write(`${
|
|
4803
|
+
output.write(`${chalk6.bold("Context")}
|
|
5144
4804
|
`);
|
|
5145
4805
|
output.write(`${formatShellContext(context).join("\n")}
|
|
5146
4806
|
`);
|
|
@@ -5149,19 +4809,11 @@ function createProgram(version, runtime = {}) {
|
|
|
5149
4809
|
program.addHelpText(
|
|
5150
4810
|
"after",
|
|
5151
4811
|
`
|
|
5152
|
-
Commands:
|
|
5153
|
-
tt Open the conversational terminal (TTY only)
|
|
5154
|
-
tt status Inspect local project context
|
|
5155
|
-
tt doctor Check the host and run prerequisites
|
|
5156
|
-
tt run Fine-tune and evaluate on your GPU
|
|
5157
|
-
tt auth Store a dashboard API key
|
|
5158
|
-
tt publish Upload local run evidence to the dashboard
|
|
5159
|
-
|
|
5160
4812
|
Examples:
|
|
4813
|
+
tt Open the conversational terminal (TTY only)
|
|
5161
4814
|
tt doctor tunedtensor.json
|
|
5162
4815
|
tt run tunedtensor.json --dry-run
|
|
5163
4816
|
tt runs list
|
|
5164
|
-
tt publish --yes
|
|
5165
4817
|
`
|
|
5166
4818
|
);
|
|
5167
4819
|
return program;
|
|
@@ -5217,10 +4869,10 @@ async function runCli(version, runtime = {}) {
|
|
|
5217
4869
|
error
|
|
5218
4870
|
});
|
|
5219
4871
|
await (runtime.startShell ?? startInteractiveShell)({
|
|
5220
|
-
runner: async (
|
|
5221
|
-
|
|
4872
|
+
runner: async (request) => await invokeSelf(
|
|
4873
|
+
request.args,
|
|
5222
4874
|
{
|
|
5223
|
-
cwd:
|
|
4875
|
+
cwd: request.cwd,
|
|
5224
4876
|
env,
|
|
5225
4877
|
entrypoint: argv[1]
|
|
5226
4878
|
}
|
|
@@ -5241,7 +4893,7 @@ async function runCli(version, runtime = {}) {
|
|
|
5241
4893
|
}
|
|
5242
4894
|
|
|
5243
4895
|
// src/index.ts
|
|
5244
|
-
runCli("0.13.0
|
|
4896
|
+
runCli("0.13.0").catch((err) => {
|
|
5245
4897
|
reportError(err);
|
|
5246
4898
|
process.exit(1);
|
|
5247
4899
|
});
|