@tuned-tensor/cli 0.9.0 → 0.9.1
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 +34 -0
- package/dist/chunk-DAP2Z7EZ.js +4400 -0
- package/dist/chunk-DAP2Z7EZ.js.map +1 -0
- package/dist/index.js +324 -70
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +1630 -0
- package/dist/local-runtime.js.map +1 -0
- package/docs/local-runtime/architecture.md +91 -0
- package/docs/local-runtime/spark.md +107 -0
- package/examples/local-runtime/dry-runner.json +14 -0
- package/examples/local-runtime/general-regression.jsonl +4 -0
- package/examples/local-runtime/local-runner.json +25 -0
- package/examples/local-runtime/smoke-spec.json +41 -0
- package/package.json +15 -5
- package/training/local-runner/pyproject.toml +25 -0
- package/training/local-runner/src/evaluate.py +274 -0
- package/training/local-runner/src/model_contract.py +47 -0
- package/training/local-runner/src/prefetch.py +192 -0
- package/training/local-runner/src/serve.py +272 -0
- package/training/local-runner/src/sft_data.py +97 -0
- package/training/local-runner/src/train.py +289 -0
- package/training/local-runner/uv.lock +1204 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertLocalRunInputReady,
|
|
4
|
+
loadLocalRunInput,
|
|
5
|
+
loadLocalRunnerConfig,
|
|
6
|
+
runLocalPipeline
|
|
7
|
+
} from "./chunk-DAP2Z7EZ.js";
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -235,6 +241,135 @@ function canonicalizeSpecBaseModel(body) {
|
|
|
235
241
|
return body;
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
// src/pipeline.ts
|
|
245
|
+
var PIPELINE_VERSION = 1;
|
|
246
|
+
var PIPELINE_TARGETS = ["local", "cloud"];
|
|
247
|
+
var PIPELINE_USES = ["train", "evaluate", "compare"];
|
|
248
|
+
var OUTPUTS = {
|
|
249
|
+
train: ["model"],
|
|
250
|
+
evaluate: ["report"],
|
|
251
|
+
compare: ["comparison"]
|
|
252
|
+
};
|
|
253
|
+
function isRecord(value) {
|
|
254
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
255
|
+
}
|
|
256
|
+
function ref(value) {
|
|
257
|
+
return isRecord(value) && typeof value.from === "string" ? { from: value.from } : void 0;
|
|
258
|
+
}
|
|
259
|
+
function parseRef(value) {
|
|
260
|
+
const match = /^([A-Za-z][A-Za-z0-9_-]*)\.([A-Za-z][A-Za-z0-9_-]*)$/.exec(value.from);
|
|
261
|
+
return match ? { stepId: match[1], output: match[2] } : void 0;
|
|
262
|
+
}
|
|
263
|
+
function references(step) {
|
|
264
|
+
if (step.uses === "evaluate") return [{ field: "model", value: step.with?.model, expected: "model" }];
|
|
265
|
+
if (step.uses === "compare") return [
|
|
266
|
+
{ field: "before", value: step.with?.before, expected: "report" },
|
|
267
|
+
{ field: "after", value: step.with?.after, expected: "report" }
|
|
268
|
+
];
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
function resolvedTarget(step, pipeline2) {
|
|
272
|
+
return step.target ?? pipeline2.target;
|
|
273
|
+
}
|
|
274
|
+
function validatePipeline(pipeline2) {
|
|
275
|
+
const errors = [];
|
|
276
|
+
if (!isRecord(pipeline2)) return ["Pipeline must be an object."];
|
|
277
|
+
if (pipeline2.version !== PIPELINE_VERSION) errors.push("Pipeline version must be 1.");
|
|
278
|
+
if (pipeline2.target !== void 0 && !PIPELINE_TARGETS.includes(pipeline2.target)) {
|
|
279
|
+
errors.push("Pipeline target must be local or cloud.");
|
|
280
|
+
}
|
|
281
|
+
const typedPipeline = pipeline2;
|
|
282
|
+
if (!Array.isArray(pipeline2.steps) || pipeline2.steps.length === 0) return [...errors, "Pipeline must contain at least one step."];
|
|
283
|
+
const seen = /* @__PURE__ */ new Map();
|
|
284
|
+
let trainCount = 0;
|
|
285
|
+
for (const rawStep of pipeline2.steps) {
|
|
286
|
+
if (!isRecord(rawStep)) {
|
|
287
|
+
errors.push("Each pipeline step must be an object.");
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const step = rawStep;
|
|
291
|
+
if (typeof step.id !== "string" || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(step.id)) {
|
|
292
|
+
errors.push("Step id must start with a letter and contain only letters, numbers, _ or -.");
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (seen.has(step.id)) errors.push(`Duplicate step id: ${step.id}.`);
|
|
296
|
+
if (!PIPELINE_USES.includes(step.uses)) errors.push(`Step ${step.id} uses an unsupported component: ${String(step.uses)}.`);
|
|
297
|
+
if (step.uses === "train" && ++trainCount > 1) errors.push("Pipeline v1 supports at most one train step because local model artifacts are run-scoped.");
|
|
298
|
+
if (step.uses === "evaluate" && step.with?.evaluator !== void 0 && step.with.evaluator !== "behavior") {
|
|
299
|
+
errors.push(`Step ${step.id} evaluator must be behavior.`);
|
|
300
|
+
}
|
|
301
|
+
if (step.target !== void 0 && !PIPELINE_TARGETS.includes(step.target)) errors.push(`Step ${step.id} target must be local or cloud.`);
|
|
302
|
+
if (!resolvedTarget(step, typedPipeline)) errors.push(`Step ${step.id} needs a target or a pipeline default target.`);
|
|
303
|
+
for (const input of references(step)) {
|
|
304
|
+
if (step.uses === "evaluate" && input.value === "base") continue;
|
|
305
|
+
const source = ref(input.value);
|
|
306
|
+
if (!source) {
|
|
307
|
+
errors.push(`Step ${step.id} ${input.field} must be ${step.uses === "evaluate" ? "base or " : ""}a { from: "step.output" } reference.`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const parsed = parseRef(source);
|
|
311
|
+
if (!parsed) {
|
|
312
|
+
errors.push(`Step ${step.id} has an invalid reference: ${source.from}.`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const producer = seen.get(parsed.stepId);
|
|
316
|
+
if (!producer) {
|
|
317
|
+
const existsLater = pipeline2.steps.some((candidate) => isRecord(candidate) && candidate.id === parsed.stepId);
|
|
318
|
+
errors.push(`Step ${step.id} has a ${existsLater ? "forward" : "missing"} reference to ${parsed.stepId}.`);
|
|
319
|
+
} else if (!OUTPUTS[producer.uses]?.includes(parsed.output)) {
|
|
320
|
+
errors.push(`Step ${parsed.stepId} does not produce ${parsed.output} for step ${step.id}; it produces ${OUTPUTS[producer.uses]?.join(", ") ?? "no outputs"}.`);
|
|
321
|
+
} else if (parsed.output !== input.expected) {
|
|
322
|
+
errors.push(`Step ${step.id} ${input.field} requires a ${input.expected} reference, not ${source.from}.`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
seen.set(step.id, step);
|
|
326
|
+
}
|
|
327
|
+
return errors;
|
|
328
|
+
}
|
|
329
|
+
function canonicalPipeline(target = "local") {
|
|
330
|
+
return {
|
|
331
|
+
version: PIPELINE_VERSION,
|
|
332
|
+
name: `default-${target}`,
|
|
333
|
+
target,
|
|
334
|
+
steps: [
|
|
335
|
+
{ id: "baseline", uses: "evaluate", target, with: { model: "base", evaluator: "behavior" } },
|
|
336
|
+
{ id: "train", uses: "train", target },
|
|
337
|
+
{ id: "candidate", uses: "evaluate", target, with: { model: { from: "train.model" }, evaluator: "behavior" } },
|
|
338
|
+
{ id: "compare", uses: "compare", target, with: { before: { from: "baseline.report" }, after: { from: "candidate.report" } } }
|
|
339
|
+
]
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function createExecutionPlan(pipeline2, selection = {}) {
|
|
343
|
+
const errors = validatePipeline(pipeline2);
|
|
344
|
+
if (errors.length) throw new Error(`Invalid pipeline:
|
|
345
|
+
- ${errors.join("\n- ")}`);
|
|
346
|
+
const allIds = new Set(pipeline2.steps.map((step) => step.id));
|
|
347
|
+
for (const id of [...selection.only ?? [], ...selection.skip ?? []]) {
|
|
348
|
+
if (!allIds.has(id)) throw new Error(`Unknown pipeline step: ${id}.`);
|
|
349
|
+
}
|
|
350
|
+
const only = selection.only?.length ? new Set(selection.only) : void 0;
|
|
351
|
+
const skip = new Set(selection.skip ?? []);
|
|
352
|
+
const steps = pipeline2.steps.filter((step) => (!only || only.has(step.id)) && !skip.has(step.id));
|
|
353
|
+
if (!steps.length) throw new Error("Selection leaves no pipeline steps to run.");
|
|
354
|
+
const retained = new Map(steps.map((step) => [step.id, step]));
|
|
355
|
+
const resolved = steps.map((step) => {
|
|
356
|
+
const target = resolvedTarget(step, pipeline2);
|
|
357
|
+
const transfers = [];
|
|
358
|
+
for (const input of references(step)) {
|
|
359
|
+
const source = ref(input.value);
|
|
360
|
+
if (!source) continue;
|
|
361
|
+
const parsed = parseRef(source);
|
|
362
|
+
const producer = retained.get(parsed.stepId);
|
|
363
|
+
if (!producer) throw new Error(`Step ${step.id} has a dependency on ${parsed.stepId}, which was omitted by --only or --skip.`);
|
|
364
|
+
const fromTarget = resolvedTarget(producer, pipeline2);
|
|
365
|
+
if (fromTarget !== target) transfers.push({ from: source.from, from_target: fromTarget, to_target: target });
|
|
366
|
+
}
|
|
367
|
+
const normalized = step.uses === "evaluate" ? { ...step, with: { ...step.with, evaluator: step.with?.evaluator ?? "behavior" } } : step;
|
|
368
|
+
return { ...normalized, target, transfers };
|
|
369
|
+
});
|
|
370
|
+
return { version: PIPELINE_VERSION, ...pipeline2.name ? { name: pipeline2.name } : {}, steps: resolved };
|
|
371
|
+
}
|
|
372
|
+
|
|
238
373
|
// src/agent-tools.ts
|
|
239
374
|
var MAX_TOOL_OUTPUT = 32e3;
|
|
240
375
|
var MAX_PREPARED_ACTION = 24e3;
|
|
@@ -283,6 +418,17 @@ var RunBody = Type.Object({
|
|
|
283
418
|
var BaseModel = Type.Union(
|
|
284
419
|
SUPPORTED_BASE_MODELS.map((model) => Type.Literal(model))
|
|
285
420
|
);
|
|
421
|
+
var PipelineDocument = Type.Object({
|
|
422
|
+
version: Type.Literal(1),
|
|
423
|
+
name: Type.Optional(Type.String({ minLength: 1, maxLength: 255 })),
|
|
424
|
+
target: Type.Optional(Type.Union([Type.Literal("local"), Type.Literal("cloud")])),
|
|
425
|
+
steps: Type.Array(Type.Object({
|
|
426
|
+
id: Type.String({ minLength: 1, maxLength: 128 }),
|
|
427
|
+
uses: Type.Union([Type.Literal("train"), Type.Literal("evaluate"), Type.Literal("compare")]),
|
|
428
|
+
target: Type.Optional(Type.Union([Type.Literal("local"), Type.Literal("cloud")])),
|
|
429
|
+
with: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
430
|
+
}, { additionalProperties: false }), { minItems: 1, maxItems: 100 })
|
|
431
|
+
}, { additionalProperties: false });
|
|
286
432
|
var Example = Type.Object({
|
|
287
433
|
input: Type.String({ minLength: 1, maxLength: 1e5 }),
|
|
288
434
|
output: Type.String({ minLength: 1, maxLength: 1e5 })
|
|
@@ -460,7 +606,18 @@ function createTunedTensorTools(api) {
|
|
|
460
606
|
}, { additionalProperties: false }), async (p) => await api.get("/billing/transactions", {
|
|
461
607
|
page: p.page ?? 1,
|
|
462
608
|
per_page: p.per_page ?? 10
|
|
463
|
-
}))
|
|
609
|
+
})),
|
|
610
|
+
define("describe_pipeline", "Describe pipeline", "Describe the built-in v1 pipeline contract and canonical recipe. This never executes anything.", Type.Object({
|
|
611
|
+
target: Type.Optional(Type.Union([Type.Literal("local"), Type.Literal("cloud")]))
|
|
612
|
+
}, { additionalProperties: false }), async (p) => ({
|
|
613
|
+
version: 1,
|
|
614
|
+
components: ["train", "evaluate", "compare"],
|
|
615
|
+
canonical: canonicalPipeline(p.target ?? "local")
|
|
616
|
+
})),
|
|
617
|
+
define("validate_pipeline", "Validate pipeline", "Validate an ordered pipeline and resolve targets/transfers without filesystem, network, or execution side effects.", Type.Object({ pipeline: PipelineDocument }, { additionalProperties: false }), async (p) => {
|
|
618
|
+
const errors = validatePipeline(p.pipeline);
|
|
619
|
+
return { valid: errors.length === 0, errors, ...errors.length ? {} : { plan: createExecutionPlan(p.pipeline) } };
|
|
620
|
+
})
|
|
464
621
|
];
|
|
465
622
|
const mutations = [
|
|
466
623
|
define("prepare_create_spec", "Prepare create spec", "Prepare creating a spec. This never mutates; /approve is required.", Type.Object({
|
|
@@ -480,6 +637,19 @@ function createTunedTensorTools(api) {
|
|
|
480
637
|
changes: p.changes,
|
|
481
638
|
expected_spec_updated_at: current.updated_at
|
|
482
639
|
}));
|
|
640
|
+
}),
|
|
641
|
+
define("prepare_pipeline_run", "Prepare pipeline run", "Prepare a pipeline action for review only. It never executes a step, transfers an artifact, or reserves credits.", Type.Object({
|
|
642
|
+
pipeline: PipelineDocument,
|
|
643
|
+
dry_run: Type.Optional(Type.Boolean())
|
|
644
|
+
}, { additionalProperties: false }), async (p) => {
|
|
645
|
+
const errors = validatePipeline(p.pipeline);
|
|
646
|
+
if (errors.length) throw new Error(`Invalid pipeline:
|
|
647
|
+
- ${errors.join("\n- ")}`);
|
|
648
|
+
const plan = createExecutionPlan(p.pipeline);
|
|
649
|
+
return await api.propose(proposal("run_pipeline", "Run pipeline", "Review the resolved pipeline before any separate execution approval flow.", {
|
|
650
|
+
pipeline: p.pipeline,
|
|
651
|
+
dry_run: p.dry_run ?? true
|
|
652
|
+
}, { plan, execution: "not started" }));
|
|
483
653
|
})
|
|
484
654
|
];
|
|
485
655
|
return [...reads, ...mutations];
|
|
@@ -692,7 +862,7 @@ var DEFAULT_MAX_CLAIMS = 1e3;
|
|
|
692
862
|
function assertThreadId(id) {
|
|
693
863
|
if (!UUID.test(id)) throw new Error("Invalid local agent thread ID.");
|
|
694
864
|
}
|
|
695
|
-
function
|
|
865
|
+
function isRecord2(value) {
|
|
696
866
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
697
867
|
}
|
|
698
868
|
function scrub(value, secrets) {
|
|
@@ -703,7 +873,7 @@ function scrub(value, secrets) {
|
|
|
703
873
|
);
|
|
704
874
|
}
|
|
705
875
|
if (Array.isArray(value)) return value.map((entry) => scrub(entry, secrets));
|
|
706
|
-
if (
|
|
876
|
+
if (isRecord2(value)) {
|
|
707
877
|
return Object.fromEntries(
|
|
708
878
|
Object.entries(value).map(([key, entry]) => [key, scrub(entry, secrets)])
|
|
709
879
|
);
|
|
@@ -852,7 +1022,7 @@ var LocalAgentStore = class {
|
|
|
852
1022
|
throw new Error("Invalid local agent thread file.");
|
|
853
1023
|
}
|
|
854
1024
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
855
|
-
if (!
|
|
1025
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.thread) || parsed.thread.id !== id || !Array.isArray(parsed.messages) || !Array.isArray(parsed.actions)) {
|
|
856
1026
|
throw new Error("Local agent thread file is malformed.");
|
|
857
1027
|
}
|
|
858
1028
|
return parsed;
|
|
@@ -2218,7 +2388,7 @@ var StreamingTerminalMarkdown = class {
|
|
|
2218
2388
|
var accent3 = chalk3.hex("#8B5CF6");
|
|
2219
2389
|
var successMark2 = () => chalk3.green("\u2713");
|
|
2220
2390
|
var errorMark2 = () => chalk3.red("\u2717");
|
|
2221
|
-
function
|
|
2391
|
+
function isRecord3(value) {
|
|
2222
2392
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2223
2393
|
}
|
|
2224
2394
|
function stringValue(value) {
|
|
@@ -2259,7 +2429,7 @@ function formatThread(thread, activeId) {
|
|
|
2259
2429
|
return `${active} ${chalk3.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText2(thread.title)} ${chalk3.dim(dateLabel)}`;
|
|
2260
2430
|
}
|
|
2261
2431
|
function actionFromPayload(payload) {
|
|
2262
|
-
const value =
|
|
2432
|
+
const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
|
|
2263
2433
|
const id = stringValue(value.id);
|
|
2264
2434
|
const title = stringValue(value.title);
|
|
2265
2435
|
if (!id || !title) return null;
|
|
@@ -2392,7 +2562,7 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
2392
2562
|
if (event.type === "tool_call") {
|
|
2393
2563
|
this.flushResponse();
|
|
2394
2564
|
this.endOpenLine();
|
|
2395
|
-
const name = stringValue(payload.name) ?? stringValue(
|
|
2565
|
+
const name = stringValue(payload.name) ?? stringValue(isRecord3(payload.tool_call) ? payload.tool_call.name : null) ?? "tool";
|
|
2396
2566
|
this.options.io.write(chalk3.dim(` \u25CB ${name}
|
|
2397
2567
|
`));
|
|
2398
2568
|
return;
|
|
@@ -2646,8 +2816,9 @@ ${chalk3.yellow.bold("Approval required")} ${chalk3.dim(action.id.slice(0, 8))}
|
|
|
2646
2816
|
// src/local-runner.ts
|
|
2647
2817
|
import { constants as osConstants } from "os";
|
|
2648
2818
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2649
|
-
import { createRequire } from "module";
|
|
2650
2819
|
import { spawn } from "child_process";
|
|
2820
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2821
|
+
import { fileURLToPath } from "url";
|
|
2651
2822
|
import {
|
|
2652
2823
|
readFile as readFile3,
|
|
2653
2824
|
rm,
|
|
@@ -2820,7 +2991,7 @@ function shortId(id) {
|
|
|
2820
2991
|
}
|
|
2821
2992
|
|
|
2822
2993
|
// src/local-output.ts
|
|
2823
|
-
function
|
|
2994
|
+
function isRecord4(value) {
|
|
2824
2995
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2825
2996
|
}
|
|
2826
2997
|
function text(value) {
|
|
@@ -2834,10 +3005,10 @@ function number(value) {
|
|
|
2834
3005
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2835
3006
|
}
|
|
2836
3007
|
function record3(value) {
|
|
2837
|
-
return
|
|
3008
|
+
return isRecord4(value) ? value : void 0;
|
|
2838
3009
|
}
|
|
2839
3010
|
function rows(value) {
|
|
2840
|
-
return Array.isArray(value) ? value.filter(
|
|
3011
|
+
return Array.isArray(value) ? value.filter(isRecord4) : [];
|
|
2841
3012
|
}
|
|
2842
3013
|
function displayId(value) {
|
|
2843
3014
|
const id = text(value);
|
|
@@ -3125,19 +3296,19 @@ function renderServePlan(value) {
|
|
|
3125
3296
|
function renderKnownJson(args2, value) {
|
|
3126
3297
|
const command = args2[0];
|
|
3127
3298
|
const subcommand = args2[1];
|
|
3128
|
-
if (command === "doctor" &&
|
|
3299
|
+
if (command === "doctor" && isRecord4(value)) {
|
|
3129
3300
|
renderDoctor(value);
|
|
3130
3301
|
return true;
|
|
3131
3302
|
}
|
|
3132
|
-
if (command === "init" &&
|
|
3303
|
+
if (command === "init" && isRecord4(value)) {
|
|
3133
3304
|
renderInit(value);
|
|
3134
3305
|
return true;
|
|
3135
3306
|
}
|
|
3136
|
-
if (command === "validate" &&
|
|
3307
|
+
if (command === "validate" && isRecord4(value)) {
|
|
3137
3308
|
renderValidate(value);
|
|
3138
3309
|
return true;
|
|
3139
3310
|
}
|
|
3140
|
-
if (command === "run" &&
|
|
3311
|
+
if (command === "run" && isRecord4(value)) {
|
|
3141
3312
|
renderRun(value);
|
|
3142
3313
|
return true;
|
|
3143
3314
|
}
|
|
@@ -3150,15 +3321,15 @@ function renderKnownJson(args2, value) {
|
|
|
3150
3321
|
renderRunEvents(value);
|
|
3151
3322
|
return true;
|
|
3152
3323
|
}
|
|
3153
|
-
if (subcommand === "get" &&
|
|
3324
|
+
if (subcommand === "get" && isRecord4(value)) {
|
|
3154
3325
|
renderRunRecord(value);
|
|
3155
3326
|
return true;
|
|
3156
3327
|
}
|
|
3157
|
-
if (subcommand === "report" &&
|
|
3328
|
+
if (subcommand === "report" && isRecord4(value)) {
|
|
3158
3329
|
renderRunReport(value);
|
|
3159
3330
|
return true;
|
|
3160
3331
|
}
|
|
3161
|
-
if (subcommand === "compare" &&
|
|
3332
|
+
if (subcommand === "compare" && isRecord4(value)) {
|
|
3162
3333
|
renderRunComparison(value);
|
|
3163
3334
|
return true;
|
|
3164
3335
|
}
|
|
@@ -3168,25 +3339,25 @@ function renderKnownJson(args2, value) {
|
|
|
3168
3339
|
renderModelsList(value);
|
|
3169
3340
|
return true;
|
|
3170
3341
|
}
|
|
3171
|
-
if ((subcommand === "get" || subcommand === "verify") &&
|
|
3342
|
+
if ((subcommand === "get" || subcommand === "verify") && isRecord4(value)) {
|
|
3172
3343
|
if (subcommand === "verify") printSuccess("Local model artifact is valid");
|
|
3173
3344
|
renderModelRecord(value);
|
|
3174
3345
|
return true;
|
|
3175
3346
|
}
|
|
3176
|
-
if ((subcommand === "prefetch" || subcommand === "verify-base") &&
|
|
3347
|
+
if ((subcommand === "prefetch" || subcommand === "verify-base") && isRecord4(value)) {
|
|
3177
3348
|
renderModelPrefetch(value);
|
|
3178
3349
|
return true;
|
|
3179
3350
|
}
|
|
3180
|
-
if (["active", "activate", "rollback"].includes(subcommand ?? "") &&
|
|
3351
|
+
if (["active", "activate", "rollback"].includes(subcommand ?? "") && isRecord4(value)) {
|
|
3181
3352
|
renderActiveModel(value);
|
|
3182
3353
|
return true;
|
|
3183
3354
|
}
|
|
3184
|
-
if (subcommand === "serve" &&
|
|
3355
|
+
if (subcommand === "serve" && isRecord4(value)) {
|
|
3185
3356
|
renderServePlan(value);
|
|
3186
3357
|
return true;
|
|
3187
3358
|
}
|
|
3188
3359
|
}
|
|
3189
|
-
if (command === "serve" &&
|
|
3360
|
+
if (command === "serve" && isRecord4(value)) {
|
|
3190
3361
|
renderServePlan(value);
|
|
3191
3362
|
return true;
|
|
3192
3363
|
}
|
|
@@ -3274,7 +3445,6 @@ function renderLocalOutput(payload, options = {}) {
|
|
|
3274
3445
|
}
|
|
3275
3446
|
|
|
3276
3447
|
// src/local-runner.ts
|
|
3277
|
-
var localRequire = createRequire(import.meta.url);
|
|
3278
3448
|
var DEFAULT_SPEC_NAME = "tunedtensor.json";
|
|
3279
3449
|
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
3280
3450
|
"--config",
|
|
@@ -3460,14 +3630,13 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
3460
3630
|
};
|
|
3461
3631
|
}
|
|
3462
3632
|
function resolveLocalCliEntrypoint() {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
} catch (error) {
|
|
3633
|
+
const entrypoint = fileURLToPath(new URL("./local-runtime.js", import.meta.url));
|
|
3634
|
+
if (!existsSync2(entrypoint)) {
|
|
3466
3635
|
throw new Error(
|
|
3467
|
-
"
|
|
3468
|
-
{ cause: error }
|
|
3636
|
+
"The bundled local runtime is missing. Rebuild or reinstall @tuned-tensor/cli."
|
|
3469
3637
|
);
|
|
3470
3638
|
}
|
|
3639
|
+
return entrypoint;
|
|
3471
3640
|
}
|
|
3472
3641
|
function localCommandStreamsStdout(args2) {
|
|
3473
3642
|
const serves = args2[0] === "serve" || args2[0] === "models" && args2[1] === "serve";
|
|
@@ -4527,7 +4696,7 @@ Eval Results (${data3._evals.length}):`);
|
|
|
4527
4696
|
}
|
|
4528
4697
|
|
|
4529
4698
|
// src/commands/datasets.ts
|
|
4530
|
-
import { existsSync as
|
|
4699
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
4531
4700
|
var DOCUMENT_OCR_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
4532
4701
|
function formatBytes(bytes) {
|
|
4533
4702
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -4783,7 +4952,7 @@ function registerDatasetsCommands(parent) {
|
|
|
4783
4952
|
});
|
|
4784
4953
|
datasets.command("upload").description("Upload a JSONL dataset file").argument("<file>", "Path to JSONL file").option("-n, --name <name>", "Dataset name (defaults to filename)").option("-d, --description <desc>", "Dataset description").option("--format <format>", "Dataset format: jsonl or document_ocr_jsonl").action(async (file, cmdOpts) => {
|
|
4785
4954
|
const opts = parent.opts();
|
|
4786
|
-
if (!
|
|
4955
|
+
if (!existsSync3(file)) {
|
|
4787
4956
|
printError(`File not found: ${file}`);
|
|
4788
4957
|
process.exit(1);
|
|
4789
4958
|
}
|
|
@@ -4834,12 +5003,12 @@ function registerDatasetsCommands(parent) {
|
|
|
4834
5003
|
}
|
|
4835
5004
|
|
|
4836
5005
|
// src/commands/label.ts
|
|
4837
|
-
import { existsSync as
|
|
5006
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
4838
5007
|
function formatCents2(cents) {
|
|
4839
5008
|
return `$${(cents / 100).toFixed(2)}`;
|
|
4840
5009
|
}
|
|
4841
5010
|
function sleep(ms) {
|
|
4842
|
-
return new Promise((
|
|
5011
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
4843
5012
|
}
|
|
4844
5013
|
function parseCsvHeader(line) {
|
|
4845
5014
|
const fields = [];
|
|
@@ -5006,7 +5175,7 @@ function registerLabelCommands(parent) {
|
|
|
5006
5175
|
const label = parent.command("label").description("Teacher-label real data into training datasets");
|
|
5007
5176
|
label.command("upload").description("Upload unlabeled inputs (.jsonl or .csv) and start a labeling job").argument("<file>", 'Path to JSONL ({"input": ...} per line) or CSV file').requiredOption("-s, --spec <id>", "Behaviour spec the teacher labels under").option("-c, --input-column <name>", "CSV column that holds the input text").option("-n, --name <name>", "Job name (defaults to filename)").option("--watch", "Block and show progress until labeling finishes").action(async (file, cmdOpts) => {
|
|
5008
5177
|
const opts = parent.opts();
|
|
5009
|
-
if (!
|
|
5178
|
+
if (!existsSync4(file)) {
|
|
5010
5179
|
printError(`File not found: ${file}`);
|
|
5011
5180
|
process.exit(1);
|
|
5012
5181
|
}
|
|
@@ -5235,7 +5404,7 @@ function registerLabelCommands(parent) {
|
|
|
5235
5404
|
import { spawn as spawn3, execFileSync } from "child_process";
|
|
5236
5405
|
import { createHash } from "crypto";
|
|
5237
5406
|
import {
|
|
5238
|
-
existsSync as
|
|
5407
|
+
existsSync as existsSync6,
|
|
5239
5408
|
mkdirSync as mkdirSync3,
|
|
5240
5409
|
statSync as statSync4,
|
|
5241
5410
|
createWriteStream,
|
|
@@ -5251,7 +5420,7 @@ import { Readable, Transform } from "stream";
|
|
|
5251
5420
|
import { pipeline } from "stream/promises";
|
|
5252
5421
|
|
|
5253
5422
|
// src/commands/init.ts
|
|
5254
|
-
import { existsSync as
|
|
5423
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
5255
5424
|
import { resolve as resolve4 } from "path";
|
|
5256
5425
|
var DEFAULT_SPEC_FILE = "tunedtensor.json";
|
|
5257
5426
|
var SCAFFOLD = {
|
|
@@ -5272,7 +5441,7 @@ var SCAFFOLD = {
|
|
|
5272
5441
|
function registerInitCommand(parent) {
|
|
5273
5442
|
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) => {
|
|
5274
5443
|
const filePath = resolve4(cmdOpts.file);
|
|
5275
|
-
if (
|
|
5444
|
+
if (existsSync5(filePath)) {
|
|
5276
5445
|
if (isJsonMode()) {
|
|
5277
5446
|
printJson({ created: false, path: filePath });
|
|
5278
5447
|
return;
|
|
@@ -5301,7 +5470,7 @@ function registerInitCommand(parent) {
|
|
|
5301
5470
|
}
|
|
5302
5471
|
function loadSpec(filePath) {
|
|
5303
5472
|
const resolved = resolve4(filePath || DEFAULT_SPEC_FILE);
|
|
5304
|
-
if (!
|
|
5473
|
+
if (!existsSync5(resolved)) {
|
|
5305
5474
|
printError(
|
|
5306
5475
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE}
|
|
5307
5476
|
Run \`tt init\` to create one.`
|
|
@@ -5324,13 +5493,13 @@ function parsePositiveInteger(value, fallback) {
|
|
|
5324
5493
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
5325
5494
|
}
|
|
5326
5495
|
function sleep2(ms) {
|
|
5327
|
-
return new Promise((
|
|
5496
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
5328
5497
|
}
|
|
5329
5498
|
function readRequestBody(req) {
|
|
5330
|
-
return new Promise((
|
|
5499
|
+
return new Promise((resolve8, reject) => {
|
|
5331
5500
|
const chunks = [];
|
|
5332
5501
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
5333
|
-
req.on("end", () =>
|
|
5502
|
+
req.on("end", () => resolve8(Buffer.concat(chunks)));
|
|
5334
5503
|
req.on("error", reject);
|
|
5335
5504
|
});
|
|
5336
5505
|
}
|
|
@@ -5398,7 +5567,7 @@ function errorSummary(response, fallback) {
|
|
|
5398
5567
|
return typeof message === "string" ? message : fallback;
|
|
5399
5568
|
}
|
|
5400
5569
|
async function allocatePort(host) {
|
|
5401
|
-
return new Promise((
|
|
5570
|
+
return new Promise((resolve8, reject) => {
|
|
5402
5571
|
const server = createNetServer();
|
|
5403
5572
|
server.on("error", reject);
|
|
5404
5573
|
server.listen(0, host, () => {
|
|
@@ -5408,7 +5577,7 @@ async function allocatePort(host) {
|
|
|
5408
5577
|
return;
|
|
5409
5578
|
}
|
|
5410
5579
|
const port = address.port;
|
|
5411
|
-
server.close(() =>
|
|
5580
|
+
server.close(() => resolve8(port));
|
|
5412
5581
|
});
|
|
5413
5582
|
});
|
|
5414
5583
|
}
|
|
@@ -5448,11 +5617,11 @@ var ManagedModelServer = class {
|
|
|
5448
5617
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
5449
5618
|
});
|
|
5450
5619
|
});
|
|
5451
|
-
await new Promise((
|
|
5620
|
+
await new Promise((resolve8, reject) => {
|
|
5452
5621
|
this.server?.once("error", reject);
|
|
5453
5622
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
5454
5623
|
this.server?.off("error", reject);
|
|
5455
|
-
|
|
5624
|
+
resolve8();
|
|
5456
5625
|
});
|
|
5457
5626
|
});
|
|
5458
5627
|
}
|
|
@@ -5462,15 +5631,15 @@ var ManagedModelServer = class {
|
|
|
5462
5631
|
if (!this.server) return;
|
|
5463
5632
|
const server = this.server;
|
|
5464
5633
|
this.server = null;
|
|
5465
|
-
await new Promise((
|
|
5466
|
-
server.close((err) => err ? reject(err) :
|
|
5634
|
+
await new Promise((resolve8, reject) => {
|
|
5635
|
+
server.close((err) => err ? reject(err) : resolve8());
|
|
5467
5636
|
});
|
|
5468
5637
|
}
|
|
5469
5638
|
async run() {
|
|
5470
5639
|
await this.start();
|
|
5471
|
-
await new Promise((
|
|
5640
|
+
await new Promise((resolve8, reject) => {
|
|
5472
5641
|
this.server?.once("error", reject);
|
|
5473
|
-
this.server?.once("close",
|
|
5642
|
+
this.server?.once("close", resolve8);
|
|
5474
5643
|
});
|
|
5475
5644
|
}
|
|
5476
5645
|
async handle(req, res) {
|
|
@@ -5644,7 +5813,7 @@ var ManagedModelServer = class {
|
|
|
5644
5813
|
try {
|
|
5645
5814
|
child.kill("SIGTERM");
|
|
5646
5815
|
await Promise.race([
|
|
5647
|
-
new Promise((
|
|
5816
|
+
new Promise((resolve8) => child.once("exit", () => resolve8())),
|
|
5648
5817
|
sleep2(5e3).then(() => {
|
|
5649
5818
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
5650
5819
|
})
|
|
@@ -6269,7 +6438,7 @@ if __name__ == "__main__":
|
|
|
6269
6438
|
`;
|
|
6270
6439
|
function resolveOutputPath(output, filename) {
|
|
6271
6440
|
if (!output) return filename;
|
|
6272
|
-
if (
|
|
6441
|
+
if (existsSync6(output) && statSync4(output).isDirectory()) {
|
|
6273
6442
|
return join6(output, filename);
|
|
6274
6443
|
}
|
|
6275
6444
|
return output;
|
|
@@ -6390,7 +6559,7 @@ function runtimePythonPath(cacheDir) {
|
|
|
6390
6559
|
function resolveServePython(cacheDir, explicitPython) {
|
|
6391
6560
|
if (explicitPython) return explicitPython;
|
|
6392
6561
|
const managedPython = runtimePythonPath(cacheDir);
|
|
6393
|
-
return
|
|
6562
|
+
return existsSync6(managedPython) ? managedPython : "python3";
|
|
6394
6563
|
}
|
|
6395
6564
|
function commandExists(command) {
|
|
6396
6565
|
try {
|
|
@@ -6469,7 +6638,7 @@ function safeSegment(value) {
|
|
|
6469
6638
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "model";
|
|
6470
6639
|
}
|
|
6471
6640
|
function directoryHasFiles(path) {
|
|
6472
|
-
return
|
|
6641
|
+
return existsSync6(path) && statSync4(path).isDirectory() && readdirSync(path).length > 0;
|
|
6473
6642
|
}
|
|
6474
6643
|
function validateArchiveEntryPath(entry) {
|
|
6475
6644
|
const normalized = normalize(entry).replace(/^[\\/]+/, "");
|
|
@@ -6490,7 +6659,7 @@ function validateArchiveEntries(archivePath) {
|
|
|
6490
6659
|
function extractArchive(archivePath, targetDir, force) {
|
|
6491
6660
|
if (directoryHasFiles(targetDir) && !force) return targetDir;
|
|
6492
6661
|
validateArchiveEntries(archivePath);
|
|
6493
|
-
if (
|
|
6662
|
+
if (existsSync6(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
6494
6663
|
mkdirSync3(targetDir, { recursive: true });
|
|
6495
6664
|
execFileSync("tar", ["-xzf", archivePath, "-C", targetDir], { stdio: "inherit" });
|
|
6496
6665
|
return targetDir;
|
|
@@ -6517,7 +6686,7 @@ function findSpecPath(explicitPath, modelPath) {
|
|
|
6517
6686
|
const candidates = explicitPath ? [explicitPath] : [join6(process.cwd(), DEFAULT_SPEC_FILE), join6(modelPath, DEFAULT_SPEC_FILE)];
|
|
6518
6687
|
for (const candidate of candidates) {
|
|
6519
6688
|
const resolved = resolve5(candidate);
|
|
6520
|
-
if (
|
|
6689
|
+
if (existsSync6(resolved) && statSync4(resolved).isFile()) return resolved;
|
|
6521
6690
|
}
|
|
6522
6691
|
return null;
|
|
6523
6692
|
}
|
|
@@ -6527,7 +6696,7 @@ function loadSystemPromptFromSpec(specPath) {
|
|
|
6527
6696
|
}
|
|
6528
6697
|
function loadJsonSchemaForServe(schemaPath) {
|
|
6529
6698
|
const resolved = resolve5(schemaPath);
|
|
6530
|
-
if (!
|
|
6699
|
+
if (!existsSync6(resolved) || !statSync4(resolved).isFile()) {
|
|
6531
6700
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
6532
6701
|
}
|
|
6533
6702
|
try {
|
|
@@ -6545,7 +6714,7 @@ function writeReferenceServerScript(cacheDir) {
|
|
|
6545
6714
|
return scriptPath;
|
|
6546
6715
|
}
|
|
6547
6716
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
6548
|
-
if (
|
|
6717
|
+
if (existsSync6(target)) {
|
|
6549
6718
|
const resolved = resolve5(target);
|
|
6550
6719
|
const stats = statSync4(resolved);
|
|
6551
6720
|
if (stats.isDirectory()) {
|
|
@@ -6571,7 +6740,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
6571
6740
|
const modelCacheDir = join6(cacheDir, fullId);
|
|
6572
6741
|
const archivePath = join6(modelCacheDir, data3.filename);
|
|
6573
6742
|
const extractDir = join6(modelCacheDir, "model");
|
|
6574
|
-
if (!
|
|
6743
|
+
if (!existsSync6(archivePath) || forceDownload) {
|
|
6575
6744
|
await downloadUrlToFile(data3.url, archivePath);
|
|
6576
6745
|
}
|
|
6577
6746
|
extractArchive(archivePath, extractDir, forceDownload);
|
|
@@ -6675,7 +6844,7 @@ function ollamaSlug(name) {
|
|
|
6675
6844
|
}
|
|
6676
6845
|
function firstExisting(candidates) {
|
|
6677
6846
|
for (const candidate of candidates) {
|
|
6678
|
-
if (candidate &&
|
|
6847
|
+
if (candidate && existsSync6(candidate) && statSync4(candidate).isFile()) {
|
|
6679
6848
|
return resolve5(candidate);
|
|
6680
6849
|
}
|
|
6681
6850
|
}
|
|
@@ -6687,7 +6856,7 @@ function llamaCppDir(explicit) {
|
|
|
6687
6856
|
function resolveConvertScript(opts, required) {
|
|
6688
6857
|
if (opts.convertScript) {
|
|
6689
6858
|
const resolved = resolve5(opts.convertScript);
|
|
6690
|
-
if (required && !
|
|
6859
|
+
if (required && !existsSync6(resolved)) {
|
|
6691
6860
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
6692
6861
|
}
|
|
6693
6862
|
return resolved;
|
|
@@ -6706,7 +6875,7 @@ function resolveConvertScript(opts, required) {
|
|
|
6706
6875
|
function resolveQuantizeBin(opts, required) {
|
|
6707
6876
|
if (opts.quantizeBin) {
|
|
6708
6877
|
const resolved = resolve5(opts.quantizeBin);
|
|
6709
|
-
if (required && !
|
|
6878
|
+
if (required && !existsSync6(resolved)) {
|
|
6710
6879
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
6711
6880
|
}
|
|
6712
6881
|
return resolved;
|
|
@@ -6841,7 +7010,7 @@ function registerModelsCommands(parent) {
|
|
|
6841
7010
|
opts
|
|
6842
7011
|
);
|
|
6843
7012
|
const outputPath = resolveOutputPath(cmdOpts.output, data3.filename);
|
|
6844
|
-
if (
|
|
7013
|
+
if (existsSync6(outputPath) && !cmdOpts.force) {
|
|
6845
7014
|
throw new Error(`Output file already exists: ${outputPath}. Use --force to overwrite.`);
|
|
6846
7015
|
}
|
|
6847
7016
|
const bytes = await downloadUrlToFile(data3.url, outputPath);
|
|
@@ -6950,7 +7119,7 @@ function registerModelsCommands(parent) {
|
|
|
6950
7119
|
ollama: ollamaInfo
|
|
6951
7120
|
};
|
|
6952
7121
|
if (printOnly) return printExportPlan(plan);
|
|
6953
|
-
if (
|
|
7122
|
+
if (existsSync6(finalPath) && !cmdOpts.force) {
|
|
6954
7123
|
throw new Error(`Output already exists: ${finalPath}. Use --force to overwrite.`);
|
|
6955
7124
|
}
|
|
6956
7125
|
mkdirSync3(outputDir, { recursive: true });
|
|
@@ -6971,7 +7140,7 @@ function registerModelsCommands(parent) {
|
|
|
6971
7140
|
finalPath,
|
|
6972
7141
|
quantPlan.quantizeType
|
|
6973
7142
|
]);
|
|
6974
|
-
if (!cmdOpts.keepIntermediate && intermediatePath &&
|
|
7143
|
+
if (!cmdOpts.keepIntermediate && intermediatePath && existsSync6(intermediatePath)) {
|
|
6975
7144
|
rmSync(intermediatePath, { force: true });
|
|
6976
7145
|
}
|
|
6977
7146
|
}
|
|
@@ -7024,10 +7193,10 @@ function registerModelsCommands(parent) {
|
|
|
7024
7193
|
]);
|
|
7025
7194
|
return;
|
|
7026
7195
|
}
|
|
7027
|
-
if (
|
|
7196
|
+
if (existsSync6(venvDir) && cmdOpts.force) {
|
|
7028
7197
|
rmSync(venvDir, { recursive: true, force: true });
|
|
7029
7198
|
}
|
|
7030
|
-
if (!
|
|
7199
|
+
if (!existsSync6(venvPython)) {
|
|
7031
7200
|
mkdirSync3(cacheDir, { recursive: true });
|
|
7032
7201
|
execFileSync(commands[0][0], commands[0].slice(1), { stdio: "inherit" });
|
|
7033
7202
|
}
|
|
@@ -7639,6 +7808,81 @@ function registerAgentCommands(parent, options) {
|
|
|
7639
7808
|
});
|
|
7640
7809
|
}
|
|
7641
7810
|
|
|
7811
|
+
// src/commands/pipeline.ts
|
|
7812
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
7813
|
+
import { resolve as resolve7 } from "path";
|
|
7814
|
+
var DEFAULT_PIPELINE_FILE = "tunedtensor.pipeline.json";
|
|
7815
|
+
function parseList(value) {
|
|
7816
|
+
if (!value) return void 0;
|
|
7817
|
+
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
7818
|
+
if (!ids.length) throw new Error("Step selection must name at least one step.");
|
|
7819
|
+
return ids;
|
|
7820
|
+
}
|
|
7821
|
+
function loadPipeline(path) {
|
|
7822
|
+
const resolved = resolve7(path);
|
|
7823
|
+
if (!existsSync7(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
|
|
7824
|
+
try {
|
|
7825
|
+
return JSON.parse(readFileSync9(resolved, "utf8"));
|
|
7826
|
+
} catch (error) {
|
|
7827
|
+
throw new Error(`Pipeline file must be JSON: ${error.message}`);
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
function outputPlan(plan) {
|
|
7831
|
+
if (isJsonMode()) return printJson(plan);
|
|
7832
|
+
const typed = plan;
|
|
7833
|
+
for (const step of typed.steps) {
|
|
7834
|
+
console.log(`${step.id.padEnd(16)} ${step.uses.padEnd(10)} ${step.target}`);
|
|
7835
|
+
for (const transfer of step.transfers) console.log(` transfer ${transfer.from}: ${transfer.from_target} -> ${transfer.to_target}`);
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
function registerPipelineCommands(parent) {
|
|
7839
|
+
const pipeline2 = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
|
|
7840
|
+
pipeline2.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Output file", DEFAULT_PIPELINE_FILE).option("--target <target>", "Default target: local or cloud", "local").action((options) => {
|
|
7841
|
+
if (!PIPELINE_TARGETS.includes(options.target)) throw new Error("--target must be local or cloud.");
|
|
7842
|
+
const path = resolve7(options.file);
|
|
7843
|
+
if (existsSync7(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
|
|
7844
|
+
const recipe = canonicalPipeline(options.target);
|
|
7845
|
+
writeFileSync5(path, `${JSON.stringify(recipe, null, 2)}
|
|
7846
|
+
`);
|
|
7847
|
+
if (isJsonMode()) return printJson({ created: true, path, pipeline: recipe });
|
|
7848
|
+
printSuccess(`Created ${options.file}`);
|
|
7849
|
+
});
|
|
7850
|
+
pipeline2.command("validate").description("Validate a pipeline without any execution or transfer").option("-f, --file <path>", "Pipeline file", DEFAULT_PIPELINE_FILE).action((options) => {
|
|
7851
|
+
const errors = validatePipeline(loadPipeline(options.file));
|
|
7852
|
+
const result2 = { valid: errors.length === 0, errors };
|
|
7853
|
+
if (isJsonMode()) return printJson(result2);
|
|
7854
|
+
if (errors.length) throw new Error(`Invalid pipeline:
|
|
7855
|
+
- ${errors.join("\n- ")}`);
|
|
7856
|
+
printSuccess("Pipeline is valid.");
|
|
7857
|
+
});
|
|
7858
|
+
pipeline2.command("plan").description("Resolve step targets and required artifact transfers").option("-f, --file <path>", "Pipeline file", DEFAULT_PIPELINE_FILE).option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action((options) => {
|
|
7859
|
+
outputPlan(createExecutionPlan(loadPipeline(options.file), { only: parseList(options.only), skip: parseList(options.skip) }));
|
|
7860
|
+
});
|
|
7861
|
+
pipeline2.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Pipeline file", DEFAULT_PIPELINE_FILE).option("--spec <path>", "Local behavior spec", "tunedtensor.json").option("--config <path>", "Local runtime config").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options) => {
|
|
7862
|
+
const plan = createExecutionPlan(loadPipeline(options.file), { only: parseList(options.only), skip: parseList(options.skip) });
|
|
7863
|
+
if (options.dryRun) {
|
|
7864
|
+
if (isJsonMode()) return printJson({ dry_run: true, ...plan });
|
|
7865
|
+
console.log("Dry run only \u2014 no execution, artifact transfer, or credit reservation will occur.");
|
|
7866
|
+
return outputPlan(plan);
|
|
7867
|
+
}
|
|
7868
|
+
const remote = plan.steps.find((step) => step.target !== "local");
|
|
7869
|
+
if (remote) {
|
|
7870
|
+
throw new Error(`Step "${remote.id}" targets cloud execution. Hosted and mixed pipeline execution is not enabled by this CLI build; use --dry-run.`);
|
|
7871
|
+
}
|
|
7872
|
+
const input = await loadLocalRunInput(resolve7(options.spec));
|
|
7873
|
+
assertLocalRunInputReady(input.request);
|
|
7874
|
+
const config = await loadLocalRunnerConfig(options.config ? resolve7(options.config) : void 0);
|
|
7875
|
+
const localPipeline = {
|
|
7876
|
+
version: 1,
|
|
7877
|
+
...plan.name ? { name: plan.name } : {},
|
|
7878
|
+
steps: plan.steps.map(({ transfers: _transfers, ...step }) => step)
|
|
7879
|
+
};
|
|
7880
|
+
const result2 = await runLocalPipeline({ request: input.request, config, pipeline: localPipeline });
|
|
7881
|
+
if (isJsonMode()) return printJson(result2);
|
|
7882
|
+
printSuccess(`Pipeline completed with status ${result2.status}.`);
|
|
7883
|
+
});
|
|
7884
|
+
}
|
|
7885
|
+
|
|
7642
7886
|
// src/cli.ts
|
|
7643
7887
|
async function createDefaultAgentClient(runtime, env, cloud) {
|
|
7644
7888
|
const selection = getAgentSelection(env);
|
|
@@ -7681,7 +7925,16 @@ async function createDefaultAgentClient(runtime, env, cloud) {
|
|
|
7681
7925
|
}
|
|
7682
7926
|
function createLazyDefaultAgentClient(runtime, env, cloud) {
|
|
7683
7927
|
let pending;
|
|
7684
|
-
const client = () =>
|
|
7928
|
+
const client = () => {
|
|
7929
|
+
if (!pending) {
|
|
7930
|
+
const attempt = createDefaultAgentClient(runtime, env, cloud);
|
|
7931
|
+
pending = attempt;
|
|
7932
|
+
attempt.catch(() => {
|
|
7933
|
+
if (pending === attempt) pending = void 0;
|
|
7934
|
+
});
|
|
7935
|
+
}
|
|
7936
|
+
return pending;
|
|
7937
|
+
};
|
|
7685
7938
|
return {
|
|
7686
7939
|
createThread: async () => await (await client()).createThread(),
|
|
7687
7940
|
listThreads: async () => await (await client()).listThreads(),
|
|
@@ -7696,7 +7949,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
7696
7949
|
if (!entrypoint) {
|
|
7697
7950
|
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
7698
7951
|
}
|
|
7699
|
-
return await new Promise((
|
|
7952
|
+
return await new Promise((resolve8, reject) => {
|
|
7700
7953
|
const child = spawn4(process.execPath, [entrypoint, ...args2], {
|
|
7701
7954
|
cwd: options.cwd,
|
|
7702
7955
|
env: options.env ?? process.env,
|
|
@@ -7732,7 +7985,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
7732
7985
|
child.once("close", (exitCode, signal) => {
|
|
7733
7986
|
cleanup();
|
|
7734
7987
|
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
7735
|
-
|
|
7988
|
+
resolve8({
|
|
7736
7989
|
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
7737
7990
|
signal
|
|
7738
7991
|
});
|
|
@@ -7836,6 +8089,7 @@ function createProgram(version, runtime = {}) {
|
|
|
7836
8089
|
registerInitCommand(program);
|
|
7837
8090
|
registerEvalCommand(program);
|
|
7838
8091
|
registerPushCommand(program);
|
|
8092
|
+
registerPipelineCommands(program);
|
|
7839
8093
|
registerAgentCommands(program, {
|
|
7840
8094
|
env,
|
|
7841
8095
|
output: runtime.stdout ?? process.stdout,
|
|
@@ -7993,7 +8247,7 @@ async function runCli(version, runtime = {}) {
|
|
|
7993
8247
|
}
|
|
7994
8248
|
|
|
7995
8249
|
// src/index.ts
|
|
7996
|
-
runCli("0.9.
|
|
8250
|
+
runCli("0.9.1").catch((err) => {
|
|
7997
8251
|
reportError(err);
|
|
7998
8252
|
process.exit(1);
|
|
7999
8253
|
});
|