@tuned-tensor/cli 0.20.0 → 0.22.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 +123 -7
- package/dist/{chunk-4Q54NHKR.js → chunk-4SUXBHIY.js} +1715 -392
- package/dist/chunk-4SUXBHIY.js.map +1 -0
- package/dist/index.js +1910 -356
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +147 -49
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +44 -1
- package/docs/local-runtime/aws-gpu.md +2 -0
- package/docs/local-runtime/decision.md +142 -0
- package/docs/spec-architecture.html +2 -2
- package/docs/spec-workflow.md +18 -3
- package/examples/single-spec/decision/tunedtensor.json +630 -0
- package/package.json +5 -2
- package/training/adapter/src/evaluate.py +14 -7
- package/training/adapter/src/hub_download.py +384 -0
- package/training/adapter/src/model_contract.py +38 -5
- package/training/adapter/src/prefetch.py +26 -32
- package/training/adapter/src/prefetch_dataset.py +116 -0
- package/training/adapter/src/serve.py +42 -12
- package/training/adapter/src/train.py +8 -0
- package/training/decision/pyproject.toml +25 -0
- package/training/decision/src/common.py +127 -0
- package/training/decision/src/evaluate.py +70 -0
- package/training/decision/src/train.py +181 -0
- package/training/decision/uv.lock +1175 -0
- package/dist/chunk-4Q54NHKR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DECISION_MODELS,
|
|
3
4
|
DEFAULT_ARTIFACT_ROOT,
|
|
5
|
+
DEFAULT_DECISION_RUNS_DIR,
|
|
4
6
|
DEFAULT_FOUNDATION_RUNS_DIR,
|
|
5
7
|
DEFAULT_PROJECT_STORE_ROOT,
|
|
6
8
|
TRAINING_MODELS,
|
|
7
9
|
assertFoundationSettingsMatch,
|
|
8
10
|
assessHardware,
|
|
11
|
+
auditRun,
|
|
12
|
+
buildDecisionPythonCommand,
|
|
9
13
|
buildFoundationPythonCommand,
|
|
10
14
|
buildSystemMessage,
|
|
11
15
|
canonicalFoundationPipeline,
|
|
12
16
|
canonicalJson,
|
|
13
17
|
canonicalPipeline,
|
|
18
|
+
compareEvalReports,
|
|
14
19
|
configForSpec,
|
|
15
20
|
createExecutionPlan,
|
|
16
21
|
createLocalStore,
|
|
22
|
+
createRunConsoleReporter,
|
|
23
|
+
decisionLabels,
|
|
17
24
|
defaultLocalHome,
|
|
18
25
|
defaultStoreRoot,
|
|
26
|
+
deriveSampleSeed,
|
|
27
|
+
ensureDatasetCached,
|
|
28
|
+
evalReportSchema,
|
|
29
|
+
evaluateCapabilities,
|
|
19
30
|
expandUserPath,
|
|
31
|
+
fileUri,
|
|
20
32
|
formatAgentHostBlock,
|
|
21
33
|
formatHostStatusLine,
|
|
22
34
|
getAgentConfigDir,
|
|
@@ -32,24 +44,27 @@ import {
|
|
|
32
44
|
pipelineForRunInput,
|
|
33
45
|
pipelineFromFoundationHyperparameters,
|
|
34
46
|
readHardwareSnapshot,
|
|
47
|
+
resolveDecisionModel,
|
|
35
48
|
resolveProjectConfig,
|
|
36
49
|
runGpuProcess,
|
|
37
50
|
runLocalPipeline,
|
|
38
51
|
runLoggedProcess,
|
|
39
52
|
runReportSchema,
|
|
40
53
|
specHash,
|
|
54
|
+
splitSpecExamples,
|
|
41
55
|
validateBehaviorSpec,
|
|
42
56
|
validateEvalCases,
|
|
43
57
|
validatePipeline,
|
|
44
58
|
warningsFromSnapshot,
|
|
59
|
+
withDecisionPythonEnvironment,
|
|
45
60
|
withFoundationPythonEnvironment
|
|
46
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-4SUXBHIY.js";
|
|
47
62
|
|
|
48
63
|
// src/cli.ts
|
|
49
64
|
import { spawn as spawn4 } from "child_process";
|
|
50
65
|
import { constants as osConstants2 } from "os";
|
|
51
66
|
import { Command } from "commander";
|
|
52
|
-
import
|
|
67
|
+
import chalk13 from "chalk";
|
|
53
68
|
|
|
54
69
|
// src/local-agent-client.ts
|
|
55
70
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -75,15 +90,18 @@ var FolderName = Type.String({
|
|
|
75
90
|
description: "One new folder name directly beneath the current workspace"
|
|
76
91
|
});
|
|
77
92
|
var LocalBaseModel = Type.String({
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
minLength: 3,
|
|
94
|
+
maxLength: 193,
|
|
95
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*$",
|
|
96
|
+
description: `Hugging Face repo id. Certified adapter models: ${TRAINING_MODELS.map((model) => model.id).join(", ")}. Decision models: ${DECISION_MODELS.map((model) => model.id).join(", ")}. Other adapter repos require hyperparameters.base_model_revision (40-character commit SHA).`
|
|
80
97
|
});
|
|
81
98
|
var NonEmptyText = Type.String({ minLength: 1, maxLength: 1e3 });
|
|
82
99
|
var LocalProjectSpecSchema = Type.Object({
|
|
83
100
|
name: Type.String({ minLength: 1, maxLength: 255 }),
|
|
84
101
|
description: Type.Optional(Type.String({ maxLength: 5e3 })),
|
|
85
102
|
base_model: Type.Optional(LocalBaseModel),
|
|
86
|
-
engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation")])),
|
|
103
|
+
engine: Type.Optional(Type.Union([Type.Literal("adapter"), Type.Literal("foundation"), Type.Literal("decision")])),
|
|
104
|
+
decision: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
87
105
|
hyperparameters: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
88
106
|
foundation: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
89
107
|
dataset_prebuilt: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
@@ -335,6 +353,7 @@ var EDIT_KEYS = /* @__PURE__ */ new Set([
|
|
|
335
353
|
"base_model",
|
|
336
354
|
"hyperparameters",
|
|
337
355
|
"foundation",
|
|
356
|
+
"decision",
|
|
338
357
|
"dataset_prebuilt",
|
|
339
358
|
"runtime",
|
|
340
359
|
"evaluation",
|
|
@@ -774,7 +793,7 @@ async function prepareLocalPipelineAction(args2) {
|
|
|
774
793
|
specPath: spec.displayPath,
|
|
775
794
|
specSha256: createHash2("sha256").update(spec.source).digest("hex"),
|
|
776
795
|
dryRun,
|
|
777
|
-
engine: input.kind === "foundation-spec" ? "foundation" : "adapter",
|
|
796
|
+
engine: input.kind === "foundation-spec" ? "foundation" : input.kind === "decision-spec" ? "decision" : "adapter",
|
|
778
797
|
resolvedSpec: input.spec,
|
|
779
798
|
resolvedConfig,
|
|
780
799
|
...config ? {
|
|
@@ -1534,9 +1553,16 @@ function canonicalizeHostedBaseModel(model) {
|
|
|
1534
1553
|
}
|
|
1535
1554
|
return canonical;
|
|
1536
1555
|
}
|
|
1556
|
+
var HUGGING_FACE_REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/;
|
|
1557
|
+
function canonicalizeLocalBaseModel(model) {
|
|
1558
|
+
if (typeof model === "string" && HUGGING_FACE_REPO_ID.test(model.trim())) {
|
|
1559
|
+
return BASE_MODEL_ALIASES.get(model.trim().toLowerCase()) ?? model.trim();
|
|
1560
|
+
}
|
|
1561
|
+
return canonicalizeBaseModel(model);
|
|
1562
|
+
}
|
|
1537
1563
|
function canonicalizeSpecBaseModel(body) {
|
|
1538
1564
|
if ("base_model" in body && body.base_model !== void 0) {
|
|
1539
|
-
return { ...body, base_model:
|
|
1565
|
+
return { ...body, base_model: canonicalizeLocalBaseModel(body.base_model) };
|
|
1540
1566
|
}
|
|
1541
1567
|
return body;
|
|
1542
1568
|
}
|
|
@@ -1958,8 +1984,8 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
1958
1984
|
});
|
|
1959
1985
|
}
|
|
1960
1986
|
),
|
|
1961
|
-
define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter or
|
|
1962
|
-
engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation")])),
|
|
1987
|
+
define("describe_pipeline", "Describe pipeline", "Describe the built-in adapter, foundation or decision workflow and exact TT commands. A decision spec fine-tunes a small typed decision model (e.g. convaiinnovations/laya) that returns a calibrated probability per label instead of generated text; use it for classification, routing, triage and yes/no or score gates. This never executes anything.", Type2.Object({
|
|
1988
|
+
engine: Type2.Optional(Type2.Union([Type2.Literal("adapter"), Type2.Literal("foundation"), Type2.Literal("decision")])),
|
|
1963
1989
|
target: Type2.Optional(Type2.Union([Type2.Literal("local"), Type2.Literal("cloud")]))
|
|
1964
1990
|
}, { additionalProperties: false }), async (p) => {
|
|
1965
1991
|
const engine = p.engine ?? "adapter";
|
|
@@ -1980,6 +2006,24 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
1980
2006
|
},
|
|
1981
2007
|
note: "Configure runtime.gpu in tunedtensor.json with provider aws, instanceId, SSH user and optional AWS profile/region/identityFile. The instance must already be running and reachable with a verified SSH host key. AWS profile lookup and SSH access are separate; no TT token is required. GPU processes use it automatically; orchestration, scoring and artifacts stay local. Hardware tools inspect only the laptop; doctor checks the configured instance. The user manages AWS quota, costs and shutdown. Keep the laptop running until outputs return. Hosted training is retired."
|
|
1982
2008
|
} : void 0;
|
|
2009
|
+
if (engine === "decision") {
|
|
2010
|
+
return {
|
|
2011
|
+
version: 1,
|
|
2012
|
+
engine,
|
|
2013
|
+
scope: { execution: "local", devices: ["cpu", "mps", "cuda"] },
|
|
2014
|
+
canonical: canonicalPipeline("local"),
|
|
2015
|
+
models: DECISION_MODELS.map((model) => ({ id: model.id, revision: model.revision, parameters: model.parameters, license: model.license })),
|
|
2016
|
+
spec: 'engine "decision", base_model, decision {type: choice|noul|score, criteria}; example outputs are labels (choice keys, true/false, or score level indices). system_prompt, guidelines and constraints become the question instructions.',
|
|
2017
|
+
commands: {
|
|
2018
|
+
init: "tt init --engine decision",
|
|
2019
|
+
validate: "tt validate",
|
|
2020
|
+
dry_run: "tt pipeline run --dry-run --spec tunedtensor.json",
|
|
2021
|
+
run: "tt pipeline run --spec tunedtensor.json"
|
|
2022
|
+
},
|
|
2023
|
+
note: "Runs locally on CPU, Apple MPS or CUDA; runtime.gpu (AWS) is not used. The tuned model directory loads in Python with laya.load(<model_dir>).",
|
|
2024
|
+
...host ? { host } : {}
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
1983
2027
|
if (engine === "foundation") {
|
|
1984
2028
|
const foundation = {
|
|
1985
2029
|
version: 1,
|
|
@@ -2097,7 +2141,7 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2097
2141
|
define(
|
|
2098
2142
|
"prepare_update_local_spec",
|
|
2099
2143
|
"Prepare behavior spec edit",
|
|
2100
|
-
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
|
|
2144
|
+
"Prepare a reviewed edit of the existing local spec using the SHA-256 from get_local_spec. Use add_examples to append examples without resending existing ones. Other arrays, the decision question and pipeline replace whole fields; hyperparameters and foundation settings merge by key. Runtime and evaluation objects merge recursively; null removes optional settings. Preserve unrelated fields. No write until /approve.",
|
|
2101
2145
|
Type2.Object({
|
|
2102
2146
|
spec_path: Type2.Optional(Type2.String({ maxLength: 1e3 })),
|
|
2103
2147
|
expected_sha256: Type2.String({ pattern: "^[a-f0-9]{64}$" }),
|
|
@@ -2118,10 +2162,13 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2118
2162
|
}, { additionalProperties: false }), { minItems: 1 })),
|
|
2119
2163
|
hyperparameters: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2120
2164
|
foundation: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2165
|
+
decision: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown())),
|
|
2121
2166
|
runtime: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
2122
2167
|
evaluation: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
2123
2168
|
pipeline: Type2.Optional(Type2.Union([Type2.Record(Type2.String(), Type2.Unknown()), Type2.Null()])),
|
|
2124
|
-
dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown()
|
|
2169
|
+
dataset_prebuilt: Type2.Optional(Type2.Record(Type2.String(), Type2.Unknown(), {
|
|
2170
|
+
description: "Local chat JSONL files ({training, validation|test}), or a Hugging Face dataset: {huggingface: {repo, revision?: 40-char commit, columns?: {input, output}}, training: 'repo/path.jsonl', test|validation: ...}. The user downloads it with `tt datasets prefetch tunedtensor.json`."
|
|
2171
|
+
}))
|
|
2125
2172
|
}, { additionalProperties: false })
|
|
2126
2173
|
}, { additionalProperties: false }),
|
|
2127
2174
|
async (p) => {
|
|
@@ -2246,11 +2293,13 @@ function createTunedTensorTools(api, options = {}) {
|
|
|
2246
2293
|
// src/local-agent-client.ts
|
|
2247
2294
|
var MAX_TOOL_CALLS_PER_TURN = 12;
|
|
2248
2295
|
var SYSTEM_PROMPT = `You are the local Tuned Tensor assistant running on the user's laptop.
|
|
2249
|
-
The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes; use add_examples to append examples. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new project. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file.
|
|
2296
|
+
The workspace tunedtensor.json behavior spec is the source of truth for model behavior, training, evaluation policy, runtime placement and optional pipeline settings. For requests to change the model's behavior, inspect it with get_local_spec first, then prepare_update_local_spec with the returned SHA-256 and only the requested field changes; use add_examples to append examples. Preserve unrelated examples, instructions, engine and training settings. Explain the proposed diff and validation; never claim an edit was saved before approval. Read-only questions about the current spec use get_local_spec without proposing an edit. If no spec exists, help prepare a new project. Malformed JSON must be repaired in an editor before prepare_update_local_spec can operate; never offer that tool as a way to reconstruct a truncated file. For direct review, point users to /spec (a readable overview), /spec <section> such as /spec examples, /spec show for the JSON, and /spec diff, /spec validate and /spec history. General educational or inspection questions do not require a spec mutation.
|
|
2250
2297
|
Local workflow commands need no TT access token. For local inspection commands such as runs, models, or doctor, tell the user to run the matching TT command in this shell (for example \`runs list\` or \`doctor\`); those commands execute outside the agent. User-owned AWS GPU training uses \`tt pipeline run\` with runtime.gpu configuration in tunedtensor.json, without a TT token. \`tt cloud\` is for TT account records, not GPU execution; hosted training start/estimate are retired. AWS credentials authorize instance lookup; separate SSH access is required. The instance must already be running, and the user manages quota, capacity, costs and shutdown. Keep the laptop running until outputs return. Model-provider choice is independent of GPU placement.
|
|
2251
2298
|
When the user requests commands to inspect a run or serve a result, provide the direct CLI handoff: \`tt runs list\` to find a run, \`tt runs get <run-id>\` for its details, \`tt runs report <run-id>\` for its evaluation report, \`tt models list\` to find the resulting model ID, and \`tt serve <model-id>\` to serve that model. Replace placeholders with actual IDs from those commands; never invent results or IDs. Until the user supplies command output, keep literal <run-id> and <model-id> placeholders; do not fabricate example IDs or prefixes. Local run IDs are UUIDs and adapter model IDs are local-<run-id>. Do not refresh hardware or propose spec edits merely to supply those commands; perform those actions only when requested.
|
|
2252
|
-
For accurate adapter and
|
|
2299
|
+
For accurate adapter, foundation and decision workflow stages and commands, call \`describe_pipeline\` with the matching engine instead of relying on prior knowledge.
|
|
2253
2300
|
When the user asks to train, fine-tune, dry-run, or execute a workflow, call \`prepare_pipeline_run\`. Omit its pipeline argument to use the saved recipe or derive its default from the workspace spec. To change workflow stages, propose an edit to the spec pipeline section first; runtime and evaluation settings also belong in the core spec. This prepares a sealed local pipeline dry-run; approved pipeline actions are dry-runs only and may be stopped with Ctrl-C. Never claim training started. Real training requires an explicit direct \`tt pipeline run --spec tunedtensor.json\` command in the shell; preview uses the same command with \`--dry-run\`. The spec path is a --spec option, not a positional argument. No pipeline file or pipeline init is required for this default spec-driven workflow.
|
|
2301
|
+
Adapter base models: TT certifies ${TRAINING_MODELS.map((model) => model.id).join(", ")} (reviewed revision, architecture and memory profile). For adapter training, when the user asks for a model that is not certified, do not silently substitute a certified one. Confirm the repo exists with \`search_hugging_face\`, then explain the trade-off: an uncertified model trains as a text causal LM with generic LoRA defaults, safetensors weights and no remote code, but TT has not verified its architecture, chat template or hardware fit. If the user accepts, propose setting base_model to the exact repo id and hyperparameters.base_model_revision to a 40-character commit SHA the user copies from its Hugging Face commits page; never invent a SHA. Suggest the closest certified model as the lower-risk option, and point out that models needing remote code, vision inputs or gated access the user lacks will fail.
|
|
2302
|
+
Decision models use engine "decision" and their own certified registry. Call \`describe_pipeline\` with engine "decision" for supported models and typed question settings; do not apply adapter revision requirements to decision specs.
|
|
2254
2303
|
When the user asks to discover public models or datasets, call \`search_hugging_face\`; it searches Hugging Face metadata for foundation and fine-tuning workflows. The query is sent to huggingface.co, so never include secrets or private data.
|
|
2255
2304
|
For educational questions about how or why training works, call \`inspect_training_source\` before answering. Ground the explanation in the returned code and distinguish observed behavior from inferred rationale; do not invent author intent.
|
|
2256
2305
|
When the user wants to examine this host, GPU, VRAM, CUDA, or decide what this machine can train, fine-tune, or infer, call \`examine_hardware\` before recommending a base model, engine, or pipeline. For local execution, recommend only workloads marked ready (mention tight as a caution). This tool inspects only the laptop: do not use a missing local GPU to reject AWS training or infer remote capacity. For AWS, direct the user to \`tt doctor tunedtensor.json\` to check the configured instance. Never invent generic 7B/70B sizing. Never start or cancel training from the model tool loop.
|
|
@@ -2778,16 +2827,79 @@ function del(path, opts) {
|
|
|
2778
2827
|
}
|
|
2779
2828
|
|
|
2780
2829
|
// src/agent.ts
|
|
2781
|
-
import
|
|
2830
|
+
import chalk8 from "chalk";
|
|
2831
|
+
|
|
2832
|
+
// src/mascot.ts
|
|
2833
|
+
import chalk from "chalk";
|
|
2834
|
+
var MASCOT_NAME = "tt";
|
|
2835
|
+
var MASCOT_WIDTH = 11;
|
|
2836
|
+
var PALETTE = {
|
|
2837
|
+
a: "#B39DFB",
|
|
2838
|
+
// diagonal, top-left
|
|
2839
|
+
b: "#9B72F8",
|
|
2840
|
+
// diagonal, center
|
|
2841
|
+
c: "#7C3AED",
|
|
2842
|
+
// diagonal, bottom-right
|
|
2843
|
+
d: "#2B323D"
|
|
2844
|
+
// slate tiles
|
|
2845
|
+
};
|
|
2846
|
+
var MARK = [
|
|
2847
|
+
"aa.dd...",
|
|
2848
|
+
"aa.dd...",
|
|
2849
|
+
"........",
|
|
2850
|
+
"dd.bb.dd",
|
|
2851
|
+
"dd.bb.dd",
|
|
2852
|
+
"........",
|
|
2853
|
+
"...dd.cc",
|
|
2854
|
+
"...dd.cc"
|
|
2855
|
+
];
|
|
2856
|
+
function pixelRow(upper, lower) {
|
|
2857
|
+
let line = "";
|
|
2858
|
+
for (let column = 0; column < upper.length; column += 1) {
|
|
2859
|
+
const up = upper[column];
|
|
2860
|
+
const down = lower[column];
|
|
2861
|
+
if (up === "." && down === ".") line += " ";
|
|
2862
|
+
else if (up === ".") line += chalk.hex(PALETTE[down])("\u2584");
|
|
2863
|
+
else if (down === ".") line += chalk.hex(PALETTE[up])("\u2580");
|
|
2864
|
+
else if (up === down) line += chalk.hex(PALETTE[up])("\u2588");
|
|
2865
|
+
else line += chalk.hex(PALETTE[up]).bgHex(PALETTE[down])("\u2580");
|
|
2866
|
+
}
|
|
2867
|
+
return line;
|
|
2868
|
+
}
|
|
2869
|
+
function renderMascot() {
|
|
2870
|
+
if (chalk.level === 0) {
|
|
2871
|
+
return ["", " \u25A0 \u25A1", " \u25A1 \u25A0 \u25A1", " \u25A1 \u25A0"].map((row) => row.padEnd(MASCOT_WIDTH));
|
|
2872
|
+
}
|
|
2873
|
+
const rows2 = [];
|
|
2874
|
+
for (let row = 0; row < MARK.length; row += 2) {
|
|
2875
|
+
rows2.push(` ${pixelRow(MARK[row], MARK[row + 1])} `);
|
|
2876
|
+
}
|
|
2877
|
+
return rows2;
|
|
2878
|
+
}
|
|
2879
|
+
function mascotMark() {
|
|
2880
|
+
if (chalk.level === 0) return "\u259A";
|
|
2881
|
+
return `${chalk.hex(PALETTE.a)("\u2580")}${chalk.hex(PALETTE.c)("\u2584")}`;
|
|
2882
|
+
}
|
|
2883
|
+
function mascotSays(message) {
|
|
2884
|
+
return `${mascotMark()} ${chalk.hex(PALETTE.b).bold(MASCOT_NAME)} ${chalk.dim("\u203A")} ${message}`;
|
|
2885
|
+
}
|
|
2886
|
+
var FAREWELLS = [
|
|
2887
|
+
"See you next epoch.",
|
|
2888
|
+
"Bye! Your checkpoints are safe with me.",
|
|
2889
|
+
"Run tt to pick up where you left off."
|
|
2890
|
+
];
|
|
2891
|
+
function mascotFarewell(seed = Date.now()) {
|
|
2892
|
+
return mascotSays(FAREWELLS[Math.abs(seed) % FAREWELLS.length]);
|
|
2893
|
+
}
|
|
2782
2894
|
|
|
2783
2895
|
// src/shell.ts
|
|
2784
|
-
import
|
|
2896
|
+
import chalk7 from "chalk";
|
|
2785
2897
|
|
|
2786
2898
|
// src/terminal-markdown.ts
|
|
2787
|
-
import
|
|
2899
|
+
import chalk2 from "chalk";
|
|
2788
2900
|
import { stripVTControlCharacters } from "util";
|
|
2789
|
-
var accent =
|
|
2790
|
-
var code =
|
|
2901
|
+
var accent = chalk2.hex("#8B5CF6");
|
|
2902
|
+
var code = chalk2.hex("#A78BFA");
|
|
2791
2903
|
function sanitizeTerminalText(text2) {
|
|
2792
2904
|
return stripVTControlCharacters(text2).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, "");
|
|
2793
2905
|
}
|
|
@@ -2806,7 +2918,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2806
2918
|
if (text2[index] === "[") {
|
|
2807
2919
|
const link = text2.slice(index).match(/^\[([^\]]+)]\(([^)]+)\)/);
|
|
2808
2920
|
if (link) {
|
|
2809
|
-
rendered += `${
|
|
2921
|
+
rendered += `${chalk2.underline(renderInlineMarkdown(link[1]))}${chalk2.dim(` (${link[2]})`)}`;
|
|
2810
2922
|
index += link[0].length;
|
|
2811
2923
|
continue;
|
|
2812
2924
|
}
|
|
@@ -2815,7 +2927,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2815
2927
|
if (strong) {
|
|
2816
2928
|
const close = closingDelimiter(text2, strong, index);
|
|
2817
2929
|
if (close > index + strong.length) {
|
|
2818
|
-
rendered +=
|
|
2930
|
+
rendered += chalk2.bold(
|
|
2819
2931
|
renderInlineMarkdown(text2.slice(index + strong.length, close))
|
|
2820
2932
|
);
|
|
2821
2933
|
index = close + strong.length;
|
|
@@ -2834,7 +2946,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2834
2946
|
if (emphasis) {
|
|
2835
2947
|
const close = closingDelimiter(text2, emphasis, index);
|
|
2836
2948
|
if (close > index + 1) {
|
|
2837
|
-
rendered +=
|
|
2949
|
+
rendered += chalk2.italic(
|
|
2838
2950
|
renderInlineMarkdown(text2.slice(index + 1, close))
|
|
2839
2951
|
);
|
|
2840
2952
|
index = close + 1;
|
|
@@ -2849,7 +2961,7 @@ function renderInlineMarkdown(text2) {
|
|
|
2849
2961
|
function renderMarkdownLine(line, inCodeBlock) {
|
|
2850
2962
|
if (inCodeBlock) return code(` ${line}`);
|
|
2851
2963
|
const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
|
|
2852
|
-
if (heading) return
|
|
2964
|
+
if (heading) return chalk2.bold(renderInlineMarkdown(heading[1]));
|
|
2853
2965
|
const bullet = line.match(/^(\s*)[-+*]\s+(.+)$/);
|
|
2854
2966
|
if (bullet) {
|
|
2855
2967
|
return `${bullet[1]}${accent("\u2022")} ${renderInlineMarkdown(bullet[2])}`;
|
|
@@ -2859,9 +2971,9 @@ function renderMarkdownLine(line, inCodeBlock) {
|
|
|
2859
2971
|
return `${numbered[1]}${accent(`${numbered[2]}.`)} ${renderInlineMarkdown(numbered[3])}`;
|
|
2860
2972
|
}
|
|
2861
2973
|
const quote = line.match(/^\s*>\s?(.*)$/);
|
|
2862
|
-
if (quote) return `${
|
|
2974
|
+
if (quote) return `${chalk2.dim("\u2502")} ${chalk2.italic(renderInlineMarkdown(quote[1]))}`;
|
|
2863
2975
|
if (/^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/.test(line)) {
|
|
2864
|
-
return
|
|
2976
|
+
return chalk2.dim("\u2500".repeat(32));
|
|
2865
2977
|
}
|
|
2866
2978
|
return renderInlineMarkdown(line);
|
|
2867
2979
|
}
|
|
@@ -2937,9 +3049,9 @@ function wrapTerminalLine(line, width) {
|
|
|
2937
3049
|
}
|
|
2938
3050
|
|
|
2939
3051
|
// src/output.ts
|
|
2940
|
-
import
|
|
3052
|
+
import chalk3 from "chalk";
|
|
2941
3053
|
import Table from "cli-table3";
|
|
2942
|
-
var labelStyle =
|
|
3054
|
+
var labelStyle = chalk3.bold.hex("#A78BFA");
|
|
2943
3055
|
var jsonMode = false;
|
|
2944
3056
|
function setJsonMode(enabled) {
|
|
2945
3057
|
jsonMode = enabled;
|
|
@@ -2962,7 +3074,7 @@ function printTable(headers, rows2, meta) {
|
|
|
2962
3074
|
if (meta) {
|
|
2963
3075
|
const totalPages = Math.ceil(meta.total / meta.per_page);
|
|
2964
3076
|
console.log(
|
|
2965
|
-
|
|
3077
|
+
chalk3.dim(
|
|
2966
3078
|
`
|
|
2967
3079
|
Page ${meta.page}/${totalPages} (${meta.total} total)`
|
|
2968
3080
|
)
|
|
@@ -2973,17 +3085,17 @@ function printDetail(fields) {
|
|
|
2973
3085
|
const maxLabel = Math.max(...fields.map(([l]) => l.length));
|
|
2974
3086
|
for (const [label, value] of fields) {
|
|
2975
3087
|
const paddedLabel = label.padEnd(maxLabel);
|
|
2976
|
-
console.log(`${labelStyle(paddedLabel)} ${value ??
|
|
3088
|
+
console.log(`${labelStyle(paddedLabel)} ${value ?? chalk3.dim("\u2014")}`);
|
|
2977
3089
|
}
|
|
2978
3090
|
}
|
|
2979
3091
|
function printSuccess(message) {
|
|
2980
|
-
console.log(
|
|
3092
|
+
console.log(chalk3.green("\u2713") + " " + message);
|
|
2981
3093
|
}
|
|
2982
3094
|
function printWarning(message) {
|
|
2983
|
-
console.log(
|
|
3095
|
+
console.log(chalk3.yellow("!") + " " + message);
|
|
2984
3096
|
}
|
|
2985
3097
|
function printError(message) {
|
|
2986
|
-
console.error(
|
|
3098
|
+
console.error(chalk3.red("\u2717") + " " + message);
|
|
2987
3099
|
}
|
|
2988
3100
|
function reportError(err) {
|
|
2989
3101
|
const isApi = err instanceof ApiError;
|
|
@@ -3008,19 +3120,19 @@ function formatDate(iso) {
|
|
|
3008
3120
|
}
|
|
3009
3121
|
function formatStatus(status2) {
|
|
3010
3122
|
const map = {
|
|
3011
|
-
completed:
|
|
3012
|
-
running:
|
|
3013
|
-
training:
|
|
3014
|
-
evaluating:
|
|
3015
|
-
preparing:
|
|
3016
|
-
pending:
|
|
3017
|
-
uploading:
|
|
3018
|
-
failed:
|
|
3019
|
-
cancelled:
|
|
3020
|
-
validated:
|
|
3021
|
-
invalid:
|
|
3123
|
+
completed: chalk3.green,
|
|
3124
|
+
running: chalk3.hex("#A78BFA"),
|
|
3125
|
+
training: chalk3.hex("#A78BFA"),
|
|
3126
|
+
evaluating: chalk3.hex("#A78BFA"),
|
|
3127
|
+
preparing: chalk3.yellow,
|
|
3128
|
+
pending: chalk3.yellow,
|
|
3129
|
+
uploading: chalk3.yellow,
|
|
3130
|
+
failed: chalk3.red,
|
|
3131
|
+
cancelled: chalk3.dim,
|
|
3132
|
+
validated: chalk3.green,
|
|
3133
|
+
invalid: chalk3.red
|
|
3022
3134
|
};
|
|
3023
|
-
const colorFn = map[status2] ||
|
|
3135
|
+
const colorFn = map[status2] || chalk3.white;
|
|
3024
3136
|
return colorFn(status2);
|
|
3025
3137
|
}
|
|
3026
3138
|
function truncate(str, max) {
|
|
@@ -3031,6 +3143,400 @@ function shortId(id) {
|
|
|
3031
3143
|
return id.slice(0, 8);
|
|
3032
3144
|
}
|
|
3033
3145
|
|
|
3146
|
+
// src/spec-view.ts
|
|
3147
|
+
import chalk4 from "chalk";
|
|
3148
|
+
var accent2 = chalk4.hex("#8B5CF6");
|
|
3149
|
+
var soft = chalk4.hex("#A78BFA");
|
|
3150
|
+
var keyColor = chalk4.hex("#C4B5FD");
|
|
3151
|
+
var stringColor = chalk4.hex("#86EFAC");
|
|
3152
|
+
var numberColor = chalk4.hex("#FCD34D");
|
|
3153
|
+
var literalColor = chalk4.hex("#F9A8D4");
|
|
3154
|
+
var SPEC_SECTIONS = [
|
|
3155
|
+
"identity",
|
|
3156
|
+
"behavior",
|
|
3157
|
+
"examples",
|
|
3158
|
+
"training",
|
|
3159
|
+
"evaluation",
|
|
3160
|
+
"runtime",
|
|
3161
|
+
"pipeline"
|
|
3162
|
+
];
|
|
3163
|
+
var SECTION_ALIASES = {
|
|
3164
|
+
id: "identity",
|
|
3165
|
+
about: "identity",
|
|
3166
|
+
prompt: "behavior",
|
|
3167
|
+
system: "behavior",
|
|
3168
|
+
guidelines: "behavior",
|
|
3169
|
+
constraints: "behavior",
|
|
3170
|
+
example: "examples",
|
|
3171
|
+
data: "examples",
|
|
3172
|
+
hyperparameters: "training",
|
|
3173
|
+
hparams: "training",
|
|
3174
|
+
foundation: "training",
|
|
3175
|
+
train: "training",
|
|
3176
|
+
eval: "evaluation",
|
|
3177
|
+
gpu: "runtime"
|
|
3178
|
+
};
|
|
3179
|
+
function resolveSpecSection(name) {
|
|
3180
|
+
const normalized = name.trim().toLowerCase();
|
|
3181
|
+
if (SPEC_SECTIONS.includes(normalized)) return normalized;
|
|
3182
|
+
return SECTION_ALIASES[normalized];
|
|
3183
|
+
}
|
|
3184
|
+
var SECTION_BLURBS = {
|
|
3185
|
+
identity: "what this model is",
|
|
3186
|
+
behavior: "what the model should do",
|
|
3187
|
+
examples: "teaches by demonstration",
|
|
3188
|
+
training: "how it is trained",
|
|
3189
|
+
evaluation: "how results are scored",
|
|
3190
|
+
runtime: "where it runs",
|
|
3191
|
+
pipeline: "custom step recipe"
|
|
3192
|
+
};
|
|
3193
|
+
var SECTION_KEYS = {
|
|
3194
|
+
identity: ["name", "description", "engine", "base_model", "id", "dataset_prebuilt"],
|
|
3195
|
+
behavior: ["system_prompt", "guidelines", "constraints", "decision"],
|
|
3196
|
+
examples: ["examples"],
|
|
3197
|
+
training: ["hyperparameters", "foundation"],
|
|
3198
|
+
evaluation: ["evaluation"],
|
|
3199
|
+
runtime: ["runtime"],
|
|
3200
|
+
pipeline: ["pipeline"]
|
|
3201
|
+
};
|
|
3202
|
+
var KNOWN_KEYS = new Set(Object.values(SECTION_KEYS).flat());
|
|
3203
|
+
function isObject2(value) {
|
|
3204
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3205
|
+
}
|
|
3206
|
+
function clean(value) {
|
|
3207
|
+
return sanitizeTerminalText(value);
|
|
3208
|
+
}
|
|
3209
|
+
function truncateText(value, width) {
|
|
3210
|
+
const flat = clean(value).replace(/\s+/g, " ").trim();
|
|
3211
|
+
if (terminalWidth(flat) <= width) return flat;
|
|
3212
|
+
let out = "";
|
|
3213
|
+
for (const char of flat) {
|
|
3214
|
+
if (terminalWidth(out + char) > width - 1) break;
|
|
3215
|
+
out += char;
|
|
3216
|
+
}
|
|
3217
|
+
return `${out}\u2026`;
|
|
3218
|
+
}
|
|
3219
|
+
function hintLines(items, width) {
|
|
3220
|
+
return joinFitting(items, width).map((line) => chalk4.dim(line));
|
|
3221
|
+
}
|
|
3222
|
+
function joinFitting(items, width) {
|
|
3223
|
+
const lines = [];
|
|
3224
|
+
let current = "";
|
|
3225
|
+
for (const item of items) {
|
|
3226
|
+
const next = current ? `${current} \xB7 ${item}` : item;
|
|
3227
|
+
if (current && terminalWidth(next) > width) {
|
|
3228
|
+
lines.push(current);
|
|
3229
|
+
current = item;
|
|
3230
|
+
} else {
|
|
3231
|
+
current = next;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
if (current) lines.push(current);
|
|
3235
|
+
return lines;
|
|
3236
|
+
}
|
|
3237
|
+
function formatScalar(value) {
|
|
3238
|
+
if (typeof value === "string") return stringColor(clean(value));
|
|
3239
|
+
if (typeof value === "number") return numberColor(String(value));
|
|
3240
|
+
if (typeof value === "boolean" || value === null) return literalColor(String(value));
|
|
3241
|
+
return highlightJson(JSON.stringify(value));
|
|
3242
|
+
}
|
|
3243
|
+
function flattenSettings(value, prefix = "") {
|
|
3244
|
+
if (!isObject2(value)) return [[prefix || "value", value]];
|
|
3245
|
+
const rows2 = [];
|
|
3246
|
+
for (const [key, child] of Object.entries(value)) {
|
|
3247
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
3248
|
+
if (isObject2(child) && Object.keys(child).length > 0) rows2.push(...flattenSettings(child, path));
|
|
3249
|
+
else rows2.push([path, child]);
|
|
3250
|
+
}
|
|
3251
|
+
return rows2;
|
|
3252
|
+
}
|
|
3253
|
+
function highlightJson(source) {
|
|
3254
|
+
const token = /("(?:[^"\\]|\\.)*")(\s*:)?|(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)|\b(true|false|null)\b|([{}[\],])/g;
|
|
3255
|
+
return clean(source).replace(token, (match, string, colon, number2, literal, punctuation) => {
|
|
3256
|
+
if (string !== void 0) {
|
|
3257
|
+
return colon !== void 0 ? `${keyColor(string)}${chalk4.dim(colon)}` : stringColor(string);
|
|
3258
|
+
}
|
|
3259
|
+
if (number2 !== void 0) return numberColor(number2);
|
|
3260
|
+
if (literal !== void 0) return literalColor(literal);
|
|
3261
|
+
if (punctuation !== void 0) return chalk4.dim(punctuation);
|
|
3262
|
+
return match;
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
function highlightSpecDiff(diff) {
|
|
3266
|
+
return diff.split("\n").map((line) => {
|
|
3267
|
+
if (line.startsWith("@@")) return accent2.bold(line);
|
|
3268
|
+
if (line.startsWith("+")) return chalk4.green(line);
|
|
3269
|
+
if (line.startsWith("-")) return chalk4.red(line);
|
|
3270
|
+
return line;
|
|
3271
|
+
}).join("\n");
|
|
3272
|
+
}
|
|
3273
|
+
var ViewBuilder = class {
|
|
3274
|
+
constructor(width) {
|
|
3275
|
+
this.width = width;
|
|
3276
|
+
}
|
|
3277
|
+
lines = [];
|
|
3278
|
+
push(line = "") {
|
|
3279
|
+
this.lines.push(line);
|
|
3280
|
+
}
|
|
3281
|
+
/** Wrap text under a fixed indent and optional gutter marker. */
|
|
3282
|
+
paragraph(text2, indent, style = (value) => value, maxLines) {
|
|
3283
|
+
const body = this.width - terminalWidth(indent);
|
|
3284
|
+
const wrapped = clean(text2).split(/\r?\n/).flatMap((line) => line.trim() ? wrapTerminalLine(line, Math.max(20, body)) : [""]);
|
|
3285
|
+
const shown = maxLines && wrapped.length > maxLines ? wrapped.slice(0, maxLines) : wrapped;
|
|
3286
|
+
for (const line of shown) this.push(`${indent}${style(line)}`);
|
|
3287
|
+
if (shown.length < wrapped.length) {
|
|
3288
|
+
this.push(`${indent}${chalk4.dim(`\u2026 ${wrapped.length - shown.length} more lines`)}`);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
heading(section, detail) {
|
|
3292
|
+
const title = section.toUpperCase();
|
|
3293
|
+
const room = this.width - title.length - 3;
|
|
3294
|
+
const extra = detail && room >= 8 ? ` ${chalk4.dim(truncateText(detail, room))}` : "";
|
|
3295
|
+
this.push("");
|
|
3296
|
+
this.push(`${accent2("\u258D")}${accent2.bold(title)}${extra}`);
|
|
3297
|
+
}
|
|
3298
|
+
settingsGrid(entries, indent = " ") {
|
|
3299
|
+
if (entries.length === 0) return;
|
|
3300
|
+
const rows2 = entries.map(([key, value]) => [clean(key), value]);
|
|
3301
|
+
const compact = rows2.every(([, value]) => value === null || ["number", "boolean"].includes(typeof value) || typeof value === "string" && value.length <= 16);
|
|
3302
|
+
if (compact && rows2.length > 4) {
|
|
3303
|
+
const keyWidth2 = Math.max(...rows2.map(([key]) => key.length));
|
|
3304
|
+
const valueWidth = Math.max(...rows2.map(([, value]) => String(value).length));
|
|
3305
|
+
const cell = keyWidth2 + 2 + valueWidth;
|
|
3306
|
+
const columns = Math.min(3, Math.floor((this.width - indent.length + 4) / (cell + 4)));
|
|
3307
|
+
if (columns >= 2) {
|
|
3308
|
+
const height = Math.ceil(rows2.length / columns);
|
|
3309
|
+
for (let row = 0; row < height; row += 1) {
|
|
3310
|
+
const cells = [];
|
|
3311
|
+
for (let column = 0; column < columns; column += 1) {
|
|
3312
|
+
const entry = rows2[column * height + row];
|
|
3313
|
+
if (!entry) continue;
|
|
3314
|
+
const [key, value] = entry;
|
|
3315
|
+
const text2 = String(value);
|
|
3316
|
+
cells.push(`${keyColor(key.padEnd(keyWidth2))} ${formatScalar(typeof value === "string" ? clean(value) : value)}${" ".repeat(valueWidth - text2.length)}`);
|
|
3317
|
+
}
|
|
3318
|
+
this.push(`${indent}${cells.join(" ").trimEnd()}`);
|
|
3319
|
+
}
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
const keyWidth = Math.min(28, Math.max(...rows2.map(([key]) => key.length)));
|
|
3324
|
+
for (const [key, value] of rows2) {
|
|
3325
|
+
const label = key.length > keyWidth ? truncateText(key, keyWidth) : key.padEnd(keyWidth);
|
|
3326
|
+
const rendered = typeof value === "string" ? stringColor(truncateText(value, Math.max(20, this.width - keyWidth - indent.length - 2))) : formatScalar(value);
|
|
3327
|
+
this.push(`${indent}${keyColor(label)} ${rendered}`);
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
function stringList(value) {
|
|
3332
|
+
return Array.isArray(value) ? value.map((item) => typeof item === "string" ? item : JSON.stringify(item)) : [];
|
|
3333
|
+
}
|
|
3334
|
+
function exampleText(value) {
|
|
3335
|
+
if (typeof value === "string") return value;
|
|
3336
|
+
if (value === void 0) return "";
|
|
3337
|
+
return JSON.stringify(value);
|
|
3338
|
+
}
|
|
3339
|
+
function renderIdentity(view, spec, full) {
|
|
3340
|
+
if (!full) return;
|
|
3341
|
+
view.heading("identity", SECTION_BLURBS.identity);
|
|
3342
|
+
view.settingsGrid(SECTION_KEYS.identity.filter((key) => spec[key] !== void 0).map((key) => [key, spec[key]]));
|
|
3343
|
+
if (spec.engine === void 0) view.push(` ${chalk4.dim("engine defaults to adapter (LoRA on base_model)")}`);
|
|
3344
|
+
}
|
|
3345
|
+
function renderBehavior(view, spec, full) {
|
|
3346
|
+
const guidelines = stringList(spec.guidelines);
|
|
3347
|
+
const constraints = stringList(spec.constraints);
|
|
3348
|
+
view.heading("behavior", SECTION_BLURBS.behavior);
|
|
3349
|
+
view.push(` ${chalk4.bold("System prompt")}`);
|
|
3350
|
+
if (typeof spec.system_prompt === "string" && spec.system_prompt.trim()) {
|
|
3351
|
+
view.paragraph(spec.system_prompt, ` ${soft("\u2502")} `, chalk4.italic, full ? void 0 : 6);
|
|
3352
|
+
} else {
|
|
3353
|
+
view.push(` ${soft("\u2502")} ${chalk4.dim("(none)")}`);
|
|
3354
|
+
}
|
|
3355
|
+
const renderList = (label, items, mark) => {
|
|
3356
|
+
view.push(` ${chalk4.bold(label)} ${chalk4.dim(String(items.length))}`);
|
|
3357
|
+
if (items.length === 0) {
|
|
3358
|
+
view.push(` ${chalk4.dim("(none)")}`);
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
const limit = full ? items.length : 5;
|
|
3362
|
+
for (const item of items.slice(0, limit)) {
|
|
3363
|
+
const before = view.lines.length;
|
|
3364
|
+
view.paragraph(item, " ", void 0, full ? void 0 : 2);
|
|
3365
|
+
view.lines[before] = ` ${mark} ${view.lines[before].slice(6)}`;
|
|
3366
|
+
}
|
|
3367
|
+
if (items.length > limit) view.push(` ${chalk4.dim(`\u2026 ${items.length - limit} more \xB7 /spec behavior`)}`);
|
|
3368
|
+
};
|
|
3369
|
+
if (isObject2(spec.decision)) {
|
|
3370
|
+
view.push(` ${chalk4.bold("Decision question")}`);
|
|
3371
|
+
view.settingsGrid(flattenSettings(spec.decision));
|
|
3372
|
+
}
|
|
3373
|
+
renderList("Guidelines", guidelines, chalk4.green("\u2713"));
|
|
3374
|
+
renderList("Constraints", constraints, chalk4.red("\u2717"));
|
|
3375
|
+
}
|
|
3376
|
+
function renderExamples(view, spec, full) {
|
|
3377
|
+
const examples = Array.isArray(spec.examples) ? spec.examples : [];
|
|
3378
|
+
const count = examples.length;
|
|
3379
|
+
const size = count === 0 ? "none yet" : `${count} example${count === 1 ? "" : "s"}`;
|
|
3380
|
+
view.heading("examples", `${size} \xB7 ${SECTION_BLURBS.examples}`);
|
|
3381
|
+
if (count === 0) {
|
|
3382
|
+
view.push(` ${chalk4.dim("Ask TT to draft examples, or add input/output pairs to tunedtensor.json.")}`);
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
const limit = full ? count : 3;
|
|
3386
|
+
const number2 = String(Math.min(count, limit)).length;
|
|
3387
|
+
const textWidth = Math.max(20, view.width - number2 - 10);
|
|
3388
|
+
examples.slice(0, limit).forEach((example, index) => {
|
|
3389
|
+
const record4 = isObject2(example) ? example : { input: example };
|
|
3390
|
+
const label = chalk4.dim(String(index + 1).padStart(number2));
|
|
3391
|
+
const pad = " ".repeat(number2);
|
|
3392
|
+
if (full) {
|
|
3393
|
+
const before = view.lines.length;
|
|
3394
|
+
view.paragraph(exampleText(record4.input), ` ${pad} ${chalk4.cyan("in ")} `);
|
|
3395
|
+
view.lines[before] = view.lines[before].replace(` ${pad}`, ` ${label}`);
|
|
3396
|
+
view.paragraph(exampleText(record4.output), ` ${pad} ${chalk4.green("out")} `, stringColor);
|
|
3397
|
+
view.push("");
|
|
3398
|
+
} else {
|
|
3399
|
+
view.push(` ${label} ${chalk4.cyan("in ")} ${truncateText(exampleText(record4.input), textWidth)}`);
|
|
3400
|
+
view.push(` ${pad} ${chalk4.green("out")} ${stringColor(truncateText(exampleText(record4.output), textWidth))}`);
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
if (full && view.lines.at(-1) === "") view.lines.pop();
|
|
3404
|
+
if (count > limit) view.push(` ${chalk4.dim(`\u2026 ${count - limit} more \xB7 /spec examples shows all`)}`);
|
|
3405
|
+
}
|
|
3406
|
+
function renderTraining(view, spec) {
|
|
3407
|
+
const foundation = spec.engine === "foundation";
|
|
3408
|
+
const settings = foundation ? spec.foundation : spec.hyperparameters;
|
|
3409
|
+
view.heading("training", foundation ? "foundation \xB7 tokenizer, pretrain, SFT" : spec.engine === "decision" ? "decision \xB7 typed classification" : `adapter \xB7 LoRA on ${typeof spec.base_model === "string" ? clean(spec.base_model) : "base_model"}`);
|
|
3410
|
+
const rows2 = isObject2(settings) ? flattenSettings(settings) : [];
|
|
3411
|
+
if (rows2.length === 0) {
|
|
3412
|
+
view.push(` ${chalk4.dim("Defaults \u2014 no overrides set.")}`);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
view.settingsGrid(rows2);
|
|
3416
|
+
const other = foundation ? spec.hyperparameters : spec.foundation;
|
|
3417
|
+
if (isObject2(other)) view.push(` ${chalk4.yellow("!")} ${chalk4.dim(`${foundation ? "hyperparameters" : "foundation"} is set but unused by this engine`)}`);
|
|
3418
|
+
}
|
|
3419
|
+
function renderSettingsSection(view, spec, section, full) {
|
|
3420
|
+
const value = spec[section];
|
|
3421
|
+
if (value === void 0) {
|
|
3422
|
+
if (full) {
|
|
3423
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3424
|
+
view.push(` ${chalk4.dim(section === "runtime" ? "Not set \u2014 runs on this machine with default paths." : section === "pipeline" ? "Not set \u2014 TT derives the default pipeline from this spec." : "Not set \u2014 default scoring.")}`);
|
|
3425
|
+
}
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3428
|
+
if (section === "pipeline" && !full) {
|
|
3429
|
+
const steps = isObject2(value) && Array.isArray(value.steps) ? value.steps.length : void 0;
|
|
3430
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3431
|
+
view.push(` ${steps === void 0 ? "custom recipe" : `${steps} custom step${steps === 1 ? "" : "s"}`} ${chalk4.dim("\xB7 /spec pipeline")}`);
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
view.heading(section, SECTION_BLURBS[section]);
|
|
3435
|
+
if (section === "pipeline") {
|
|
3436
|
+
for (const line of JSON.stringify(value, null, 2).split("\n")) view.push(` ${highlightJson(line)}`);
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
view.settingsGrid(flattenSettings(value));
|
|
3440
|
+
}
|
|
3441
|
+
function renderOther(view, spec) {
|
|
3442
|
+
const rows2 = Object.entries(spec).filter(([key]) => !KNOWN_KEYS.has(key));
|
|
3443
|
+
if (rows2.length === 0) return;
|
|
3444
|
+
view.push("");
|
|
3445
|
+
view.push(`${accent2("\u258D")}${accent2.bold("OTHER")}`);
|
|
3446
|
+
view.settingsGrid(rows2);
|
|
3447
|
+
}
|
|
3448
|
+
function renderHeader(view, input, spec) {
|
|
3449
|
+
const name = typeof spec.name === "string" && spec.name.trim() ? clean(spec.name) : "Unnamed spec";
|
|
3450
|
+
const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red(`\u2717 ${input.validation.errors.length} issue${input.validation.errors.length === 1 ? "" : "s"}`);
|
|
3451
|
+
const gap = Math.max(2, view.width - terminalWidth(name) - terminalWidth(status2));
|
|
3452
|
+
view.push(`${chalk4.bold(name)}${" ".repeat(gap)}${status2}`);
|
|
3453
|
+
if (typeof spec.description === "string" && spec.description.trim()) {
|
|
3454
|
+
view.paragraph(spec.description, "", chalk4.dim, 3);
|
|
3455
|
+
}
|
|
3456
|
+
const examples = Array.isArray(spec.examples) ? spec.examples.length : 0;
|
|
3457
|
+
const facts = [
|
|
3458
|
+
spec.engine === "foundation" ? "foundation" : spec.engine === "decision" ? "decision" : "adapter",
|
|
3459
|
+
typeof spec.base_model === "string" ? truncateText(spec.base_model, view.width) : void 0,
|
|
3460
|
+
`${examples} example${examples === 1 ? "" : "s"}`,
|
|
3461
|
+
isObject2(spec.runtime) && isObject2(spec.runtime.gpu) ? "remote GPU" : void 0
|
|
3462
|
+
].filter((fact) => Boolean(fact));
|
|
3463
|
+
view.lines.push(...joinFitting(facts, view.width));
|
|
3464
|
+
view.lines.push(...hintLines([truncateText(input.displayPath, view.width), `sha256 ${input.sha256.slice(0, 12)}`], view.width));
|
|
3465
|
+
}
|
|
3466
|
+
function renderDiagnostics(view, input) {
|
|
3467
|
+
const { errors, warnings, valid } = input.validation;
|
|
3468
|
+
if (valid && warnings.length === 0) return;
|
|
3469
|
+
view.push("");
|
|
3470
|
+
const marked = (text2, mark, style) => {
|
|
3471
|
+
const before = view.lines.length;
|
|
3472
|
+
view.paragraph(text2, " ", style);
|
|
3473
|
+
view.lines[before] = `${mark} ${view.lines[before].slice(2)}`;
|
|
3474
|
+
};
|
|
3475
|
+
for (const error of errors) marked(error, chalk4.red("\u2717"), chalk4.red);
|
|
3476
|
+
for (const warning of warnings) marked(warning, chalk4.yellow("!"), chalk4.yellow);
|
|
3477
|
+
}
|
|
3478
|
+
function renderInvalid(view, input) {
|
|
3479
|
+
view.push(`${chalk4.red("\u2717")} ${chalk4.bold(clean(input.displayPath))} ${chalk4.dim("could not be parsed")}`);
|
|
3480
|
+
for (const error of input.validation.errors) view.paragraph(error, " ", chalk4.red);
|
|
3481
|
+
view.push("");
|
|
3482
|
+
for (const line of clean(input.source).split("\n")) view.push(` ${chalk4.dim(line)}`);
|
|
3483
|
+
return `${view.lines.join("\n")}
|
|
3484
|
+
`;
|
|
3485
|
+
}
|
|
3486
|
+
function renderSpecView(input, options = {}) {
|
|
3487
|
+
const width = Math.max(40, Math.min(options.columns ?? 88, 88));
|
|
3488
|
+
const view = new ViewBuilder(width);
|
|
3489
|
+
if (!isObject2(input.document)) return renderInvalid(view, input);
|
|
3490
|
+
const spec = input.document;
|
|
3491
|
+
const section = options.section;
|
|
3492
|
+
if (!section) {
|
|
3493
|
+
renderHeader(view, input, spec);
|
|
3494
|
+
renderBehavior(view, spec, false);
|
|
3495
|
+
renderExamples(view, spec, false);
|
|
3496
|
+
renderTraining(view, spec);
|
|
3497
|
+
renderSettingsSection(view, spec, "evaluation", false);
|
|
3498
|
+
renderSettingsSection(view, spec, "runtime", false);
|
|
3499
|
+
renderSettingsSection(view, spec, "pipeline", false);
|
|
3500
|
+
renderOther(view, spec);
|
|
3501
|
+
renderDiagnostics(view, input);
|
|
3502
|
+
view.push("");
|
|
3503
|
+
view.lines.push(...hintLines(["/spec <section> for detail", "/spec show for JSON", "/spec diff", "/spec history"], width));
|
|
3504
|
+
view.lines.push(...wrapTerminalLine(`sections: ${SPEC_SECTIONS.join(", ")}`, width).map((line) => chalk4.dim(line)));
|
|
3505
|
+
return `${view.lines.join("\n")}
|
|
3506
|
+
`;
|
|
3507
|
+
}
|
|
3508
|
+
switch (section) {
|
|
3509
|
+
case "identity":
|
|
3510
|
+
renderIdentity(view, spec, true);
|
|
3511
|
+
break;
|
|
3512
|
+
case "behavior":
|
|
3513
|
+
renderBehavior(view, spec, true);
|
|
3514
|
+
break;
|
|
3515
|
+
case "examples":
|
|
3516
|
+
renderExamples(view, spec, true);
|
|
3517
|
+
break;
|
|
3518
|
+
case "training":
|
|
3519
|
+
renderTraining(view, spec);
|
|
3520
|
+
break;
|
|
3521
|
+
default:
|
|
3522
|
+
renderSettingsSection(view, spec, section, true);
|
|
3523
|
+
}
|
|
3524
|
+
view.lines.shift();
|
|
3525
|
+
return `${view.lines.join("\n")}
|
|
3526
|
+
`;
|
|
3527
|
+
}
|
|
3528
|
+
function renderSpecSource(input) {
|
|
3529
|
+
const status2 = input.validation.valid ? chalk4.green("\u2713 valid") : chalk4.red("\u2717 invalid");
|
|
3530
|
+
const lines = [
|
|
3531
|
+
`${chalk4.bold(clean(input.displayPath))} ${chalk4.dim(`\xB7 sha256 ${input.sha256.slice(0, 12)}`)} ${status2}`,
|
|
3532
|
+
highlightJson(input.source.replace(/\s+$/, "")),
|
|
3533
|
+
...input.validation.errors.map((error) => chalk4.red(`\u2717 ${clean(error)}`)),
|
|
3534
|
+
...input.validation.warnings.map((warning) => chalk4.yellow(`! ${clean(warning)}`))
|
|
3535
|
+
];
|
|
3536
|
+
return `${lines.join("\n")}
|
|
3537
|
+
`;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3034
3540
|
// src/commands/spec.ts
|
|
3035
3541
|
async function reviewSpec(workspaceRoot, operation = "show", specPath = "tunedtensor.json") {
|
|
3036
3542
|
if (!["show", "validate", "diff", "history"].includes(operation)) {
|
|
@@ -3083,8 +3589,14 @@ ${label}
|
|
|
3083
3589
|
${diff}` };
|
|
3084
3590
|
}
|
|
3085
3591
|
function registerSpecCommands(program, cwd) {
|
|
3086
|
-
program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]",
|
|
3087
|
-
const
|
|
3592
|
+
program.command("spec").description("Review the local behavior spec, validation, changes and history").argument("[operation]", `show, view, validate, diff, history, or a section (${SPEC_SECTIONS.join(", ")})`, "show").argument("[path]", "Workspace-relative tunedtensor.json", "tunedtensor.json").action(async (operation, path) => {
|
|
3593
|
+
const section = resolveSpecSection(operation);
|
|
3594
|
+
if ((section || operation === "view") && !isJsonMode()) {
|
|
3595
|
+
const spec = await inspectLocalSpec(cwd, path);
|
|
3596
|
+
process.stdout.write(renderSpecView(spec, { section, columns: process.stdout.columns }));
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3599
|
+
const result2 = await reviewSpec(cwd, section || operation === "view" ? "show" : operation, path);
|
|
3088
3600
|
if (isJsonMode()) {
|
|
3089
3601
|
const { text: _text, ...data3 } = result2;
|
|
3090
3602
|
printJson(data3);
|
|
@@ -3097,10 +3609,487 @@ function registerSpecCommands(program, cwd) {
|
|
|
3097
3609
|
});
|
|
3098
3610
|
}
|
|
3099
3611
|
|
|
3612
|
+
// src/shell-view.ts
|
|
3613
|
+
import chalk5 from "chalk";
|
|
3614
|
+
var accent3 = chalk5.hex("#8B5CF6");
|
|
3615
|
+
var GIB = 1024 ** 3;
|
|
3616
|
+
var BAR_WIDTH = 10;
|
|
3617
|
+
var MAX_PANEL_WIDTH = 88;
|
|
3618
|
+
function panelWidth(columns) {
|
|
3619
|
+
return Math.max(40, Math.min(columns ?? 80, MAX_PANEL_WIDTH));
|
|
3620
|
+
}
|
|
3621
|
+
function formatGiB(bytes) {
|
|
3622
|
+
const amount = bytes / GIB;
|
|
3623
|
+
return amount >= 100 ? amount.toFixed(0) : amount.toFixed(1);
|
|
3624
|
+
}
|
|
3625
|
+
function loadColor(percent2) {
|
|
3626
|
+
if (percent2 >= 85) return chalk5.red;
|
|
3627
|
+
if (percent2 >= 60) return chalk5.yellow;
|
|
3628
|
+
return chalk5.green;
|
|
3629
|
+
}
|
|
3630
|
+
function usageBar(percent2, width = BAR_WIDTH) {
|
|
3631
|
+
if (percent2 === void 0) return chalk5.dim("\xB7".repeat(width));
|
|
3632
|
+
const clamped = Math.max(0, Math.min(100, percent2));
|
|
3633
|
+
const filled = Math.round(clamped / 100 * width);
|
|
3634
|
+
return `${loadColor(clamped)("\u2588".repeat(filled))}${chalk5.dim("\u2591".repeat(width - filled))}`;
|
|
3635
|
+
}
|
|
3636
|
+
function percentLabel(percent2) {
|
|
3637
|
+
if (percent2 === void 0) return chalk5.dim(" n/a");
|
|
3638
|
+
return loadColor(percent2)(`${String(Math.round(percent2)).padStart(3)}%`).padStart(4);
|
|
3639
|
+
}
|
|
3640
|
+
function sectionRule(title, width, detail) {
|
|
3641
|
+
const label = `${accent3.bold(title)}${detail ? ` ${chalk5.dim(detail)}` : ""}`;
|
|
3642
|
+
const rule = Math.max(3, width - terminalWidth(label) - 1);
|
|
3643
|
+
return `${label} ${chalk5.dim("\u2500".repeat(rule))}`;
|
|
3644
|
+
}
|
|
3645
|
+
function inventoryFromUsage(usage) {
|
|
3646
|
+
const nvidia = usage.gpus.filter((gpu) => gpu.vendor === "nvidia");
|
|
3647
|
+
return {
|
|
3648
|
+
collected_at: usage.sampled_at,
|
|
3649
|
+
quick: true,
|
|
3650
|
+
node: { version: process.versions.node, major: Number(process.versions.node.split(".")[0]), ok: true },
|
|
3651
|
+
os: {
|
|
3652
|
+
platform: process.platform,
|
|
3653
|
+
arch: process.arch,
|
|
3654
|
+
type: process.platform,
|
|
3655
|
+
cpu_count: usage.cpu.cores,
|
|
3656
|
+
total_memory_bytes: usage.memory.total_bytes,
|
|
3657
|
+
free_memory_bytes: usage.memory.total_bytes - usage.memory.used_bytes
|
|
3658
|
+
},
|
|
3659
|
+
gpus: nvidia.map((gpu) => ({
|
|
3660
|
+
index: gpu.index,
|
|
3661
|
+
name: gpu.name,
|
|
3662
|
+
memory_total_bytes: gpu.memory_total_bytes,
|
|
3663
|
+
memory_free_bytes: gpu.memory_total_bytes !== void 0 && gpu.memory_used_bytes !== void 0 ? gpu.memory_total_bytes - gpu.memory_used_bytes : void 0,
|
|
3664
|
+
unified_memory: gpu.unified_memory
|
|
3665
|
+
})),
|
|
3666
|
+
nvidia_smi: { ok: nvidia.length > 0, message: usage.gpu_note ?? "" },
|
|
3667
|
+
disks: []
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function shortModel(id, width = 24) {
|
|
3671
|
+
return truncateText(id.split("/").at(-1) ?? id, width);
|
|
3672
|
+
}
|
|
3673
|
+
var STATUS_WORD = {
|
|
3674
|
+
ready: "ready",
|
|
3675
|
+
tight: "tight",
|
|
3676
|
+
not_possible: "not possible"
|
|
3677
|
+
};
|
|
3678
|
+
function describeFineTuneFit(usage, context) {
|
|
3679
|
+
const spec = context.spec;
|
|
3680
|
+
if (spec?.remoteGpu) {
|
|
3681
|
+
return {
|
|
3682
|
+
status: "remote",
|
|
3683
|
+
text: `training runs on AWS ${spec.remoteGpu} (runtime.gpu)`,
|
|
3684
|
+
source: "tt doctor checks it"
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
const snapshot = context.host && !context.host.stale ? context.host.capabilities : void 0;
|
|
3688
|
+
let report = snapshot;
|
|
3689
|
+
const source = snapshot ? "from tt hardware" : "quick check \xB7 tt hardware for a full probe";
|
|
3690
|
+
if (!report && usage) report = evaluateCapabilities(inventoryFromUsage(usage));
|
|
3691
|
+
if (!report) return { status: "unknown", text: "not checked yet", source: "run tt hardware" };
|
|
3692
|
+
if (spec?.engine === "decision") {
|
|
3693
|
+
return {
|
|
3694
|
+
status: "unknown",
|
|
3695
|
+
text: "decision training runs locally on CPU, MPS or CUDA",
|
|
3696
|
+
source: "device selected by hyperparameters.device"
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
const noCuda = !report.cuda_available;
|
|
3700
|
+
if (spec?.engine === "foundation") {
|
|
3701
|
+
const verdict = report.foundation.train;
|
|
3702
|
+
return {
|
|
3703
|
+
status: verdict.status,
|
|
3704
|
+
text: verdict.status === "not_possible" ? `foundation training ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}` : `foundation training ${STATUS_WORD[verdict.status]} \xB7 suggested max depth ${report.foundation.suggested_max_depth}`,
|
|
3705
|
+
source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
if (spec?.baseModel) {
|
|
3709
|
+
const adapter = report.adapters.find((item) => item.id.toLowerCase() === spec.baseModel.toLowerCase());
|
|
3710
|
+
if (!adapter) {
|
|
3711
|
+
return {
|
|
3712
|
+
status: "unknown",
|
|
3713
|
+
text: `no certified memory profile for ${shortModel(spec.baseModel, 32)}`,
|
|
3714
|
+
source: "tt hardware lists supported base models"
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
const verdict = adapter.finetune;
|
|
3718
|
+
return {
|
|
3719
|
+
status: verdict.status,
|
|
3720
|
+
text: `LoRA ${shortModel(adapter.id)} ${STATUS_WORD[verdict.status]} \u2014 ${verdict.reason}`,
|
|
3721
|
+
source: noCuda ? "set runtime.gpu to train on your AWS GPU" : source
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
const ready = report.adapters.filter((item) => item.finetune.status !== "not_possible");
|
|
3725
|
+
if (ready.length === 0) {
|
|
3726
|
+
return {
|
|
3727
|
+
status: "not_possible",
|
|
3728
|
+
text: noCuda ? "no local CUDA GPU for LoRA training" : "no certified base model fits this GPU",
|
|
3729
|
+
source: noCuda ? "runtime.gpu can use your AWS GPU" : source
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
const best = ready.some((item) => item.finetune.status === "ready") ? "ready" : "tight";
|
|
3733
|
+
return {
|
|
3734
|
+
status: best,
|
|
3735
|
+
text: `LoRA ${best} for ${ready.map((item) => shortModel(item.id, 20)).join(", ")}`,
|
|
3736
|
+
source
|
|
3737
|
+
};
|
|
3738
|
+
}
|
|
3739
|
+
function fitMark(status2) {
|
|
3740
|
+
switch (status2) {
|
|
3741
|
+
case "ready":
|
|
3742
|
+
return chalk5.green("\u2713");
|
|
3743
|
+
case "tight":
|
|
3744
|
+
return chalk5.yellow("!");
|
|
3745
|
+
case "not_possible":
|
|
3746
|
+
return chalk5.red("\u2717");
|
|
3747
|
+
case "remote":
|
|
3748
|
+
return accent3("\u2197");
|
|
3749
|
+
default:
|
|
3750
|
+
return chalk5.dim("?");
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
function gpuDetail(gpu) {
|
|
3754
|
+
const parts = [];
|
|
3755
|
+
if (gpu.vendor === "apple") {
|
|
3756
|
+
parts.push(`${formatGiB(gpu.memory_total_bytes ?? 0)} GiB unified \xB7 Metal`);
|
|
3757
|
+
} else if (gpu.memory_total_bytes !== void 0) {
|
|
3758
|
+
const used = gpu.memory_used_bytes;
|
|
3759
|
+
parts.push(used === void 0 ? `${formatGiB(gpu.memory_total_bytes)} GiB VRAM` : `VRAM ${formatGiB(used)}/${formatGiB(gpu.memory_total_bytes)} GiB`);
|
|
3760
|
+
} else if (gpu.unified_memory) {
|
|
3761
|
+
parts.push("unified with system RAM");
|
|
3762
|
+
}
|
|
3763
|
+
if (gpu.temperature_c !== void 0) parts.push(`${Math.round(gpu.temperature_c)}\xB0C`);
|
|
3764
|
+
return parts.join(" ");
|
|
3765
|
+
}
|
|
3766
|
+
function renderMeterRows(rows2, width) {
|
|
3767
|
+
const detailWidth = Math.max(0, ...rows2.map((row) => terminalWidth(row.detail)));
|
|
3768
|
+
const fixed = 6 + 1 + 1 + BAR_WIDTH + 1 + 4 + 2;
|
|
3769
|
+
const nameWidth = Math.max(6, Math.min(28, width - fixed - detailWidth));
|
|
3770
|
+
const detailRoom = Math.max(8, width - fixed - nameWidth);
|
|
3771
|
+
return rows2.map((row) => {
|
|
3772
|
+
const nameCell = truncateText(row.name, nameWidth).padEnd(nameWidth);
|
|
3773
|
+
return `${chalk5.bold(row.label.padEnd(6))} ${nameCell} ${usageBar(row.percent)} ${percentLabel(row.percent)} ${chalk5.dim(truncateText(row.detail, detailRoom))}`;
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
function renderMachinePanel(usage, context, options = {}) {
|
|
3777
|
+
const width = panelWidth(options.columns);
|
|
3778
|
+
const lines = [sectionRule("MACHINE", width)];
|
|
3779
|
+
if (usage.gpus.length === 0) {
|
|
3780
|
+
const reason = usage.gpu_note ? ` (${usage.gpu_note})` : "";
|
|
3781
|
+
lines.push(`${chalk5.bold("GPU".padEnd(6))} ${chalk5.dim(truncateText(`no NVIDIA GPU detected${reason}`, width - 7))}`);
|
|
3782
|
+
}
|
|
3783
|
+
const meters = usage.gpus.map((gpu) => ({
|
|
3784
|
+
label: usage.gpus.length > 1 ? `GPU ${gpu.index}` : "GPU",
|
|
3785
|
+
// "NVIDIA GeForce RTX 4090" reads better as "GeForce RTX 4090" in a narrow column.
|
|
3786
|
+
name: sanitizeTerminalText(gpu.name).replace(/^NVIDIA\s+(?=\S)/, ""),
|
|
3787
|
+
percent: gpu.utilization_percent,
|
|
3788
|
+
detail: gpuDetail(gpu)
|
|
3789
|
+
}));
|
|
3790
|
+
meters.push({
|
|
3791
|
+
label: "CPU",
|
|
3792
|
+
name: `${usage.cpu.cores}\xD7 ${sanitizeTerminalText(usage.cpu.model)}`,
|
|
3793
|
+
percent: usage.cpu.utilization_percent,
|
|
3794
|
+
detail: `load ${usage.cpu.load_average[0].toFixed(2)}`
|
|
3795
|
+
});
|
|
3796
|
+
meters.push({
|
|
3797
|
+
label: "RAM",
|
|
3798
|
+
name: "system memory",
|
|
3799
|
+
percent: usage.memory.total_bytes > 0 ? usage.memory.used_bytes / usage.memory.total_bytes * 100 : void 0,
|
|
3800
|
+
detail: `${formatGiB(usage.memory.used_bytes)}/${formatGiB(usage.memory.total_bytes)} GiB`
|
|
3801
|
+
});
|
|
3802
|
+
if (usage.disk && usage.disk.total_bytes > 0) {
|
|
3803
|
+
meters.push({
|
|
3804
|
+
label: "Disk",
|
|
3805
|
+
name: "model cache",
|
|
3806
|
+
percent: (usage.disk.total_bytes - usage.disk.free_bytes) / usage.disk.total_bytes * 100,
|
|
3807
|
+
detail: `${formatGiB(usage.disk.free_bytes)} GiB free`
|
|
3808
|
+
});
|
|
3809
|
+
}
|
|
3810
|
+
lines.push(...renderMeterRows(meters, width));
|
|
3811
|
+
const fit = describeFineTuneFit(usage, context);
|
|
3812
|
+
lines.push(`${chalk5.bold("Tune".padEnd(6))} ${fitMark(fit.status)} ${truncateText(fit.text, width - 9)}`);
|
|
3813
|
+
if (fit.source) lines.push(`${" ".repeat(9)}${chalk5.dim(truncateText(fit.source, width - 9))}`);
|
|
3814
|
+
if (options.detailed) {
|
|
3815
|
+
const report = context.host && !context.host.stale ? context.host.capabilities : evaluateCapabilities(inventoryFromUsage(usage));
|
|
3816
|
+
if (report) {
|
|
3817
|
+
lines.push("", sectionRule("CERTIFIED BASE MODELS", width, "LoRA fine-tune"));
|
|
3818
|
+
const idWidth = Math.min(44, Math.floor(width * 0.55));
|
|
3819
|
+
const reasonWidth = Math.max(10, width - idWidth - 3);
|
|
3820
|
+
const row = (status2, label, reason) => `${fitMark(status2)} ${truncateText(label, idWidth).padEnd(idWidth)} ${chalk5.dim(truncateText(reason, reasonWidth))}`;
|
|
3821
|
+
for (const adapter of report.adapters) {
|
|
3822
|
+
lines.push(row(adapter.finetune.status, adapter.id, adapter.finetune.reason));
|
|
3823
|
+
}
|
|
3824
|
+
const foundation = report.foundation.train;
|
|
3825
|
+
lines.push(row(
|
|
3826
|
+
foundation.status,
|
|
3827
|
+
"foundation engine (from scratch)",
|
|
3828
|
+
foundation.status === "not_possible" ? foundation.reason : `max depth ${report.foundation.suggested_max_depth}`
|
|
3829
|
+
));
|
|
3830
|
+
}
|
|
3831
|
+
lines.push("");
|
|
3832
|
+
const note = context.host ? `Full probe ${context.host.stale ? "is stale" : "cached"} from ${context.host.collectedAt.slice(0, 16).replace("T", " ")} \xB7 tt hardware refreshes it` : "No full probe yet \xB7 tt hardware checks CUDA, torch and disk space";
|
|
3833
|
+
lines.push(...wrapTerminalLine(note, width).map((line) => chalk5.dim(line)));
|
|
3834
|
+
}
|
|
3835
|
+
return lines;
|
|
3836
|
+
}
|
|
3837
|
+
function agentLabel(context) {
|
|
3838
|
+
const agent = context.agent;
|
|
3839
|
+
if (!agent?.provider || !agent.model) return void 0;
|
|
3840
|
+
return `${agent.provider}/${agent.model}`;
|
|
3841
|
+
}
|
|
3842
|
+
function bannerTip(input) {
|
|
3843
|
+
const { context, usage } = input;
|
|
3844
|
+
const agent = agentLabel(context);
|
|
3845
|
+
if (!agent) {
|
|
3846
|
+
return "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now.";
|
|
3847
|
+
}
|
|
3848
|
+
if (!context.spec) {
|
|
3849
|
+
return "No tunedtensor.json here yet. Describe the model you want and I'll draft a spec, or run init.";
|
|
3850
|
+
}
|
|
3851
|
+
if (context.spec.parseError) {
|
|
3852
|
+
return "tunedtensor.json doesn't parse. /spec show points at the problem.";
|
|
3853
|
+
}
|
|
3854
|
+
const busy = usage?.gpus.find((gpu) => (gpu.utilization_percent ?? 0) >= 85);
|
|
3855
|
+
if (busy) {
|
|
3856
|
+
return `${sanitizeTerminalText(busy.name)} is ${busy.utilization_percent}% busy; training now may be slow.`;
|
|
3857
|
+
}
|
|
3858
|
+
const examples = context.spec.exampleCount ?? 0;
|
|
3859
|
+
if (examples < 20) {
|
|
3860
|
+
return `${examples} example${examples === 1 ? "" : "s"} is a small set. Ask me to draft more, or /spec examples to review them.`;
|
|
3861
|
+
}
|
|
3862
|
+
return "Ask TT anything. Known commands run directly.";
|
|
3863
|
+
}
|
|
3864
|
+
function specLine(context) {
|
|
3865
|
+
const spec = context.spec;
|
|
3866
|
+
if (!spec) return `${chalk5.dim("spec")} ${chalk5.yellow("none here")} ${chalk5.dim("\xB7 init creates one")}`;
|
|
3867
|
+
if (spec.parseError) return `${chalk5.dim("spec")} ${chalk5.red("tunedtensor.json is invalid JSON")}`;
|
|
3868
|
+
const name = sanitizeTerminalText(spec.name ?? "unnamed");
|
|
3869
|
+
const facts = [
|
|
3870
|
+
spec.engine === "foundation" ? "foundation" : spec.baseModel ? sanitizeTerminalText(spec.baseModel) : void 0,
|
|
3871
|
+
spec.exampleCount === void 0 ? void 0 : `${spec.exampleCount} examples`
|
|
3872
|
+
].filter(Boolean).join(" \xB7 ");
|
|
3873
|
+
return `${chalk5.dim("spec")} ${chalk5.bold(name)}${facts ? chalk5.dim(` \xB7 ${facts}`) : ""}`;
|
|
3874
|
+
}
|
|
3875
|
+
function renderBanner(input) {
|
|
3876
|
+
const width = panelWidth(input.columns);
|
|
3877
|
+
const { context } = input;
|
|
3878
|
+
const tip = bannerTip(input);
|
|
3879
|
+
const heading = [
|
|
3880
|
+
`${accent3.bold("tt")}${input.version ? ` ${chalk5.dim(`v${input.version}`)}` : ""} ${chalk5.dim("Tuned Tensor")}`,
|
|
3881
|
+
specLine(context),
|
|
3882
|
+
chalk5.dim(`agent ${agentLabel(context) ?? "not configured"} \xB7 workflow model ${input.activeModel}`)
|
|
3883
|
+
];
|
|
3884
|
+
const mascot = renderMascot();
|
|
3885
|
+
const textWidth = width - MASCOT_WIDTH - 1;
|
|
3886
|
+
const offset = mascot.length - heading.length;
|
|
3887
|
+
const lines = mascot.map((row, index) => {
|
|
3888
|
+
const text2 = heading[index - offset] ?? "";
|
|
3889
|
+
return `${row} ${terminalWidth(text2) > textWidth ? truncateText(text2, textWidth) : text2}`;
|
|
3890
|
+
});
|
|
3891
|
+
if (input.usage) {
|
|
3892
|
+
lines.push("", ...renderMachinePanel(input.usage, context, { columns: input.columns }));
|
|
3893
|
+
}
|
|
3894
|
+
const prefix = mascotSays("");
|
|
3895
|
+
const hang = terminalWidth(prefix);
|
|
3896
|
+
const wrapped = wrapTerminalLine(tip, Math.max(20, width - hang));
|
|
3897
|
+
lines.push("", `${prefix}${wrapped[0]}`, ...wrapped.slice(1).map((line) => `${" ".repeat(hang)}${line}`));
|
|
3898
|
+
lines.push(...hintLines([
|
|
3899
|
+
"/spec review",
|
|
3900
|
+
"/system machine",
|
|
3901
|
+
"/help commands",
|
|
3902
|
+
"tab complete",
|
|
3903
|
+
"ctrl+c stop/clear",
|
|
3904
|
+
"ctrl+d exit"
|
|
3905
|
+
], width));
|
|
3906
|
+
return `${lines.join("\n")}
|
|
3907
|
+
|
|
3908
|
+
`;
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
// src/local-runtime/live-usage.ts
|
|
3912
|
+
import { execFile } from "child_process";
|
|
3913
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3914
|
+
import { statfs } from "fs/promises";
|
|
3915
|
+
import { cpus, freemem, homedir, loadavg, totalmem } from "os";
|
|
3916
|
+
import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
|
|
3917
|
+
function cpuTimes() {
|
|
3918
|
+
let idle = 0;
|
|
3919
|
+
let total = 0;
|
|
3920
|
+
for (const cpu of cpus()) {
|
|
3921
|
+
const times = cpu.times;
|
|
3922
|
+
idle += times.idle;
|
|
3923
|
+
total += times.user + times.nice + times.sys + times.irq + times.idle;
|
|
3924
|
+
}
|
|
3925
|
+
return { idle, total };
|
|
3926
|
+
}
|
|
3927
|
+
function cpuUtilization(before, after) {
|
|
3928
|
+
const total = after.total - before.total;
|
|
3929
|
+
if (!(total > 0)) return void 0;
|
|
3930
|
+
const busy = total - (after.idle - before.idle);
|
|
3931
|
+
return Math.min(100, Math.max(0, Math.round(busy / total * 100)));
|
|
3932
|
+
}
|
|
3933
|
+
function optionalNumber(value) {
|
|
3934
|
+
if (value === void 0) return void 0;
|
|
3935
|
+
const trimmed = value.trim();
|
|
3936
|
+
if (!trimmed || /^\[?n\/a\]?$/i.test(trimmed) || /not supported/i.test(trimmed)) return void 0;
|
|
3937
|
+
const parsed = Number(trimmed);
|
|
3938
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3939
|
+
}
|
|
3940
|
+
var MIB = 1024 * 1024;
|
|
3941
|
+
var NVIDIA_USAGE_QUERY = [
|
|
3942
|
+
"--query-gpu=index,name,utilization.gpu,memory.used,memory.total,temperature.gpu",
|
|
3943
|
+
"--format=csv,noheader,nounits"
|
|
3944
|
+
];
|
|
3945
|
+
function parseNvidiaUsage(stdout3) {
|
|
3946
|
+
const gpus = [];
|
|
3947
|
+
for (const line of stdout3.split(/\r?\n/).map((item) => item.trim()).filter(Boolean)) {
|
|
3948
|
+
const parts = line.split(",").map((part) => part.trim());
|
|
3949
|
+
if (parts.length < 2 || !/^\d+$/.test(parts[0] ?? "")) continue;
|
|
3950
|
+
const name = parts[1] || "NVIDIA GPU";
|
|
3951
|
+
const used = optionalNumber(parts[3]);
|
|
3952
|
+
const total = optionalNumber(parts[4]);
|
|
3953
|
+
gpus.push({
|
|
3954
|
+
index: Number(parts[0]),
|
|
3955
|
+
name,
|
|
3956
|
+
utilization_percent: optionalNumber(parts[2]),
|
|
3957
|
+
memory_used_bytes: used === void 0 ? void 0 : Math.round(used * MIB),
|
|
3958
|
+
memory_total_bytes: total === void 0 || total <= 0 ? void 0 : Math.round(total * MIB),
|
|
3959
|
+
temperature_c: optionalNumber(parts[5]),
|
|
3960
|
+
unified_memory: /spark|gb10/i.test(name),
|
|
3961
|
+
vendor: "nvidia"
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
return gpus;
|
|
3965
|
+
}
|
|
3966
|
+
function queryNvidia(env, timeoutMs) {
|
|
3967
|
+
return new Promise((resolve13) => {
|
|
3968
|
+
execFile("nvidia-smi", NVIDIA_USAGE_QUERY, { env, timeout: timeoutMs, windowsHide: true }, (error, stdout3) => {
|
|
3969
|
+
if (error) {
|
|
3970
|
+
const code2 = error.code;
|
|
3971
|
+
resolve13({
|
|
3972
|
+
gpus: [],
|
|
3973
|
+
note: code2 === "ENOENT" ? "nvidia-smi not found" : error.killed ? "nvidia-smi timed out" : "nvidia-smi failed"
|
|
3974
|
+
});
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3977
|
+
const gpus = parseNvidiaUsage(String(stdout3));
|
|
3978
|
+
resolve13(gpus.length > 0 ? { gpus } : { gpus, note: "nvidia-smi listed no GPUs" });
|
|
3979
|
+
});
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
function tidyCpuModel(model) {
|
|
3983
|
+
return model.replace(/\((?:R|TM|tm|r)\)/g, "").replace(/\s+(?:CPU|Processor)\b/gi, "").replace(/\s*@\s*[\d.]+\s*[GM]Hz/i, "").replace(/\s+\d+-Core$/i, "").replace(/\s+/g, " ").trim() || "CPU";
|
|
3984
|
+
}
|
|
3985
|
+
var ARM_CORES = {
|
|
3986
|
+
"0xd03": "Cortex-A53",
|
|
3987
|
+
"0xd05": "Cortex-A55",
|
|
3988
|
+
"0xd08": "Cortex-A72",
|
|
3989
|
+
"0xd0b": "Cortex-A76",
|
|
3990
|
+
"0xd0c": "Neoverse-N1",
|
|
3991
|
+
"0xd40": "Neoverse-V1",
|
|
3992
|
+
"0xd41": "Cortex-A78",
|
|
3993
|
+
"0xd44": "Cortex-X1",
|
|
3994
|
+
"0xd47": "Cortex-A710",
|
|
3995
|
+
"0xd48": "Cortex-X2",
|
|
3996
|
+
"0xd49": "Neoverse-N2",
|
|
3997
|
+
"0xd4d": "Cortex-A715",
|
|
3998
|
+
"0xd4e": "Cortex-X3",
|
|
3999
|
+
"0xd4f": "Neoverse-V2",
|
|
4000
|
+
"0xd80": "Cortex-A520",
|
|
4001
|
+
"0xd81": "Cortex-A720",
|
|
4002
|
+
"0xd82": "Cortex-X4",
|
|
4003
|
+
"0xd84": "Neoverse-V3",
|
|
4004
|
+
"0xd85": "Cortex-X925",
|
|
4005
|
+
"0xd87": "Cortex-A725",
|
|
4006
|
+
"0xd8e": "Neoverse-N3"
|
|
4007
|
+
};
|
|
4008
|
+
function armCpuModel(cpuinfo) {
|
|
4009
|
+
const names = [];
|
|
4010
|
+
let implementer;
|
|
4011
|
+
for (const line of cpuinfo.split(/\r?\n/)) {
|
|
4012
|
+
const [key, value] = line.split(":").map((item) => item.trim().toLowerCase());
|
|
4013
|
+
if (key === "cpu implementer") implementer = value;
|
|
4014
|
+
if (key !== "cpu part" || !value) continue;
|
|
4015
|
+
const name = implementer === "0x41" ? ARM_CORES[value] : void 0;
|
|
4016
|
+
if (name && !names.includes(name)) names.push(name);
|
|
4017
|
+
}
|
|
4018
|
+
return names.length > 0 ? names.join(" + ") : void 0;
|
|
4019
|
+
}
|
|
4020
|
+
function cpuModel(reported, platform, arch) {
|
|
4021
|
+
const model = tidyCpuModel(reported ?? "");
|
|
4022
|
+
if (platform !== "linux" || !/^(?:unknown|CPU)$/i.test(model)) return model;
|
|
4023
|
+
try {
|
|
4024
|
+
return armCpuModel(readFileSync5("/proc/cpuinfo", "utf8")) ?? (arch === "arm64" ? "Arm CPU" : model);
|
|
4025
|
+
} catch {
|
|
4026
|
+
return arch === "arm64" ? "Arm CPU" : model;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
async function modelCacheDisk(env) {
|
|
4030
|
+
const path = resolve2(env.HF_HOME?.trim() || join7(homedir(), ".cache", "huggingface"));
|
|
4031
|
+
for (let probe = path, depth = 0; depth < 8; depth += 1) {
|
|
4032
|
+
try {
|
|
4033
|
+
const fs = await statfs(probe);
|
|
4034
|
+
return {
|
|
4035
|
+
path,
|
|
4036
|
+
free_bytes: Number(fs.bavail) * Number(fs.bsize),
|
|
4037
|
+
total_bytes: Number(fs.blocks) * Number(fs.bsize)
|
|
4038
|
+
};
|
|
4039
|
+
} catch {
|
|
4040
|
+
const parent = dirname3(probe);
|
|
4041
|
+
if (parent === probe) break;
|
|
4042
|
+
probe = parent;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
return void 0;
|
|
4046
|
+
}
|
|
4047
|
+
function appleGpu(cpuModel2, platform, arch) {
|
|
4048
|
+
if (platform !== "darwin" || arch !== "arm64") return void 0;
|
|
4049
|
+
const chip = /apple/i.test(cpuModel2) ? cpuModel2.trim() : "Apple Silicon";
|
|
4050
|
+
return {
|
|
4051
|
+
index: 0,
|
|
4052
|
+
name: `${chip} GPU`,
|
|
4053
|
+
memory_total_bytes: totalmem(),
|
|
4054
|
+
unified_memory: true,
|
|
4055
|
+
vendor: "apple"
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
async function sampleLiveUsage(options = {}) {
|
|
4059
|
+
const env = options.env ?? process.env;
|
|
4060
|
+
const platform = options.platform ?? process.platform;
|
|
4061
|
+
const arch = options.arch ?? process.arch;
|
|
4062
|
+
const before = cpuTimes();
|
|
4063
|
+
const [nvidia, disk] = await Promise.all([
|
|
4064
|
+
queryNvidia(env, options.gpuTimeoutMs ?? 2e3),
|
|
4065
|
+
modelCacheDisk(env).catch(() => void 0),
|
|
4066
|
+
new Promise((resolve13) => setTimeout(resolve13, options.cpuSampleMs ?? 200))
|
|
4067
|
+
]);
|
|
4068
|
+
const after = cpuTimes();
|
|
4069
|
+
const list = cpus();
|
|
4070
|
+
const model = cpuModel(list[0]?.model, platform, arch);
|
|
4071
|
+
const total = totalmem();
|
|
4072
|
+
const apple = nvidia.gpus.length === 0 ? appleGpu(model, platform, arch) : void 0;
|
|
4073
|
+
const [one, five, fifteen] = loadavg();
|
|
4074
|
+
return {
|
|
4075
|
+
sampled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4076
|
+
cpu: {
|
|
4077
|
+
model,
|
|
4078
|
+
cores: list.length,
|
|
4079
|
+
utilization_percent: cpuUtilization(before, after),
|
|
4080
|
+
load_average: [one ?? 0, five ?? 0, fifteen ?? 0]
|
|
4081
|
+
},
|
|
4082
|
+
memory: { used_bytes: Math.max(0, total - freemem()), total_bytes: total },
|
|
4083
|
+
disk,
|
|
4084
|
+
gpus: apple ? [apple] : nvidia.gpus,
|
|
4085
|
+
gpu_note: apple ? void 0 : nvidia.note
|
|
4086
|
+
};
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3100
4089
|
// src/shell.ts
|
|
3101
4090
|
import { stat as stat3 } from "fs/promises";
|
|
3102
|
-
import { homedir } from "os";
|
|
3103
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
4091
|
+
import { homedir as homedir2 } from "os";
|
|
4092
|
+
import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
3104
4093
|
import { createInterface as createInterface2 } from "readline";
|
|
3105
4094
|
import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
3106
4095
|
|
|
@@ -3108,6 +4097,7 @@ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
|
|
|
3108
4097
|
var LOCAL = ["local"];
|
|
3109
4098
|
var COMMAND_CATALOG = [
|
|
3110
4099
|
{ path: "spec", description: "Review the current behavior spec.", group: "Workflow", modes: LOCAL },
|
|
4100
|
+
{ path: "spec view", description: "Readable, highlighted behavior spec; add a section name for detail.", group: "Workflow", modes: LOCAL },
|
|
3111
4101
|
...["show", "diff", "validate", "history"].map((operation) => ({ path: `spec ${operation}`, description: `Review behavior spec ${operation}.`, group: "Workflow", modes: LOCAL })),
|
|
3112
4102
|
{ path: "init", description: "Create a local behaviour-spec project.", group: "Workflow", modes: LOCAL },
|
|
3113
4103
|
{ path: "validate", description: "Validate a local fine-tuning project.", group: "Workflow", modes: LOCAL },
|
|
@@ -3123,12 +4113,15 @@ var COMMAND_CATALOG = [
|
|
|
3123
4113
|
{ path: "runs report", description: "Show a local run report.", group: "Inspect", modes: LOCAL },
|
|
3124
4114
|
{ path: "runs compare", description: "Compare two local run reports.", group: "Inspect", modes: LOCAL },
|
|
3125
4115
|
{ path: "runs events", description: "Show local run progress events.", group: "Inspect", modes: LOCAL },
|
|
4116
|
+
{ path: "runs audit", description: "Verify a run's event log and report, with timeline and provenance.", group: "Inspect", modes: LOCAL },
|
|
3126
4117
|
{ path: "models list", description: "List local models.", group: "Inspect", modes: LOCAL },
|
|
3127
4118
|
{ path: "models get", description: "Show a local model.", group: "Inspect", modes: LOCAL },
|
|
3128
4119
|
{ path: "models serve", description: "Serve a model through an OpenAI-compatible API.", group: "Serving", modes: LOCAL },
|
|
3129
4120
|
{ path: "models verify", description: "Verify a local model artifact.", group: "Serving", modes: LOCAL },
|
|
3130
4121
|
{ path: "models prefetch", description: "Download the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
3131
4122
|
{ path: "models verify-base", description: "Verify the local base-model snapshot.", group: "Serving", modes: LOCAL },
|
|
4123
|
+
{ path: "datasets prefetch", description: "Download the spec's Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
|
|
4124
|
+
{ path: "datasets verify", description: "Verify the cached Hugging Face dataset files.", group: "Workflow", modes: LOCAL },
|
|
3132
4125
|
{ path: "models active", description: "Show the active local model.", group: "Serving", modes: LOCAL },
|
|
3133
4126
|
{ path: "models activate", description: "Activate a verified local model.", group: "Serving", modes: LOCAL },
|
|
3134
4127
|
{ path: "models rollback", description: "Roll back the active local model.", group: "Serving", modes: LOCAL },
|
|
@@ -3184,7 +4177,8 @@ var COMMAND_CATALOG = [
|
|
|
3184
4177
|
{ path: "agent status", description: "Show the laptop-local TT agent selection.", group: "Inspect", modes: LOCAL }
|
|
3185
4178
|
];
|
|
3186
4179
|
var SLASH_COMMANDS = [
|
|
3187
|
-
{ path: "/spec", description: "
|
|
4180
|
+
{ path: "/spec", description: "Readable behavior spec; add a section, show, diff, validate or history." },
|
|
4181
|
+
{ path: "/system", description: "Live GPU, CPU and memory use, and what this machine can fine-tune." },
|
|
3188
4182
|
{ path: "/help", description: "Show commands; add a word to filter." },
|
|
3189
4183
|
{ path: "/status", description: "Show lightweight workflow status." },
|
|
3190
4184
|
{ path: "/context", description: "Show the current project context." },
|
|
@@ -3246,7 +4240,7 @@ function createCommandCompleter(getMode = () => "local") {
|
|
|
3246
4240
|
|
|
3247
4241
|
// src/shell-context.ts
|
|
3248
4242
|
import { readFile as readFile3, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3249
|
-
import { basename as basename2, dirname as
|
|
4243
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve3 } from "path";
|
|
3250
4244
|
async function readJsonObject(path) {
|
|
3251
4245
|
try {
|
|
3252
4246
|
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
@@ -3273,7 +4267,7 @@ function expandPath(value, baseDirectory, env) {
|
|
|
3273
4267
|
return expandUserPath(value, env, baseDirectory);
|
|
3274
4268
|
}
|
|
3275
4269
|
function configPath(env) {
|
|
3276
|
-
return
|
|
4270
|
+
return join8(getConfigDir(env), "config.json");
|
|
3277
4271
|
}
|
|
3278
4272
|
function agentSelectionFrom(env, configAgent, hasToken) {
|
|
3279
4273
|
const stored = configAgent && typeof configAgent === "object" && !Array.isArray(configAgent) ? configAgent : {};
|
|
@@ -3291,14 +4285,14 @@ async function adjacentFile(path) {
|
|
|
3291
4285
|
}
|
|
3292
4286
|
}
|
|
3293
4287
|
async function readActiveModelId(storeRoot) {
|
|
3294
|
-
const result2 = await readJsonObject(
|
|
4288
|
+
const result2 = await readJsonObject(join8(storeRoot, "active-model.json"));
|
|
3295
4289
|
if (!result2.found || result2.invalid) return void 0;
|
|
3296
4290
|
const modelId = result2.value?.model_id;
|
|
3297
4291
|
if (modelId === null) return "base";
|
|
3298
4292
|
return typeof modelId === "string" && modelId ? modelId : void 0;
|
|
3299
4293
|
}
|
|
3300
4294
|
async function readLatestRun(storeRoot) {
|
|
3301
|
-
const runsRoot =
|
|
4295
|
+
const runsRoot = join8(storeRoot, "runs");
|
|
3302
4296
|
let names;
|
|
3303
4297
|
try {
|
|
3304
4298
|
names = (await readdir3(runsRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -3306,7 +4300,7 @@ async function readLatestRun(storeRoot) {
|
|
|
3306
4300
|
return void 0;
|
|
3307
4301
|
}
|
|
3308
4302
|
const states = await Promise.all(names.map(async (name) => {
|
|
3309
|
-
const result2 = await readJsonObject(
|
|
4303
|
+
const result2 = await readJsonObject(join8(runsRoot, name, "state.json"));
|
|
3310
4304
|
if (!result2.found || result2.invalid || !result2.value) return void 0;
|
|
3311
4305
|
const id = stringField(result2.value, "id") ?? name;
|
|
3312
4306
|
const state = {
|
|
@@ -3334,14 +4328,15 @@ function hostFromSnapshot(snapshot) {
|
|
|
3334
4328
|
summary: snapshot.summary,
|
|
3335
4329
|
line: formatHostStatusLine(snapshot),
|
|
3336
4330
|
cuda: snapshot.capabilities.cuda_available,
|
|
3337
|
-
gpuName: snapshot.capabilities.gpu?.name
|
|
4331
|
+
gpuName: snapshot.capabilities.gpu?.name,
|
|
4332
|
+
capabilities: snapshot.capabilities
|
|
3338
4333
|
};
|
|
3339
4334
|
}
|
|
3340
4335
|
async function discoverShellContext(options = {}) {
|
|
3341
4336
|
const env = options.env ?? process.env;
|
|
3342
|
-
const cwd =
|
|
4337
|
+
const cwd = resolve3(options.cwd ?? process.cwd());
|
|
3343
4338
|
const warnings = [];
|
|
3344
|
-
const specPath =
|
|
4339
|
+
const specPath = join8(cwd, "tunedtensor.json");
|
|
3345
4340
|
const specJson = await readJsonObject(specPath);
|
|
3346
4341
|
let spec;
|
|
3347
4342
|
if (specJson.found) {
|
|
@@ -3350,31 +4345,35 @@ async function discoverShellContext(options = {}) {
|
|
|
3350
4345
|
warnings.push("tunedtensor.json could not be parsed.");
|
|
3351
4346
|
} else {
|
|
3352
4347
|
const examples = specJson.value.examples;
|
|
4348
|
+
const runtime2 = specJson.value.runtime;
|
|
4349
|
+
const gpu = runtime2 && typeof runtime2 === "object" && !Array.isArray(runtime2) ? runtime2.gpu : void 0;
|
|
3353
4350
|
spec = {
|
|
3354
4351
|
path: specPath,
|
|
3355
4352
|
id: stringField(specJson.value, "id"),
|
|
3356
4353
|
name: stringField(specJson.value, "name"),
|
|
3357
4354
|
baseModel: stringField(specJson.value, "base_model"),
|
|
4355
|
+
engine: stringField(specJson.value, "engine"),
|
|
3358
4356
|
exampleCount: Array.isArray(examples) ? examples.length : void 0,
|
|
4357
|
+
remoteGpu: gpu && typeof gpu === "object" && !Array.isArray(gpu) ? stringField(gpu, "instanceId") : void 0,
|
|
3359
4358
|
parseError: false
|
|
3360
4359
|
};
|
|
3361
4360
|
}
|
|
3362
4361
|
}
|
|
3363
|
-
const localConfigPath =
|
|
4362
|
+
const localConfigPath = join8(cwd, "local-runner.json");
|
|
3364
4363
|
const hasAdjacentLocalConfig = await adjacentFile(localConfigPath);
|
|
3365
4364
|
const localConfigJson = hasAdjacentLocalConfig ? await readJsonObject(localConfigPath) : { found: false, invalid: false };
|
|
3366
4365
|
if (localConfigJson.invalid) {
|
|
3367
4366
|
warnings.push("local-runner.json could not be parsed.");
|
|
3368
4367
|
}
|
|
3369
|
-
const localConfigDirectory = hasAdjacentLocalConfig ?
|
|
4368
|
+
const localConfigDirectory = hasAdjacentLocalConfig ? dirname4(localConfigPath) : cwd;
|
|
3370
4369
|
const runtime = specJson.value?.runtime;
|
|
3371
4370
|
const projectRuntime = runtime && typeof runtime === "object" && !Array.isArray(runtime) ? runtime : void 0;
|
|
3372
4371
|
if (hasAdjacentLocalConfig && (projectRuntime || specJson.value?.evaluation)) warnings.push("Conflicting configuration sources; run tt pipeline migrate after reconciling the spec and local-runner.json.");
|
|
3373
4372
|
const config = projectRuntime ?? localConfigJson.value;
|
|
3374
4373
|
const configuredArtifactRoot = stringField(config, "artifactRoot");
|
|
3375
4374
|
const configuredStoreRoot = stringField(config, "storeRoot");
|
|
3376
|
-
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) :
|
|
3377
|
-
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ?
|
|
4375
|
+
const artifactRoot = configuredArtifactRoot ? expandPath(configuredArtifactRoot, localConfigDirectory, env) : resolve3(cwd, DEFAULT_ARTIFACT_ROOT);
|
|
4376
|
+
const storeRoot = configuredStoreRoot ? expandPath(configuredStoreRoot, localConfigDirectory, env) : specJson.value && !hasAdjacentLocalConfig && !env.TT_LOCAL_HOME?.trim() ? resolve3(cwd, DEFAULT_PROJECT_STORE_ROOT) : defaultStoreRoot(env);
|
|
3378
4377
|
const storedConfigPath = configPath(env);
|
|
3379
4378
|
const storedConfigJson = await readJsonObject(storedConfigPath);
|
|
3380
4379
|
if (storedConfigJson.invalid) {
|
|
@@ -3408,7 +4407,7 @@ async function discoverShellContext(options = {}) {
|
|
|
3408
4407
|
function valueOrDash(value) {
|
|
3409
4408
|
return value === void 0 || value === "" ? "\u2014" : String(value);
|
|
3410
4409
|
}
|
|
3411
|
-
function
|
|
4410
|
+
function agentLabel2(context) {
|
|
3412
4411
|
const agent = context.agent;
|
|
3413
4412
|
if (!agent?.provider && !agent?.model) return "not configured";
|
|
3414
4413
|
const id = [agent.provider, agent.model].filter(Boolean).join("/");
|
|
@@ -3423,7 +4422,7 @@ function formatShellContext(context) {
|
|
|
3423
4422
|
`Spec ID ${valueOrDash(context.spec?.id)}`,
|
|
3424
4423
|
`Base model ${valueOrDash(context.spec?.baseModel)}`,
|
|
3425
4424
|
`Examples ${valueOrDash(context.spec?.exampleCount)}`,
|
|
3426
|
-
`Agent model ${
|
|
4425
|
+
`Agent model ${agentLabel2(context)}`,
|
|
3427
4426
|
`Local config ${context.local.configPath ?? "not found"}`,
|
|
3428
4427
|
`Artifact root ${context.local.artifactRoot}`,
|
|
3429
4428
|
`Store root ${context.local.storeRoot}`,
|
|
@@ -3435,7 +4434,7 @@ function formatShellContext(context) {
|
|
|
3435
4434
|
function formatShellStatus(context) {
|
|
3436
4435
|
return [
|
|
3437
4436
|
`Spec ${context.spec?.name ?? (context.spec ? "unnamed" : "not found")}`,
|
|
3438
|
-
`Agent model ${
|
|
4437
|
+
`Agent model ${agentLabel2(context)}`,
|
|
3439
4438
|
`Local config ${context.local.configPath ?? "not found"}`,
|
|
3440
4439
|
`Active model ${context.local.activeModelId ?? "base"}`,
|
|
3441
4440
|
`Latest run ${context.local.latestRun ? `${context.local.latestRun.id} (${context.local.latestRun.status ?? "unknown"})` : "none"}`,
|
|
@@ -3612,7 +4611,7 @@ function setAgentModel(runtime, env, provider, model, options = {}) {
|
|
|
3612
4611
|
}
|
|
3613
4612
|
|
|
3614
4613
|
// src/secret-prompt.ts
|
|
3615
|
-
import
|
|
4614
|
+
import chalk6 from "chalk";
|
|
3616
4615
|
import { emitKeypressEvents } from "readline";
|
|
3617
4616
|
import { createInterface } from "readline/promises";
|
|
3618
4617
|
import { stdin, stdout } from "process";
|
|
@@ -3690,16 +4689,16 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
|
|
|
3690
4689
|
emitKeypressEvents(input);
|
|
3691
4690
|
return await withDetachedKeypress(input, async () => {
|
|
3692
4691
|
const wasRaw = terminal.isRaw === true;
|
|
3693
|
-
terminal.setRawMode(true);
|
|
3694
|
-
input.resume();
|
|
3695
|
-
output.write(`${message} ${hint()}`);
|
|
3696
4692
|
try {
|
|
3697
|
-
|
|
4693
|
+
terminal.setRawMode(true);
|
|
4694
|
+
input.resume();
|
|
4695
|
+
output.write(`${message} ${hint()}`);
|
|
4696
|
+
const choice = await new Promise((resolve13) => {
|
|
3698
4697
|
const finish = (decision) => {
|
|
3699
4698
|
input.removeListener("keypress", onKeypress);
|
|
3700
4699
|
input.removeListener("end", onEnd);
|
|
3701
4700
|
input.removeListener("close", onEnd);
|
|
3702
|
-
|
|
4701
|
+
resolve13(decision);
|
|
3703
4702
|
};
|
|
3704
4703
|
const onKeypress = (text2, key) => {
|
|
3705
4704
|
const decided = keyChoiceFor(key, text2);
|
|
@@ -3719,7 +4718,7 @@ async function promptKeyChoice(message, input = stdin, output = stdout) {
|
|
|
3719
4718
|
});
|
|
3720
4719
|
}
|
|
3721
4720
|
function hint() {
|
|
3722
|
-
return
|
|
4721
|
+
return chalk6.dim("y yes \xB7 n no \xB7 esc decide later ");
|
|
3723
4722
|
}
|
|
3724
4723
|
|
|
3725
4724
|
// src/shell.ts
|
|
@@ -3880,6 +4879,7 @@ var SLASH_NAMES = /* @__PURE__ */ new Set([
|
|
|
3880
4879
|
"help",
|
|
3881
4880
|
"status",
|
|
3882
4881
|
"context",
|
|
4882
|
+
"system",
|
|
3883
4883
|
"model",
|
|
3884
4884
|
"login",
|
|
3885
4885
|
"clear",
|
|
@@ -3949,9 +4949,9 @@ function parseSlashCommand(input) {
|
|
|
3949
4949
|
function errorMessage(error) {
|
|
3950
4950
|
return error instanceof Error ? error.message : String(error);
|
|
3951
4951
|
}
|
|
3952
|
-
var successMark = () =>
|
|
3953
|
-
var errorMark = () =>
|
|
3954
|
-
var
|
|
4952
|
+
var successMark = () => chalk7.green("\u2713");
|
|
4953
|
+
var errorMark = () => chalk7.red("\u2717");
|
|
4954
|
+
var accent4 = chalk7.hex("#8B5CF6");
|
|
3955
4955
|
var ANSI_BOLD = "\x1B[1m";
|
|
3956
4956
|
var ANSI_BOLD_OFF = "\x1B[22m";
|
|
3957
4957
|
var ANSI_CLEAR_LINE = "\x1B[2K";
|
|
@@ -3960,15 +4960,15 @@ var ANSI_CLEAR_DOWN = "\x1B[J";
|
|
|
3960
4960
|
var ANSI_CURSOR_UP = "\x1B[1A";
|
|
3961
4961
|
var ANSI_RESET = "\x1B[0m";
|
|
3962
4962
|
function inputColorCodes() {
|
|
3963
|
-
if (
|
|
3964
|
-
if (
|
|
4963
|
+
if (chalk7.level === 0) return null;
|
|
4964
|
+
if (chalk7.level === 1) {
|
|
3965
4965
|
return {
|
|
3966
4966
|
background: "\x1B[100m",
|
|
3967
4967
|
accent: "\x1B[95m",
|
|
3968
4968
|
text: "\x1B[97m"
|
|
3969
4969
|
};
|
|
3970
4970
|
}
|
|
3971
|
-
if (
|
|
4971
|
+
if (chalk7.level === 2) {
|
|
3972
4972
|
return {
|
|
3973
4973
|
background: "\x1B[48;5;238m",
|
|
3974
4974
|
accent: "\x1B[38;5;183m",
|
|
@@ -3981,13 +4981,14 @@ function inputColorCodes() {
|
|
|
3981
4981
|
text: "\x1B[38;2;245;243;255m"
|
|
3982
4982
|
};
|
|
3983
4983
|
}
|
|
4984
|
+
var SPEC_USAGE = `Usage: /spec [${SPEC_SECTIONS.join("|")}|show|diff|validate|history] [path/to/tunedtensor.json]`;
|
|
3984
4985
|
var DETAIL_LABEL_WIDTH = 15;
|
|
3985
4986
|
function styleDetailLines(lines) {
|
|
3986
4987
|
return lines.map((line) => {
|
|
3987
4988
|
if (line.length <= DETAIL_LABEL_WIDTH) return line;
|
|
3988
4989
|
const label = line.slice(0, DETAIL_LABEL_WIDTH);
|
|
3989
4990
|
if (!label.trim()) return line;
|
|
3990
|
-
const color = label.startsWith("Warning") ?
|
|
4991
|
+
const color = label.startsWith("Warning") ? chalk7.yellow : accent4.bold;
|
|
3991
4992
|
return `${color(label)}${line.slice(DETAIL_LABEL_WIDTH)}`;
|
|
3992
4993
|
});
|
|
3993
4994
|
}
|
|
@@ -4000,42 +5001,47 @@ function assertNoArgs(command, args2) {
|
|
|
4000
5001
|
}
|
|
4001
5002
|
}
|
|
4002
5003
|
function expandDirectory(value, cwd, env) {
|
|
4003
|
-
const home = env.HOME ?
|
|
5004
|
+
const home = env.HOME ? resolve4(env.HOME) : homedir2();
|
|
4004
5005
|
if (value === "~") return home;
|
|
4005
|
-
if (value.startsWith("~/")) return
|
|
4006
|
-
return isAbsolute3(value) ?
|
|
5006
|
+
if (value.startsWith("~/")) return resolve4(home, value.slice(2));
|
|
5007
|
+
return isAbsolute3(value) ? resolve4(value) : resolve4(cwd, value);
|
|
4007
5008
|
}
|
|
4008
5009
|
function helpText(mode, query, palette = false) {
|
|
4009
5010
|
const groups = groupedCatalog(mode, query);
|
|
4010
5011
|
const lines = [];
|
|
4011
|
-
lines.push(
|
|
5012
|
+
lines.push(accent4.bold(palette ? "Commands \u2014 type a command, or prefix with : to make the intent explicit" : `TT commands${query ? ` matching ${JSON.stringify(query)}` : ""}`));
|
|
4012
5013
|
if (groups.size === 0) {
|
|
4013
|
-
lines.push(
|
|
5014
|
+
lines.push(chalk7.dim(" No matching commands."));
|
|
4014
5015
|
} else {
|
|
4015
5016
|
for (const [group, commands] of groups) {
|
|
4016
5017
|
lines.push("");
|
|
4017
|
-
lines.push(
|
|
5018
|
+
lines.push(chalk7.bold(group));
|
|
4018
5019
|
for (const command of commands) {
|
|
4019
|
-
lines.push(` ${
|
|
5020
|
+
lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
|
|
4020
5021
|
}
|
|
4021
5022
|
}
|
|
4022
5023
|
}
|
|
4023
5024
|
if (!query) {
|
|
4024
5025
|
lines.push("");
|
|
4025
|
-
lines.push(
|
|
4026
|
-
lines.push(` ${
|
|
4027
|
-
lines.push(` ${
|
|
4028
|
-
lines.push(` ${
|
|
4029
|
-
lines.push(` ${
|
|
4030
|
-
lines.push(` ${
|
|
4031
|
-
lines.push(` ${
|
|
5026
|
+
lines.push(chalk7.bold("Conversation"));
|
|
5027
|
+
lines.push(` ${accent4("plain text".padEnd(22))} Ask the Tuned Tensor agent anything.`);
|
|
5028
|
+
lines.push(` ${accent4("/new".padEnd(22))} Start a new agent conversation.`);
|
|
5029
|
+
lines.push(` ${accent4("/threads".padEnd(22))} List recent conversations.`);
|
|
5030
|
+
lines.push(` ${accent4("/resume <id>".padEnd(22))} Resume a conversation.`);
|
|
5031
|
+
lines.push(` ${accent4("/approve [id]".padEnd(22))} Approve an action left pending.`);
|
|
5032
|
+
lines.push(` ${accent4("/reject [id]".padEnd(22))} Reject an action left pending.`);
|
|
4032
5033
|
lines.push("");
|
|
4033
|
-
lines.push(
|
|
5034
|
+
lines.push(chalk7.bold("Session"));
|
|
4034
5035
|
for (const command of SLASH_COMMANDS) {
|
|
4035
|
-
lines.push(` ${
|
|
5036
|
+
lines.push(` ${accent4(command.path.padEnd(22))} ${command.description}`);
|
|
4036
5037
|
}
|
|
4037
|
-
lines.push(` ${
|
|
4038
|
-
lines.push(
|
|
5038
|
+
lines.push(` ${accent4("?".padEnd(22))} Alias for /help.`);
|
|
5039
|
+
lines.push("");
|
|
5040
|
+
lines.push(chalk7.bold("Behavior spec"));
|
|
5041
|
+
lines.push(` ${accent4("/spec".padEnd(22))} Readable overview of tunedtensor.json.`);
|
|
5042
|
+
lines.push(` ${accent4("/spec <section>".padEnd(22))} One section in full: ${SPEC_SECTIONS.join(", ")}.`);
|
|
5043
|
+
lines.push(` ${accent4("/spec show".padEnd(22))} The raw JSON, highlighted.`);
|
|
5044
|
+
lines.push(chalk7.dim(
|
|
4039
5045
|
"\nKnown TT commands execute directly; other text goes to the agent. Prefix a command with : to make the intent explicit."
|
|
4040
5046
|
));
|
|
4041
5047
|
}
|
|
@@ -4045,33 +5051,17 @@ function helpText(mode, query, palette = false) {
|
|
|
4045
5051
|
function activeModelLabel(snapshot) {
|
|
4046
5052
|
return snapshot.context.local.activeModelId ?? "base";
|
|
4047
5053
|
}
|
|
4048
|
-
function
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
snapshot.context.agent?.provider && snapshot.context.agent?.model
|
|
4057
|
-
);
|
|
4058
|
-
const lines = [
|
|
4059
|
-
heading,
|
|
4060
|
-
chalk4.dim(
|
|
4061
|
-
`agent ${agentModelLabel(snapshot.context)} \xB7 workflow model ${activeModelLabel(snapshot)}`
|
|
4062
|
-
),
|
|
4063
|
-
chalk4.dim("/spec review \xB7 ctrl+c stop/clear \xB7 ctrl+d exit \xB7 /help commands \xB7 tab complete"),
|
|
4064
|
-
"",
|
|
4065
|
-
chalk4.dim(
|
|
4066
|
-
configured ? "Ask TT anything. Known commands run directly." : "Use /login tunedtensor for managed inference, or /model for your own provider. Workflow commands work now."
|
|
4067
|
-
)
|
|
4068
|
-
];
|
|
4069
|
-
return `${lines.join("\n")}
|
|
4070
|
-
|
|
4071
|
-
`;
|
|
5054
|
+
function renderShellBanner(snapshot, columns) {
|
|
5055
|
+
return renderBanner({
|
|
5056
|
+
version: snapshot.version,
|
|
5057
|
+
context: snapshot.context,
|
|
5058
|
+
activeModel: activeModelLabel(snapshot),
|
|
5059
|
+
usage: snapshot.usage,
|
|
5060
|
+
columns
|
|
5061
|
+
});
|
|
4072
5062
|
}
|
|
4073
5063
|
function renderShellPrompt() {
|
|
4074
|
-
return `${
|
|
5064
|
+
return `${accent4.bold("\u203A")} `;
|
|
4075
5065
|
}
|
|
4076
5066
|
function renderSubmittedShellInput(input, columns) {
|
|
4077
5067
|
const colors = inputColorCodes();
|
|
@@ -4104,7 +5094,7 @@ function resetShellPromptStyle() {
|
|
|
4104
5094
|
return "";
|
|
4105
5095
|
}
|
|
4106
5096
|
var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
4107
|
-
constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, initialContext) {
|
|
5097
|
+
constructor(runner, io, agent, env, contextProvider, version, agentModelRuntime, systemProbe, columns, initialContext) {
|
|
4108
5098
|
this.runner = runner;
|
|
4109
5099
|
this.io = io;
|
|
4110
5100
|
this.agent = agent;
|
|
@@ -4112,6 +5102,8 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4112
5102
|
this.contextProvider = contextProvider;
|
|
4113
5103
|
this.version = version;
|
|
4114
5104
|
this.agentModelRuntime = agentModelRuntime;
|
|
5105
|
+
this.systemProbe = systemProbe;
|
|
5106
|
+
this.columns = columns;
|
|
4115
5107
|
this.cwd = initialContext.cwd;
|
|
4116
5108
|
this.context = initialContext;
|
|
4117
5109
|
}
|
|
@@ -4119,12 +5111,13 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4119
5111
|
modeSource = "default-local";
|
|
4120
5112
|
cwd;
|
|
4121
5113
|
context;
|
|
5114
|
+
usage;
|
|
4122
5115
|
static async create(options) {
|
|
4123
5116
|
const env = options.env ?? process.env;
|
|
4124
|
-
const cwd =
|
|
5117
|
+
const cwd = resolve4(options.cwd ?? process.cwd());
|
|
4125
5118
|
const contextProvider = options.contextProvider ?? ((input) => discoverShellContext(input));
|
|
4126
5119
|
const initialContext = await contextProvider({ cwd, env });
|
|
4127
|
-
|
|
5120
|
+
const session = new _TunedTensorShellSession(
|
|
4128
5121
|
options.runner,
|
|
4129
5122
|
options.io,
|
|
4130
5123
|
options.agent,
|
|
@@ -4132,8 +5125,22 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4132
5125
|
contextProvider,
|
|
4133
5126
|
options.version,
|
|
4134
5127
|
options.agentModelRuntime,
|
|
5128
|
+
options.systemProbe,
|
|
5129
|
+
options.columns ?? (() => void 0),
|
|
4135
5130
|
initialContext
|
|
4136
5131
|
);
|
|
5132
|
+
await session.sampleSystem();
|
|
5133
|
+
return session;
|
|
5134
|
+
}
|
|
5135
|
+
/** Refresh live machine usage; a failed probe only hides the panel. */
|
|
5136
|
+
async sampleSystem() {
|
|
5137
|
+
if (!this.systemProbe) return void 0;
|
|
5138
|
+
try {
|
|
5139
|
+
this.usage = await this.systemProbe();
|
|
5140
|
+
} catch {
|
|
5141
|
+
this.usage = void 0;
|
|
5142
|
+
}
|
|
5143
|
+
return this.usage;
|
|
4137
5144
|
}
|
|
4138
5145
|
interruptAgent() {
|
|
4139
5146
|
return this.agent?.interrupt() ?? false;
|
|
@@ -4144,14 +5151,25 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4144
5151
|
modeSource: this.modeSource,
|
|
4145
5152
|
cwd: this.cwd,
|
|
4146
5153
|
context: this.context,
|
|
4147
|
-
version: this.version
|
|
5154
|
+
version: this.version,
|
|
5155
|
+
usage: this.usage
|
|
4148
5156
|
};
|
|
4149
5157
|
}
|
|
4150
5158
|
prompt() {
|
|
4151
5159
|
return renderShellPrompt();
|
|
4152
5160
|
}
|
|
4153
5161
|
banner() {
|
|
4154
|
-
return renderShellBanner(this.snapshot());
|
|
5162
|
+
return renderShellBanner(this.snapshot(), this.columns());
|
|
5163
|
+
}
|
|
5164
|
+
async inspectSpecForView(path) {
|
|
5165
|
+
try {
|
|
5166
|
+
return await inspectLocalSpec(this.cwd, path);
|
|
5167
|
+
} catch (error) {
|
|
5168
|
+
if (error.code === "ENOENT") {
|
|
5169
|
+
throw new ShellParseError(`No ${path} in ${this.cwd}. Run init, or ask TT to draft a behavior spec.`);
|
|
5170
|
+
}
|
|
5171
|
+
throw error;
|
|
5172
|
+
}
|
|
4155
5173
|
}
|
|
4156
5174
|
async refreshContext() {
|
|
4157
5175
|
this.context = await this.contextProvider({ cwd: this.cwd, env: this.env });
|
|
@@ -4187,42 +5205,42 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4187
5205
|
const provider = query ? findAgentProvider(runtime, query) : void 0;
|
|
4188
5206
|
if (provider) {
|
|
4189
5207
|
const matches = listAgentModels(runtime, { ...catalog, provider: provider.id });
|
|
4190
|
-
lines.push("",
|
|
5208
|
+
lines.push("", chalk7.bold(`Models from ${provider.id}`));
|
|
4191
5209
|
this.appendAgentModelChoices(lines, matches, summary, 20, "/model <query> to search");
|
|
4192
5210
|
if (matches.length === 0) {
|
|
4193
|
-
lines.push(
|
|
5211
|
+
lines.push(chalk7.dim(" no models"));
|
|
4194
5212
|
}
|
|
4195
5213
|
return lines;
|
|
4196
5214
|
}
|
|
4197
5215
|
if (query) {
|
|
4198
5216
|
const matches = listAgentModels(runtime, { ...catalog, query });
|
|
4199
|
-
lines.push("",
|
|
5217
|
+
lines.push("", chalk7.bold(`Models matching ${JSON.stringify(query)}`));
|
|
4200
5218
|
this.appendAgentModelChoices(lines, matches, summary, 10, "refine the query");
|
|
4201
5219
|
if (matches.length === 0) {
|
|
4202
|
-
lines.push(
|
|
5220
|
+
lines.push(chalk7.dim(" no matches"));
|
|
4203
5221
|
}
|
|
4204
5222
|
return lines;
|
|
4205
5223
|
}
|
|
4206
5224
|
const providers = listAgentProviders(runtime, { featuredOnly: true });
|
|
4207
|
-
lines.push("",
|
|
5225
|
+
lines.push("", chalk7.bold("Providers"));
|
|
4208
5226
|
if (providers.length === 0) {
|
|
4209
|
-
lines.push(
|
|
5227
|
+
lines.push(chalk7.dim(" no providers in the catalog"));
|
|
4210
5228
|
} else {
|
|
4211
5229
|
for (const choice of providers) {
|
|
4212
5230
|
lines.push(this.formatAgentProviderChoice(choice));
|
|
4213
5231
|
}
|
|
4214
|
-
lines.push(
|
|
5232
|
+
lines.push(chalk7.dim(" Other providers: /login <id> or /model <id>"));
|
|
4215
5233
|
if (providers.some((choice) => !choice.authenticated)) {
|
|
4216
|
-
lines.push(
|
|
5234
|
+
lines.push(chalk7.dim(" Use /login tunedtensor for your TT token, or /login <provider> for a BYO key."));
|
|
4217
5235
|
}
|
|
4218
5236
|
}
|
|
4219
5237
|
const suggestions = recommendAgentModels(runtime, featuredCatalog);
|
|
4220
|
-
lines.push("",
|
|
5238
|
+
lines.push("", chalk7.bold("Suggestions"));
|
|
4221
5239
|
this.appendAgentModelChoices(lines, suggestions, summary);
|
|
4222
5240
|
if (suggestions.length === 0) {
|
|
4223
|
-
lines.push(
|
|
5241
|
+
lines.push(chalk7.dim(" no models in the catalog"));
|
|
4224
5242
|
} else {
|
|
4225
|
-
lines.push(
|
|
5243
|
+
lines.push(chalk7.dim(" Use /model <provider> to list models, /model <query> to search"));
|
|
4226
5244
|
}
|
|
4227
5245
|
return lines;
|
|
4228
5246
|
}
|
|
@@ -4233,25 +5251,35 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4233
5251
|
}
|
|
4234
5252
|
if (limit && models.length > shown.length) {
|
|
4235
5253
|
const hint2 = moreHint ? ` \u2014 ${moreHint}` : "";
|
|
4236
|
-
lines.push(
|
|
5254
|
+
lines.push(chalk7.dim(` \u2026 ${models.length - shown.length} more${hint2}`));
|
|
4237
5255
|
}
|
|
4238
5256
|
}
|
|
4239
5257
|
formatAgentProviderChoice(provider) {
|
|
4240
5258
|
const displayName = provider.name !== provider.id ? ` ${provider.name}` : "";
|
|
4241
|
-
const auth = provider.authenticated ? "" :
|
|
4242
|
-
return ` ${
|
|
5259
|
+
const auth = provider.authenticated ? "" : chalk7.dim(" auth required");
|
|
5260
|
+
return ` ${accent4(provider.id)}${displayName}${auth}`;
|
|
4243
5261
|
}
|
|
4244
5262
|
formatAgentModelChoice(model, summary) {
|
|
4245
5263
|
const active = summary?.provider === model.provider && summary?.model === model.id;
|
|
4246
5264
|
const displayName = model.name !== model.id ? ` ${model.name}` : "";
|
|
4247
|
-
const auth = model.authenticated ? "" :
|
|
4248
|
-
return ` ${
|
|
5265
|
+
const auth = model.authenticated ? "" : chalk7.dim(" auth required");
|
|
5266
|
+
return ` ${accent4(`${model.provider}/${model.id}`)}${displayName}${active ? chalk7.dim(" (active)") : ""}${auth}`;
|
|
4249
5267
|
}
|
|
4250
5268
|
async handleSlash(command) {
|
|
4251
5269
|
switch (command.name) {
|
|
4252
5270
|
case "spec": {
|
|
4253
|
-
if (command.args.length > 2) throw new ShellParseError(
|
|
4254
|
-
|
|
5271
|
+
if (command.args.length > 2) throw new ShellParseError(SPEC_USAGE);
|
|
5272
|
+
let [operation = "view", path = "tunedtensor.json"] = command.args;
|
|
5273
|
+
if (command.args.length === 1 && /(^|\/)tunedtensor\.json$/.test(operation)) {
|
|
5274
|
+
[operation, path] = ["view", operation];
|
|
5275
|
+
}
|
|
5276
|
+
const section = resolveSpecSection(operation);
|
|
5277
|
+
if (section || operation === "view" || operation === "show") {
|
|
5278
|
+
const spec = await this.inspectSpecForView(path);
|
|
5279
|
+
this.io.write(operation === "show" ? renderSpecSource(spec) : renderSpecView(spec, { section, columns: this.columns() }));
|
|
5280
|
+
return "continue";
|
|
5281
|
+
}
|
|
5282
|
+
if (!["diff", "validate", "history"].includes(operation)) throw new ShellParseError(SPEC_USAGE);
|
|
4255
5283
|
if (operation === "diff") {
|
|
4256
5284
|
const current = await inspectLocalSpec(this.cwd, path);
|
|
4257
5285
|
const pending = this.agent?.snapshot?.().pendingActions.filter((action) => {
|
|
@@ -4262,18 +5290,27 @@ var TunedTensorShellSession = class _TunedTensorShellSession {
|
|
|
4262
5290
|
for (const action of pending) {
|
|
4263
5291
|
const preview = action.preview;
|
|
4264
5292
|
const input = action.arguments;
|
|
4265
|
-
const
|
|
5293
|
+
const text3 = `Pending edit ${action.id.slice(0, 8)}${input.expected_sha256 !== current.sha256 ? " (stale; prepare again)" : ""}
|
|
4266
5294
|
${preview.diff}
|
|
4267
5295
|
`;
|
|
4268
|
-
this.io.write(sanitizeTerminalText(
|
|
5296
|
+
this.io.write(highlightSpecDiff(sanitizeTerminalText(text3)));
|
|
4269
5297
|
}
|
|
4270
5298
|
return "continue";
|
|
4271
5299
|
}
|
|
4272
5300
|
}
|
|
4273
|
-
|
|
5301
|
+
const text2 = sanitizeTerminalText((await reviewSpec(this.cwd, operation, path)).text);
|
|
5302
|
+
this.io.write(`${operation === "diff" ? highlightSpecDiff(text2) : text2}
|
|
4274
5303
|
`);
|
|
4275
5304
|
return "continue";
|
|
4276
5305
|
}
|
|
5306
|
+
case "system": {
|
|
5307
|
+
assertNoArgs("system", command.args);
|
|
5308
|
+
const usage = await this.sampleSystem();
|
|
5309
|
+
if (!usage) throw new ShellParseError("Live machine usage is unavailable in this session.");
|
|
5310
|
+
await this.refreshContext();
|
|
5311
|
+
this.writeLines(renderMachinePanel(usage, this.context, { columns: this.columns(), detailed: true }));
|
|
5312
|
+
return "continue";
|
|
5313
|
+
}
|
|
4277
5314
|
case "palette":
|
|
4278
5315
|
this.io.write(helpText(this.mode, void 0, true));
|
|
4279
5316
|
return "continue";
|
|
@@ -4336,9 +5373,9 @@ ${preview.diff}
|
|
|
4336
5373
|
}
|
|
4337
5374
|
this.writeLines([
|
|
4338
5375
|
"",
|
|
4339
|
-
|
|
5376
|
+
chalk7.bold("Providers"),
|
|
4340
5377
|
...providers.map((choice) => this.formatAgentProviderChoice(choice)),
|
|
4341
|
-
|
|
5378
|
+
chalk7.dim(" Other providers: /login <id>")
|
|
4342
5379
|
]);
|
|
4343
5380
|
if (!this.io.promptLine) {
|
|
4344
5381
|
throw new ShellParseError("Usage: /login <provider>");
|
|
@@ -4526,7 +5563,9 @@ async function startInteractiveShell(options) {
|
|
|
4526
5563
|
cwd: options.cwd,
|
|
4527
5564
|
env: options.env,
|
|
4528
5565
|
version: options.version,
|
|
4529
|
-
agentModelRuntime: options.agentModelRuntime
|
|
5566
|
+
agentModelRuntime: options.agentModelRuntime,
|
|
5567
|
+
systemProbe: options.systemProbe ?? (() => sampleLiveUsage({ env: { ...process.env, ...options.env } })),
|
|
5568
|
+
columns: () => output.columns
|
|
4530
5569
|
});
|
|
4531
5570
|
const completer = createCommandCompleter(() => session.snapshot().mode);
|
|
4532
5571
|
readline = createInterface2({
|
|
@@ -4577,6 +5616,9 @@ async function startInteractiveShell(options) {
|
|
|
4577
5616
|
} finally {
|
|
4578
5617
|
output.write(resetShellPromptStyle());
|
|
4579
5618
|
readline.close();
|
|
5619
|
+
output.write(`
|
|
5620
|
+
${mascotFarewell()}
|
|
5621
|
+
`);
|
|
4580
5622
|
}
|
|
4581
5623
|
}
|
|
4582
5624
|
function ciEnabled(value) {
|
|
@@ -4589,9 +5631,9 @@ function shouldStartInteractiveShell(options) {
|
|
|
4589
5631
|
}
|
|
4590
5632
|
|
|
4591
5633
|
// src/agent.ts
|
|
4592
|
-
var
|
|
4593
|
-
var successMark2 = () =>
|
|
4594
|
-
var errorMark2 = () =>
|
|
5634
|
+
var accent5 = chalk8.hex("#8B5CF6");
|
|
5635
|
+
var successMark2 = () => chalk8.green("\u2713");
|
|
5636
|
+
var errorMark2 = () => chalk8.red("\u2717");
|
|
4595
5637
|
var ANSI_CLEAR_LINE2 = "\r\x1B[2K";
|
|
4596
5638
|
var GUTTER = " ";
|
|
4597
5639
|
function isRecord3(value) {
|
|
@@ -4602,25 +5644,25 @@ function stringValue(value) {
|
|
|
4602
5644
|
}
|
|
4603
5645
|
function helpText2() {
|
|
4604
5646
|
return [
|
|
4605
|
-
|
|
5647
|
+
accent5.bold("Agent commands"),
|
|
4606
5648
|
"",
|
|
4607
|
-
` ${
|
|
4608
|
-
` ${
|
|
4609
|
-
` ${
|
|
4610
|
-
` ${
|
|
4611
|
-
` ${
|
|
4612
|
-
` ${
|
|
4613
|
-
` ${
|
|
4614
|
-
` ${
|
|
5649
|
+
` ${accent5("/new".padEnd(22))} Start a new conversation.`,
|
|
5650
|
+
` ${accent5("/threads".padEnd(22))} List recent conversations.`,
|
|
5651
|
+
` ${accent5("/resume <id>".padEnd(22))} Resume a conversation by ID or prefix.`,
|
|
5652
|
+
` ${accent5("/approve [id]".padEnd(22))} Approve an action left pending.`,
|
|
5653
|
+
` ${accent5("/reject [id]".padEnd(22))} Reject an action left pending.`,
|
|
5654
|
+
` ${accent5("/status".padEnd(22))} Show the active conversation.`,
|
|
5655
|
+
` ${accent5("/clear".padEnd(22))} Clear the terminal.`,
|
|
5656
|
+
` ${accent5("/exit".padEnd(22))} Exit the agent.`,
|
|
4615
5657
|
"",
|
|
4616
|
-
|
|
5658
|
+
chalk8.dim(
|
|
4617
5659
|
"Everything else is sent to the laptop-local Tuned Tensor assistant; known TT commands still run directly in the shell. Proposed changes ask for approval right after the answer."
|
|
4618
5660
|
),
|
|
4619
5661
|
""
|
|
4620
5662
|
].join("\n");
|
|
4621
5663
|
}
|
|
4622
5664
|
function formatThread(thread, activeId) {
|
|
4623
|
-
const active = thread.id === activeId ?
|
|
5665
|
+
const active = thread.id === activeId ? accent5("\u25CF") : chalk8.dim("\u25CB");
|
|
4624
5666
|
const time = thread.last_message_at ?? thread.updated_at ?? thread.created_at;
|
|
4625
5667
|
const date = new Date(time);
|
|
4626
5668
|
const dateLabel = Number.isNaN(date.getTime()) ? "" : date.toLocaleString([], {
|
|
@@ -4629,7 +5671,7 @@ function formatThread(thread, activeId) {
|
|
|
4629
5671
|
hour: "2-digit",
|
|
4630
5672
|
minute: "2-digit"
|
|
4631
5673
|
});
|
|
4632
|
-
return `${active} ${
|
|
5674
|
+
return `${active} ${chalk8.dim(thread.id.slice(0, 8))} ${sanitizeTerminalText(thread.title)} ${chalk8.dim(dateLabel)}`;
|
|
4633
5675
|
}
|
|
4634
5676
|
function actionFromPayload(payload) {
|
|
4635
5677
|
const value = isRecord3(payload.action) ? payload.action : isRecord3(payload.approval) ? payload.approval : payload;
|
|
@@ -4660,14 +5702,14 @@ function formatJson(value) {
|
|
|
4660
5702
|
}
|
|
4661
5703
|
function riskLabel(risk) {
|
|
4662
5704
|
const safe = sanitizeTerminalText(risk);
|
|
4663
|
-
const color = safe === "low" ?
|
|
5705
|
+
const color = safe === "low" ? chalk8.green : safe === "high" ? chalk8.red : chalk8.yellow;
|
|
4664
5706
|
return color(`${safe} risk`);
|
|
4665
5707
|
}
|
|
4666
5708
|
function diffStyle(line) {
|
|
4667
|
-
if (line.startsWith("+++") || line.startsWith("---")) return
|
|
4668
|
-
if (line.startsWith("+")) return
|
|
4669
|
-
if (line.startsWith("-")) return
|
|
4670
|
-
if (line.startsWith("@@")) return
|
|
5709
|
+
if (line.startsWith("+++") || line.startsWith("---")) return chalk8.dim;
|
|
5710
|
+
if (line.startsWith("+")) return chalk8.green;
|
|
5711
|
+
if (line.startsWith("-")) return chalk8.red;
|
|
5712
|
+
if (line.startsWith("@@")) return chalk8.cyan;
|
|
4671
5713
|
return (text2) => text2;
|
|
4672
5714
|
}
|
|
4673
5715
|
function approvalQuestion(action) {
|
|
@@ -4805,7 +5847,7 @@ var TunedTensorAgentSession = class {
|
|
|
4805
5847
|
wrapped.forEach((part, partIndex) => {
|
|
4806
5848
|
if (partIndex > 0) this.io.write("\n");
|
|
4807
5849
|
if (!this.lineOpen || partIndex > 0) {
|
|
4808
|
-
const marker = this.blockStart ? `${
|
|
5850
|
+
const marker = this.blockStart ? `${accent5("\u25CF")} ` : GUTTER;
|
|
4809
5851
|
if (this.blockStart && this.wroteInTurn) this.io.write("\n");
|
|
4810
5852
|
this.blockStart = false;
|
|
4811
5853
|
this.wroteInTurn = true;
|
|
@@ -4822,11 +5864,11 @@ var TunedTensorAgentSession = class {
|
|
|
4822
5864
|
renderAction(action) {
|
|
4823
5865
|
this.flushResponse();
|
|
4824
5866
|
this.endOpenLine();
|
|
4825
|
-
const bar =
|
|
5867
|
+
const bar = chalk8.yellow("\u2502");
|
|
4826
5868
|
const out = [
|
|
4827
5869
|
"",
|
|
4828
|
-
`${GUTTER}${
|
|
4829
|
-
`${GUTTER}${bar} ${
|
|
5870
|
+
`${GUTTER}${chalk8.yellow.bold("\u25C6 Approval needed")} ${chalk8.dim(action.id.slice(0, 8))}`,
|
|
5871
|
+
`${GUTTER}${bar} ${chalk8.bold(sanitizeTerminalText(action.title))} ${chalk8.dim("\xB7")} ${riskLabel(action.risk)}`
|
|
4830
5872
|
];
|
|
4831
5873
|
const width = this.wrapWidth();
|
|
4832
5874
|
const push = (text2, style = (line) => line) => {
|
|
@@ -4835,7 +5877,7 @@ var TunedTensorAgentSession = class {
|
|
|
4835
5877
|
for (const part of parts) out.push(`${GUTTER}${bar} ${style(part)}`);
|
|
4836
5878
|
}
|
|
4837
5879
|
};
|
|
4838
|
-
if (action.summary) push(action.summary,
|
|
5880
|
+
if (action.summary) push(action.summary, chalk8.dim);
|
|
4839
5881
|
if (action.operation === "update_local_spec") {
|
|
4840
5882
|
const preview = action.preview;
|
|
4841
5883
|
out.push(`${GUTTER}${bar}`);
|
|
@@ -4846,7 +5888,7 @@ var TunedTensorAgentSession = class {
|
|
|
4846
5888
|
out.push(`${GUTTER}${bar} ${style(index === 0 ? part : ` ${part}`)}`);
|
|
4847
5889
|
});
|
|
4848
5890
|
}
|
|
4849
|
-
for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`,
|
|
5891
|
+
for (const warning of preview?.validation?.warnings ?? []) push(`! ${warning}`, chalk8.yellow);
|
|
4850
5892
|
} else {
|
|
4851
5893
|
const request2 = [action.method, action.path].filter(Boolean).join(" ");
|
|
4852
5894
|
const technical = {
|
|
@@ -4856,14 +5898,14 @@ var TunedTensorAgentSession = class {
|
|
|
4856
5898
|
preview: action.preview ?? null
|
|
4857
5899
|
};
|
|
4858
5900
|
out.push(`${GUTTER}${bar}`);
|
|
4859
|
-
push(formatJson(technical),
|
|
5901
|
+
push(formatJson(technical), chalk8.dim);
|
|
4860
5902
|
}
|
|
4861
5903
|
this.io.write(`${out.join("\n")}
|
|
4862
5904
|
`);
|
|
4863
5905
|
}
|
|
4864
5906
|
writePendingHint() {
|
|
4865
5907
|
this.io.write(
|
|
4866
|
-
|
|
5908
|
+
chalk8.dim(`${GUTTER}Nothing changes until you decide: /approve applies it, /reject discards it.
|
|
4867
5909
|
`)
|
|
4868
5910
|
);
|
|
4869
5911
|
}
|
|
@@ -4880,14 +5922,14 @@ var TunedTensorAgentSession = class {
|
|
|
4880
5922
|
const delta = stringValue(payload.delta) ?? stringValue(payload.text) ?? stringValue(payload.content);
|
|
4881
5923
|
if (!delta) return;
|
|
4882
5924
|
if (this.reasoningActive) return;
|
|
4883
|
-
this.writeStatus(
|
|
5925
|
+
this.writeStatus(`${mascotMark()} ${chalk8.dim("Thinking\u2026")}`, { transient: true });
|
|
4884
5926
|
this.reasoningActive = true;
|
|
4885
5927
|
return;
|
|
4886
5928
|
}
|
|
4887
5929
|
if (event.type === "tool_call") {
|
|
4888
5930
|
this.reasoningActive = false;
|
|
4889
5931
|
const label = toolLabel(payload);
|
|
4890
|
-
this.writeStatus(
|
|
5932
|
+
this.writeStatus(chalk8.dim(`\u25CB ${label}\u2026`), { transient: true });
|
|
4891
5933
|
const id = stringValue(payload.toolUseId);
|
|
4892
5934
|
if (id) this.pendingToolLabels.set(id, label);
|
|
4893
5935
|
else this.pendingUnkeyedToolLabel = label;
|
|
@@ -4900,7 +5942,7 @@ var TunedTensorAgentSession = class {
|
|
|
4900
5942
|
if (id) this.pendingToolLabels.delete(id);
|
|
4901
5943
|
else this.pendingUnkeyedToolLabel = null;
|
|
4902
5944
|
this.writeStatus(
|
|
4903
|
-
`${failed ? errorMark2() : successMark2()} ${
|
|
5945
|
+
`${failed ? errorMark2() : successMark2()} ${chalk8.dim(failed ? `${label} failed` : label)}`
|
|
4904
5946
|
);
|
|
4905
5947
|
return;
|
|
4906
5948
|
}
|
|
@@ -4912,7 +5954,7 @@ var TunedTensorAgentSession = class {
|
|
|
4912
5954
|
return;
|
|
4913
5955
|
}
|
|
4914
5956
|
if (event.type === "action_started") {
|
|
4915
|
-
this.writeStatus(
|
|
5957
|
+
this.writeStatus(chalk8.dim("\u25CB Running approved action\u2026"));
|
|
4916
5958
|
return;
|
|
4917
5959
|
}
|
|
4918
5960
|
if (event.type === "action_result") {
|
|
@@ -4959,7 +6001,7 @@ var TunedTensorAgentSession = class {
|
|
|
4959
6001
|
}
|
|
4960
6002
|
}
|
|
4961
6003
|
if (controller.signal.aborted || result2.status === "cancelled") {
|
|
4962
|
-
this.io.write(
|
|
6004
|
+
this.io.write(chalk8.dim(`${GUTTER}Response stopped.
|
|
4963
6005
|
`));
|
|
4964
6006
|
return null;
|
|
4965
6007
|
}
|
|
@@ -4968,7 +6010,7 @@ var TunedTensorAgentSession = class {
|
|
|
4968
6010
|
this.flushResponse();
|
|
4969
6011
|
this.endOpenLine();
|
|
4970
6012
|
if (controller.signal.aborted) {
|
|
4971
|
-
this.io.write(
|
|
6013
|
+
this.io.write(chalk8.dim(`${GUTTER}Response stopped.
|
|
4972
6014
|
`));
|
|
4973
6015
|
return null;
|
|
4974
6016
|
}
|
|
@@ -5041,13 +6083,13 @@ var TunedTensorAgentSession = class {
|
|
|
5041
6083
|
}
|
|
5042
6084
|
for (const action of actions) {
|
|
5043
6085
|
if (!this.pendingActions.has(action.id)) continue;
|
|
5044
|
-
const label = actions.length > 1 ? `${approvalQuestion(action)} ${
|
|
5045
|
-
const decision = await this.prompter(`${GUTTER}${
|
|
6086
|
+
const label = actions.length > 1 ? `${approvalQuestion(action)} ${chalk8.dim(`(${action.title})`)}` : approvalQuestion(action);
|
|
6087
|
+
const decision = await this.prompter(`${GUTTER}${chalk8.yellow("?")} ${chalk8.bold(label)}`);
|
|
5046
6088
|
try {
|
|
5047
6089
|
if (decision === "approve") await this.approve(action.id, context);
|
|
5048
6090
|
else if (decision === "reject") await this.reject(action.id);
|
|
5049
6091
|
else {
|
|
5050
|
-
this.io.write(
|
|
6092
|
+
this.io.write(chalk8.dim(`${GUTTER}Left pending. Use /approve or /reject when you're ready.
|
|
5051
6093
|
`));
|
|
5052
6094
|
}
|
|
5053
6095
|
} catch (error) {
|
|
@@ -5120,7 +6162,7 @@ var TunedTensorAgentSession = class {
|
|
|
5120
6162
|
if (args2.length > 0) throw new Error("/threads does not accept arguments.");
|
|
5121
6163
|
const threads = await this.options.client.listThreads();
|
|
5122
6164
|
if (threads.length === 0) {
|
|
5123
|
-
this.options.io.write(
|
|
6165
|
+
this.options.io.write(chalk8.dim("No conversations yet.\n"));
|
|
5124
6166
|
return "continue";
|
|
5125
6167
|
}
|
|
5126
6168
|
this.options.io.write(
|
|
@@ -5158,10 +6200,10 @@ var TunedTensorAgentSession = class {
|
|
|
5158
6200
|
if (args2.length > 0) throw new Error("/status does not accept arguments.");
|
|
5159
6201
|
this.options.io.write(
|
|
5160
6202
|
this.thread ? `${formatThread(this.thread, this.thread.id)}
|
|
5161
|
-
` :
|
|
6203
|
+
` : chalk8.dim("New conversation; send a message to create it.\n")
|
|
5162
6204
|
);
|
|
5163
6205
|
this.options.io.write(
|
|
5164
|
-
|
|
6206
|
+
chalk8.dim(`${this.pendingActions.size} pending approval(s).
|
|
5165
6207
|
`)
|
|
5166
6208
|
);
|
|
5167
6209
|
return "continue";
|
|
@@ -5205,10 +6247,10 @@ import {
|
|
|
5205
6247
|
} from "fs/promises";
|
|
5206
6248
|
import {
|
|
5207
6249
|
basename as basename3,
|
|
5208
|
-
dirname as
|
|
6250
|
+
dirname as dirname5,
|
|
5209
6251
|
isAbsolute as isAbsolute4,
|
|
5210
|
-
join as
|
|
5211
|
-
resolve as
|
|
6252
|
+
join as join9,
|
|
6253
|
+
resolve as resolve5
|
|
5212
6254
|
} from "path";
|
|
5213
6255
|
|
|
5214
6256
|
// src/project-spec.ts
|
|
@@ -5227,6 +6269,7 @@ var LOCAL_ONLY_SPEC_KEYS = [
|
|
|
5227
6269
|
"dataset_prebuilt",
|
|
5228
6270
|
"engine",
|
|
5229
6271
|
"foundation",
|
|
6272
|
+
"decision",
|
|
5230
6273
|
"evaluation",
|
|
5231
6274
|
"runtime",
|
|
5232
6275
|
"pipeline"
|
|
@@ -5268,7 +6311,7 @@ function projectCloudSpec(raw) {
|
|
|
5268
6311
|
return projectSpec(raw, CLOUD_SPEC_KEY_SET, canonicalizeHostedSpecBaseModel);
|
|
5269
6312
|
}
|
|
5270
6313
|
function projectLocalSpec(raw) {
|
|
5271
|
-
return projectSpec(raw, LOCAL_SPEC_KEY_SET, canonicalizeSpecBaseModel);
|
|
6314
|
+
return projectSpec(raw, LOCAL_SPEC_KEY_SET, raw.engine === "decision" ? (body) => body : canonicalizeSpecBaseModel);
|
|
5272
6315
|
}
|
|
5273
6316
|
function hasLocalOnlySpecFields(raw) {
|
|
5274
6317
|
return Object.keys(raw).some((key) => LOCAL_ONLY_SPEC_KEY_SET.has(key));
|
|
@@ -5383,6 +6426,7 @@ function renderInit(value) {
|
|
|
5383
6426
|
["Path", text(value.path)],
|
|
5384
6427
|
["Name", text(value.name)],
|
|
5385
6428
|
["ID", text(value.id)],
|
|
6429
|
+
["Engine", text(value.engine)],
|
|
5386
6430
|
["Base model", text(value.base_model)],
|
|
5387
6431
|
["Config", text(value.config_path)]
|
|
5388
6432
|
]);
|
|
@@ -5391,14 +6435,27 @@ function renderValidate(value) {
|
|
|
5391
6435
|
printSuccess("Local behavior spec is valid");
|
|
5392
6436
|
printDetail([
|
|
5393
6437
|
["Input", text(value.input_path)],
|
|
6438
|
+
["Engine", text(value.engine)],
|
|
5394
6439
|
["Config", text(value.config_path)],
|
|
5395
6440
|
["Spec", text(value.behavior_spec_id)],
|
|
5396
6441
|
["Base model", text(value.base_model)],
|
|
5397
6442
|
["Dataset", text(value.dataset_format)],
|
|
6443
|
+
["Hugging Face", huggingFaceDatasetLabel(value)],
|
|
5398
6444
|
["Artifacts", text(value.artifact_root)],
|
|
5399
6445
|
["Store", text(value.store_root)],
|
|
5400
6446
|
["Dry run", text(value.dry_run)]
|
|
5401
6447
|
]);
|
|
6448
|
+
if (value.dataset_status === "not_downloaded") {
|
|
6449
|
+
printWarning(`Dataset files are not downloaded yet; dataset contents were not checked. Run \`${text(value.next_step) ?? "tt datasets prefetch"}\`.`);
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
function huggingFaceDatasetLabel(value) {
|
|
6453
|
+
const source = record3(value.dataset_source) ?? record3(value.dataset_huggingface);
|
|
6454
|
+
const repo = text(source?.repo);
|
|
6455
|
+
if (!repo) return void 0;
|
|
6456
|
+
const revision = text(source?.revision) ?? "main";
|
|
6457
|
+
const status2 = value.dataset_status === "not_downloaded" ? "not downloaded" : "cached";
|
|
6458
|
+
return `${repo}@${revision} (${status2}${source?.pinned === false || !text(source?.revision) ? ", unpinned" : ""})`;
|
|
5402
6459
|
}
|
|
5403
6460
|
function renderRun(value) {
|
|
5404
6461
|
const runStatus = text(value.status) ?? "unknown";
|
|
@@ -5453,6 +6510,68 @@ function renderRunEvents(value) {
|
|
|
5453
6510
|
])
|
|
5454
6511
|
);
|
|
5455
6512
|
}
|
|
6513
|
+
function formatSeconds(value) {
|
|
6514
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "running";
|
|
6515
|
+
const seconds = Math.round(value);
|
|
6516
|
+
const hours = Math.floor(seconds / 3600);
|
|
6517
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
6518
|
+
const rest = seconds % 60;
|
|
6519
|
+
if (hours) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
6520
|
+
if (minutes) return `${minutes}m ${String(rest).padStart(2, "0")}s`;
|
|
6521
|
+
return `${rest}s`;
|
|
6522
|
+
}
|
|
6523
|
+
function shortHash(value) {
|
|
6524
|
+
const hash = text(value);
|
|
6525
|
+
return hash ? hash.slice(0, 12) : void 0;
|
|
6526
|
+
}
|
|
6527
|
+
function renderRunAudit(value) {
|
|
6528
|
+
const verdict = text(value.verdict);
|
|
6529
|
+
const chain = record3(value.chain);
|
|
6530
|
+
const reportInfo = record3(value.report);
|
|
6531
|
+
const findings = Array.isArray(value.findings) ? value.findings.map(String) : [];
|
|
6532
|
+
if (verdict === "verified") printSuccess("Run evidence verified: event log intact and report unchanged since completion");
|
|
6533
|
+
else if (verdict === "tampered") printError("Run evidence does not verify");
|
|
6534
|
+
else printWarning("Run evidence is incomplete");
|
|
6535
|
+
for (const finding of findings) console.log(` - ${finding}`);
|
|
6536
|
+
printDetail([
|
|
6537
|
+
["Run", text(value.run_id)],
|
|
6538
|
+
["Status", status(value.status)],
|
|
6539
|
+
["Event chain", `${text(chain?.status) ?? "\u2014"} (${text(chain?.chained_events) ?? 0}/${text(chain?.events) ?? 0} chained, head ${shortHash(chain?.head_hash) ?? "\u2014"})`],
|
|
6540
|
+
["Report sha256", shortHash(reportInfo?.recorded_sha256) ?? "not recorded"]
|
|
6541
|
+
]);
|
|
6542
|
+
const timeline = rows(value.timeline);
|
|
6543
|
+
if (timeline.length > 0) {
|
|
6544
|
+
console.log("");
|
|
6545
|
+
printTable(
|
|
6546
|
+
["Started", "Stage", "Duration", "Status"],
|
|
6547
|
+
timeline.map((entry) => [
|
|
6548
|
+
formatDate(text(entry.started_at)),
|
|
6549
|
+
text(entry.stage) ?? "\u2014",
|
|
6550
|
+
formatSeconds(entry.duration_seconds),
|
|
6551
|
+
status(entry.status)
|
|
6552
|
+
])
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
const provenance = record3(value.provenance);
|
|
6556
|
+
if (provenance) {
|
|
6557
|
+
const software = record3(provenance.software);
|
|
6558
|
+
const execution = record3(provenance.execution);
|
|
6559
|
+
const spec = record3(provenance.spec);
|
|
6560
|
+
const baseModel = record3(provenance.base_model);
|
|
6561
|
+
const data3 = record3(provenance.data);
|
|
6562
|
+
const files = record3(data3?.file_sha256) ?? {};
|
|
6563
|
+
console.log("");
|
|
6564
|
+
printDetail([
|
|
6565
|
+
["TT", `${text(software?.tt_version) ?? "\u2014"} (node ${text(software?.node_version) ?? "\u2014"}, ${text(software?.platform) ?? "\u2014"}/${text(software?.arch) ?? "\u2014"})`],
|
|
6566
|
+
["Execution", `${text(execution?.target) ?? "\u2014"}${text(execution?.instance_id) ? ` ${text(execution?.instance_id)}` : ""} via ${text(execution?.training_backend) ?? "\u2014"}`],
|
|
6567
|
+
["Spec", `${text(spec?.file_path) ?? text(spec?.behavior_spec_id) ?? "\u2014"} (sha256 ${shortHash(spec?.file_sha256) ?? shortHash(spec?.snapshot_sha256) ?? "\u2014"})`],
|
|
6568
|
+
["Base model", `${text(baseModel?.id) ?? "\u2014"}@${shortHash(baseModel?.revision) ?? "unpinned"}`],
|
|
6569
|
+
...Object.entries(files).map(([split, hash]) => [`Data ${split}`, `sha256 ${shortHash(hash)}`]),
|
|
6570
|
+
["Training data", `sha256 ${shortHash(data3?.compiled_training_sha256) ?? "\u2014"} (${text(data3?.training_examples) ?? "\u2014"} examples)`],
|
|
6571
|
+
["Evaluation", `${text(data3?.eval_examples_used) ?? "\u2014"}/${text(data3?.eval_examples_total) ?? "\u2014"} ${text(data3?.eval_split) ?? ""} (seed ${text(data3?.eval_sample_seed) ?? "\u2014"})`]
|
|
6572
|
+
]);
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
5456
6575
|
function renderRunRecord(value) {
|
|
5457
6576
|
printDetail([
|
|
5458
6577
|
["Run", text(value.id) ?? text(value.run_id)],
|
|
@@ -5558,6 +6677,22 @@ function renderModelPrefetch(value) {
|
|
|
5558
6677
|
["Cache", text(value.model_cache) ?? text(value.hf_home)]
|
|
5559
6678
|
]);
|
|
5560
6679
|
}
|
|
6680
|
+
function renderDatasetPrefetch(value) {
|
|
6681
|
+
printSuccess(
|
|
6682
|
+
value.local_files_only === true ? "Verified the cached Hugging Face dataset" : "Dataset is ready locally"
|
|
6683
|
+
);
|
|
6684
|
+
const files = record3(value.files) ?? {};
|
|
6685
|
+
printDetail([
|
|
6686
|
+
["Dataset", text(value.repo)],
|
|
6687
|
+
["Revision", `${text(value.revision) ?? "\u2014"}${value.pinned === false ? " (unpinned: set dataset_prebuilt.huggingface.revision to pin it)" : ""}`],
|
|
6688
|
+
...Object.entries(files).map(([split, file]) => {
|
|
6689
|
+
const entry = record3(file);
|
|
6690
|
+
return [split[0].toUpperCase() + split.slice(1), `${text(entry?.path) ?? "\u2014"} (${displayBytes(entry?.size_bytes) ?? "\u2014"}, sha256 ${String(text(entry?.sha256) ?? "").slice(0, 12)})`];
|
|
6691
|
+
}),
|
|
6692
|
+
["Snapshot", text(value.snapshot_path)],
|
|
6693
|
+
["Cache", text(value.hub_cache)]
|
|
6694
|
+
]);
|
|
6695
|
+
}
|
|
5561
6696
|
function renderActiveModel(value) {
|
|
5562
6697
|
const active = text(value.active) ?? "base";
|
|
5563
6698
|
const pointer = record3(value.pointer);
|
|
@@ -5659,6 +6794,10 @@ function renderKnownJson(args2, value) {
|
|
|
5659
6794
|
renderRunEvents(value);
|
|
5660
6795
|
return true;
|
|
5661
6796
|
}
|
|
6797
|
+
if (subcommand === "audit" && isRecord4(value)) {
|
|
6798
|
+
renderRunAudit(value);
|
|
6799
|
+
return true;
|
|
6800
|
+
}
|
|
5662
6801
|
if (subcommand === "get" && isRecord4(value)) {
|
|
5663
6802
|
renderRunRecord(value);
|
|
5664
6803
|
return true;
|
|
@@ -5695,6 +6834,10 @@ function renderKnownJson(args2, value) {
|
|
|
5695
6834
|
return true;
|
|
5696
6835
|
}
|
|
5697
6836
|
}
|
|
6837
|
+
if (command === "datasets" && isRecord4(value)) {
|
|
6838
|
+
renderDatasetPrefetch(value);
|
|
6839
|
+
return true;
|
|
6840
|
+
}
|
|
5698
6841
|
if (command === "serve" && isRecord4(value)) {
|
|
5699
6842
|
renderServePlan(value);
|
|
5700
6843
|
return true;
|
|
@@ -5867,10 +7010,18 @@ function positionalSpecArgument(args2, start, defaultPath) {
|
|
|
5867
7010
|
function selectedSpecArgument(args2, cwd) {
|
|
5868
7011
|
const command = args2[0];
|
|
5869
7012
|
if (["doctor", "validate", "run"].includes(command ?? "")) {
|
|
5870
|
-
return positionalSpecArgument(args2, 1,
|
|
7013
|
+
return positionalSpecArgument(args2, 1, join9(cwd, DEFAULT_SPEC_NAME));
|
|
5871
7014
|
}
|
|
5872
7015
|
if (command === "models" && ["prefetch", "verify-base"].includes(args2[1] ?? "")) {
|
|
5873
|
-
return positionalSpecArgument(args2, 2,
|
|
7016
|
+
return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
|
|
7017
|
+
}
|
|
7018
|
+
if (command === "datasets") {
|
|
7019
|
+
if (args2[1] === void 0 || args2[1].startsWith("-") || args2[1].endsWith(".json")) {
|
|
7020
|
+
args2.splice(1, 0, "prefetch");
|
|
7021
|
+
}
|
|
7022
|
+
if (["prefetch", "verify"].includes(args2[1] ?? "")) {
|
|
7023
|
+
return positionalSpecArgument(args2, 2, join9(cwd, DEFAULT_SPEC_NAME));
|
|
7024
|
+
}
|
|
5874
7025
|
}
|
|
5875
7026
|
const serves = command === "serve" || command === "models" && args2[1] === "serve";
|
|
5876
7027
|
if (!serves) return null;
|
|
@@ -5883,7 +7034,7 @@ async function pathIsFile(path) {
|
|
|
5883
7034
|
}
|
|
5884
7035
|
function resolveFromCwd(path, cwd) {
|
|
5885
7036
|
if (isAbsolute4(path)) return path;
|
|
5886
|
-
return
|
|
7037
|
+
return resolve5(cwd, path);
|
|
5887
7038
|
}
|
|
5888
7039
|
async function projectSpecArguments(originalArgs, cwd) {
|
|
5889
7040
|
const args2 = [...originalArgs];
|
|
@@ -5946,8 +7097,8 @@ async function projectSpecArguments(originalArgs, cwd) {
|
|
|
5946
7097
|
}
|
|
5947
7098
|
};
|
|
5948
7099
|
}
|
|
5949
|
-
const temporaryPath =
|
|
5950
|
-
|
|
7100
|
+
const temporaryPath = join9(
|
|
7101
|
+
dirname5(sourcePath),
|
|
5951
7102
|
`.${basename3(sourcePath)}.tt-local-${process.pid}-${randomUUID7()}.json`
|
|
5952
7103
|
);
|
|
5953
7104
|
await writeFile4(
|
|
@@ -6012,6 +7163,8 @@ function normalizeProjectedPaths(value, replacements) {
|
|
|
6012
7163
|
async function spawnLocalCli(args2) {
|
|
6013
7164
|
const output = args2.stdout ?? process.stdout;
|
|
6014
7165
|
const errors = args2.stderr ?? process.stderr;
|
|
7166
|
+
const errorTerminal = errors;
|
|
7167
|
+
const childEnv = errorTerminal.isTTY && !args2.jsonMode && args2.env.TT_PROGRESS_TTY === void 0 ? { ...args2.env, TT_PROGRESS_TTY: "1", TT_PROGRESS_COLUMNS: String(errorTerminal.columns ?? 100) } : args2.env;
|
|
6015
7168
|
const pipeInput = Boolean(args2.stdin && args2.stdin !== process.stdin);
|
|
6016
7169
|
const pipeStreamingOutput = args2.streamingStdout && Boolean(args2.stdout && args2.stdout !== process.stdout);
|
|
6017
7170
|
const child = spawn(
|
|
@@ -6019,7 +7172,7 @@ async function spawnLocalCli(args2) {
|
|
|
6019
7172
|
[args2.entrypoint, ...args2.commandArgs],
|
|
6020
7173
|
{
|
|
6021
7174
|
cwd: args2.cwd,
|
|
6022
|
-
env:
|
|
7175
|
+
env: childEnv,
|
|
6023
7176
|
stdio: [
|
|
6024
7177
|
pipeInput ? "pipe" : "inherit",
|
|
6025
7178
|
args2.streamingStdout && !pipeStreamingOutput ? "inherit" : "pipe",
|
|
@@ -6109,7 +7262,7 @@ function setupFailure(originalArgs, error) {
|
|
|
6109
7262
|
};
|
|
6110
7263
|
}
|
|
6111
7264
|
async function runLocalCommand(args2, options = {}) {
|
|
6112
|
-
const cwd =
|
|
7265
|
+
const cwd = resolve5(options.cwd ?? process.cwd());
|
|
6113
7266
|
let projected;
|
|
6114
7267
|
try {
|
|
6115
7268
|
const entrypoint = options.entrypoint ?? resolveLocalCliEntrypoint();
|
|
@@ -6155,7 +7308,7 @@ async function executeLocalCommand(args2, options = {}) {
|
|
|
6155
7308
|
}
|
|
6156
7309
|
|
|
6157
7310
|
// src/commands/local.ts
|
|
6158
|
-
import
|
|
7311
|
+
import chalk9 from "chalk";
|
|
6159
7312
|
|
|
6160
7313
|
// src/passthrough.ts
|
|
6161
7314
|
function extractPassthroughOptions(args2, root) {
|
|
@@ -6183,7 +7336,8 @@ var LOCAL_COMMANDS = [
|
|
|
6183
7336
|
"validate",
|
|
6184
7337
|
"serve",
|
|
6185
7338
|
"runs",
|
|
6186
|
-
"models"
|
|
7339
|
+
"models",
|
|
7340
|
+
"datasets"
|
|
6187
7341
|
];
|
|
6188
7342
|
var LOCAL_COMMAND_DESCRIPTIONS = {
|
|
6189
7343
|
info: "Show the installed local runtime version and runner status",
|
|
@@ -6193,7 +7347,8 @@ var LOCAL_COMMAND_DESCRIPTIONS = {
|
|
|
6193
7347
|
validate: "Validate a local behavior spec without executing it",
|
|
6194
7348
|
serve: "Serve a verified adapter, the active model, or the protected base",
|
|
6195
7349
|
runs: "Inspect locally stored runs",
|
|
6196
|
-
models: "Inspect, verify, prefetch, or serve local models"
|
|
7350
|
+
models: "Inspect, verify, prefetch, or serve local models",
|
|
7351
|
+
datasets: "Download or verify the Hugging Face dataset a spec trains on"
|
|
6197
7352
|
};
|
|
6198
7353
|
function applyExitCode(result2) {
|
|
6199
7354
|
if (result2?.exitCode !== null && result2?.exitCode !== void 0) {
|
|
@@ -6210,7 +7365,7 @@ function registerLocalCommands(program, runtime) {
|
|
|
6210
7365
|
const runPassthrough = async (forwarded) => {
|
|
6211
7366
|
const root = program.opts();
|
|
6212
7367
|
const passthrough = extractPassthroughOptions(forwarded, root);
|
|
6213
|
-
if (!passthrough.color)
|
|
7368
|
+
if (!passthrough.color) chalk9.level = 0;
|
|
6214
7369
|
const result2 = await runtime.invokeLocal(
|
|
6215
7370
|
passthrough.args.length > 0 ? passthrough.args : ["--help"],
|
|
6216
7371
|
{
|
|
@@ -6308,12 +7463,12 @@ function registerAgentCommands(parent, options) {
|
|
|
6308
7463
|
}
|
|
6309
7464
|
|
|
6310
7465
|
// src/commands/pipeline.ts
|
|
6311
|
-
import { existsSync as existsSync4, readFileSync as
|
|
6312
|
-
import { dirname as
|
|
7466
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
7467
|
+
import { dirname as dirname9, join as join13, resolve as resolve8 } from "path";
|
|
6313
7468
|
|
|
6314
7469
|
// src/local-runtime/project-migration.ts
|
|
6315
7470
|
import { lstat as lstat5, readFile as readFile5, rename as rename3, writeFile as writeFile5, unlink as unlink3 } from "fs/promises";
|
|
6316
|
-
import { dirname as
|
|
7471
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
6317
7472
|
async function migrateProjectWorkflow(specPath) {
|
|
6318
7473
|
const info = await lstat5(specPath);
|
|
6319
7474
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Migration requires a regular spec file.");
|
|
@@ -6324,7 +7479,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
6324
7479
|
const files = [];
|
|
6325
7480
|
const sources = /* @__PURE__ */ new Map([[specPath, original]]);
|
|
6326
7481
|
for (const name of ["tunedtensor.pipeline.json", "local-runner.json"]) {
|
|
6327
|
-
const path =
|
|
7482
|
+
const path = join10(dirname6(specPath), name);
|
|
6328
7483
|
const entry = await lstat5(path).catch((error) => {
|
|
6329
7484
|
if (error.code === "ENOENT") return void 0;
|
|
6330
7485
|
throw error;
|
|
@@ -6392,7 +7547,7 @@ async function migrateProjectWorkflow(specPath) {
|
|
|
6392
7547
|
import { createHash as createHash5, randomUUID as randomUUID8 } from "crypto";
|
|
6393
7548
|
import { createReadStream } from "fs";
|
|
6394
7549
|
import { chmod as chmod2, lstat as lstat6, mkdir as mkdir4, open as open3, readdir as readdir4, readFile as readFile6, rename as rename4, rm as rm3 } from "fs/promises";
|
|
6395
|
-
import { basename as basename4, dirname as
|
|
7550
|
+
import { basename as basename4, dirname as dirname7, join as join11, resolve as resolve6 } from "path";
|
|
6396
7551
|
var INSTRUCTION_CORPUS_VERSION = 1;
|
|
6397
7552
|
function isRef(value) {
|
|
6398
7553
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
|
|
@@ -6421,7 +7576,7 @@ async function hashPath(path) {
|
|
|
6421
7576
|
if (info.isDirectory()) {
|
|
6422
7577
|
for (const name of (await readdir4(path)).sort()) {
|
|
6423
7578
|
hash.update(name);
|
|
6424
|
-
hash.update(await hashPath(
|
|
7579
|
+
hash.update(await hashPath(join11(path, name)));
|
|
6425
7580
|
}
|
|
6426
7581
|
return hash.digest("hex");
|
|
6427
7582
|
}
|
|
@@ -6444,7 +7599,7 @@ async function makePrivateTree(path) {
|
|
|
6444
7599
|
if (info.isDirectory()) {
|
|
6445
7600
|
await chmod2(path, 448);
|
|
6446
7601
|
for (const name of await readdir4(path)) {
|
|
6447
|
-
await makePrivateTree(
|
|
7602
|
+
await makePrivateTree(join11(path, name));
|
|
6448
7603
|
}
|
|
6449
7604
|
return;
|
|
6450
7605
|
}
|
|
@@ -6473,7 +7628,7 @@ async function readRequiredJsonObject(path, description) {
|
|
|
6473
7628
|
return parsed;
|
|
6474
7629
|
}
|
|
6475
7630
|
async function writePrivateTextAtomic(path, content) {
|
|
6476
|
-
const temporary =
|
|
7631
|
+
const temporary = join11(dirname7(path), `.${basename4(path)}.${randomUUID8()}.tmp`);
|
|
6477
7632
|
const file = await open3(temporary, "wx", 384);
|
|
6478
7633
|
let closed = false;
|
|
6479
7634
|
try {
|
|
@@ -6588,8 +7743,8 @@ ${result2.stderr}` : ""}`
|
|
|
6588
7743
|
async function runFoundationPipeline(args2) {
|
|
6589
7744
|
assertFoundationPlanSupported(args2.spec, args2.plan);
|
|
6590
7745
|
assertFoundationSettingsMatch(args2.spec.foundation, args2.plan);
|
|
6591
|
-
const outputDir =
|
|
6592
|
-
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ?
|
|
7746
|
+
const outputDir = resolve6(
|
|
7747
|
+
args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join11(resolve6(dirname7(args2.specPath), args2.spec.runtime.artifactRoot), "foundation-runs", randomUUID8()) : join11(dirname7(resolve6(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID8()))
|
|
6593
7748
|
);
|
|
6594
7749
|
if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
|
|
6595
7750
|
throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
|
|
@@ -6614,7 +7769,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6614
7769
|
await mkdir4(outputDir, { recursive: true, mode: 448 });
|
|
6615
7770
|
}
|
|
6616
7771
|
await makePrivateTree(outputDir);
|
|
6617
|
-
const workflowPath =
|
|
7772
|
+
const workflowPath = join11(outputDir, "resolved-workflow.json");
|
|
6618
7773
|
const workflow = { spec: args2.spec, plan: args2.plan };
|
|
6619
7774
|
if (outputExists) {
|
|
6620
7775
|
const previous = await readFile6(workflowPath, "utf8").catch((error) => {
|
|
@@ -6647,16 +7802,16 @@ async function runFoundationPipeline(args2) {
|
|
|
6647
7802
|
throw new Error(`Missing ${kind} artifact.`);
|
|
6648
7803
|
};
|
|
6649
7804
|
for (const step of args2.plan.steps) {
|
|
6650
|
-
const stepDir =
|
|
6651
|
-
const output =
|
|
7805
|
+
const stepDir = join11(outputDir, step.id);
|
|
7806
|
+
const output = join11(stepDir, "output");
|
|
6652
7807
|
await mkdir4(stepDir, { recursive: true, mode: 448 });
|
|
6653
7808
|
await chmod2(stepDir, 448);
|
|
6654
7809
|
await mkdir4(output, { recursive: true, mode: 448 });
|
|
6655
7810
|
await chmod2(output, 448);
|
|
6656
|
-
const configPath2 =
|
|
6657
|
-
const logPath =
|
|
6658
|
-
const metricsPath =
|
|
6659
|
-
const completionPath =
|
|
7811
|
+
const configPath2 = join11(stepDir, "config.json");
|
|
7812
|
+
const logPath = join11(stepDir, "step.log");
|
|
7813
|
+
const metricsPath = join11(output, "metrics.json");
|
|
7814
|
+
const completionPath = join11(stepDir, "completion.json");
|
|
6660
7815
|
const fields = stepConfig(step);
|
|
6661
7816
|
let entrypoint;
|
|
6662
7817
|
let config;
|
|
@@ -6677,7 +7832,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6677
7832
|
config = {
|
|
6678
7833
|
tokenizer_dir: tokenizerDir,
|
|
6679
7834
|
output_dir: output,
|
|
6680
|
-
work_dir:
|
|
7835
|
+
work_dir: join11(stepDir, "recovery"),
|
|
6681
7836
|
depth: fields.depth ?? hp.depth,
|
|
6682
7837
|
steps: fields.steps ?? hp.pretrain_steps,
|
|
6683
7838
|
batch_size: fields.batchSize ?? hp.batch_size,
|
|
@@ -6782,15 +7937,15 @@ async function runFoundationPipeline(args2) {
|
|
|
6782
7937
|
const artifacts = {};
|
|
6783
7938
|
const record4 = {};
|
|
6784
7939
|
if (step.uses === "tokenize") {
|
|
6785
|
-
const tokenizerPath =
|
|
7940
|
+
const tokenizerPath = join11(output, "tokenizer.json");
|
|
6786
7941
|
await requireRegularNonemptyFile(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
6787
7942
|
await readRequiredJsonObject(tokenizerPath, `Foundation step "${step.id}" tokenizer.json`);
|
|
6788
7943
|
record4.tokenizer = output;
|
|
6789
7944
|
tokenizerDir = output;
|
|
6790
7945
|
artifacts.tokenizer = await artifactFor(output);
|
|
6791
7946
|
} else if (step.uses === "pretrain" || step.uses === "finetune" || step.uses === "rl") {
|
|
6792
|
-
const modelPath =
|
|
6793
|
-
const modelConfigPath =
|
|
7947
|
+
const modelPath = join11(output, "model.safetensors");
|
|
7948
|
+
const modelConfigPath = join11(output, "config.json");
|
|
6794
7949
|
await requireRegularNonemptyFile(modelPath, `Foundation step "${step.id}" model.safetensors`);
|
|
6795
7950
|
const modelConfig = await readRequiredJsonObject(
|
|
6796
7951
|
modelConfigPath,
|
|
@@ -6805,7 +7960,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6805
7960
|
modelDir = output;
|
|
6806
7961
|
artifacts.model = await artifactFor(output);
|
|
6807
7962
|
} else if (step.uses === "evaluate") {
|
|
6808
|
-
const reportPath2 =
|
|
7963
|
+
const reportPath2 = join11(output, "report.json");
|
|
6809
7964
|
const stepReport = await readRequiredJsonObject(
|
|
6810
7965
|
reportPath2,
|
|
6811
7966
|
`Foundation step "${step.id}" report.json`
|
|
@@ -6845,7 +8000,7 @@ async function runFoundationPipeline(args2) {
|
|
|
6845
8000
|
spec: args2.specPath,
|
|
6846
8001
|
steps
|
|
6847
8002
|
};
|
|
6848
|
-
const reportPath =
|
|
8003
|
+
const reportPath = join11(outputDir, "report.json");
|
|
6849
8004
|
await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
|
|
6850
8005
|
`);
|
|
6851
8006
|
return {
|
|
@@ -6856,6 +8011,334 @@ async function runFoundationPipeline(args2) {
|
|
|
6856
8011
|
};
|
|
6857
8012
|
}
|
|
6858
8013
|
|
|
8014
|
+
// src/local-runtime/decision-runner.ts
|
|
8015
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
8016
|
+
import { lstat as lstat7, mkdir as mkdir5, readFile as readFile7, rename as rename5, rm as rm4, writeFile as writeFile6 } from "fs/promises";
|
|
8017
|
+
import { dirname as dirname8, join as join12, resolve as resolve7 } from "path";
|
|
8018
|
+
var PROBABILITY_FLOOR = 1e-6;
|
|
8019
|
+
function assertDecisionPlanSupported(plan) {
|
|
8020
|
+
for (const step of plan.steps) {
|
|
8021
|
+
if (step.uses === "evaluate" && step.with.evaluator !== "behavior") {
|
|
8022
|
+
throw new Error(`Decision runner evaluates with the behavior evaluator; step ${step.id} uses ${step.with.evaluator}.`);
|
|
8023
|
+
}
|
|
8024
|
+
if (!["train", "evaluate", "compare"].includes(step.uses)) {
|
|
8025
|
+
throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
}
|
|
8029
|
+
function decisionQuestion(spec) {
|
|
8030
|
+
return { ...spec.decision, instructions: buildSystemMessage(spec) };
|
|
8031
|
+
}
|
|
8032
|
+
function canonicalLabel(labels, output) {
|
|
8033
|
+
const normalized = output.trim().toLowerCase();
|
|
8034
|
+
const label = labels.find((candidate) => candidate.trim().toLowerCase() === normalized);
|
|
8035
|
+
if (!label) throw new Error(`Example output ${JSON.stringify(output)} is not a decision label (${labels.join(", ")}).`);
|
|
8036
|
+
return label;
|
|
8037
|
+
}
|
|
8038
|
+
async function readPredictions(path, ids, labels) {
|
|
8039
|
+
const rows2 = (await readFile7(path, "utf8")).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
8040
|
+
if (rows2.length !== ids.length || new Set(rows2.map((row) => row.id)).size !== rows2.length) {
|
|
8041
|
+
throw new Error(`Decision predictions must contain exactly one row per evaluation ID: ${path}`);
|
|
8042
|
+
}
|
|
8043
|
+
for (const row of rows2) validatePrediction(row, labels);
|
|
8044
|
+
const byId = new Map(rows2.map((row) => [row.id, row]));
|
|
8045
|
+
return ids.map((id) => {
|
|
8046
|
+
const row = byId.get(id);
|
|
8047
|
+
if (!row) throw new Error(`Decision predictions are missing row ${id}: ${path}`);
|
|
8048
|
+
if (!labels.includes(row.prediction)) throw new Error(`Decision prediction for ${id} is not a label: ${row.prediction}`);
|
|
8049
|
+
return row;
|
|
8050
|
+
});
|
|
8051
|
+
}
|
|
8052
|
+
function validatePrediction(row, labels) {
|
|
8053
|
+
const values = row?.probabilities;
|
|
8054
|
+
if (!values || Object.keys(values).length !== labels.length || labels.some((label) => !Object.hasOwn(values, label) || !Number.isFinite(values[label]) || values[label] < 0 || values[label] > 1)) {
|
|
8055
|
+
throw new Error("Decision probabilities must contain a finite probability for every label.");
|
|
8056
|
+
}
|
|
8057
|
+
const sum = labels.reduce((total, label) => total + values[label], 0);
|
|
8058
|
+
const maximum = Math.max(...labels.map((label) => values[label]));
|
|
8059
|
+
const sumTolerance = labels.length * 5e-5 + 1e-8;
|
|
8060
|
+
const tolerance = 2e-5;
|
|
8061
|
+
if (Math.abs(sum - 1) > sumTolerance || !labels.includes(row.prediction) || Math.abs(values[row.prediction] - maximum) > tolerance || !Number.isFinite(row.confidence) || Math.abs(row.confidence - maximum) > tolerance || !Number.isFinite(row.latency_ms) || row.latency_ms < 0) {
|
|
8062
|
+
throw new Error("Decision prediction must have normalized probabilities, an argmax label, matching confidence and nonnegative latency.");
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
function scoreDecisionPredictions(args2) {
|
|
8066
|
+
if (args2.predictions.length !== args2.examples.length) {
|
|
8067
|
+
throw new Error("Decision prediction count must match the evaluation examples.");
|
|
8068
|
+
}
|
|
8069
|
+
for (const prediction of args2.predictions) validatePrediction(prediction, args2.labels);
|
|
8070
|
+
const perLabel = Object.fromEntries(
|
|
8071
|
+
args2.labels.map((label) => [label, { support: 0, correct: 0, recall: 0 }])
|
|
8072
|
+
);
|
|
8073
|
+
let logLoss = 0;
|
|
8074
|
+
let brier = 0;
|
|
8075
|
+
let confidence = 0;
|
|
8076
|
+
const results = args2.examples.map((example, index) => {
|
|
8077
|
+
const prediction = args2.predictions[index];
|
|
8078
|
+
const passed = prediction.prediction === example.output;
|
|
8079
|
+
const expectedProbability = prediction.probabilities[example.output] ?? 0;
|
|
8080
|
+
logLoss -= Math.log(Math.max(expectedProbability, PROBABILITY_FLOOR));
|
|
8081
|
+
brier += args2.labels.reduce((sum, label) => {
|
|
8082
|
+
const target = label === example.output ? 1 : 0;
|
|
8083
|
+
return sum + ((prediction.probabilities[label] ?? 0) - target) ** 2;
|
|
8084
|
+
}, 0);
|
|
8085
|
+
confidence += prediction.confidence;
|
|
8086
|
+
const entry = perLabel[example.output];
|
|
8087
|
+
entry.support += 1;
|
|
8088
|
+
if (passed) entry.correct += 1;
|
|
8089
|
+
return {
|
|
8090
|
+
prompt: example.input,
|
|
8091
|
+
expected: example.output,
|
|
8092
|
+
actual: prediction.prediction,
|
|
8093
|
+
passed,
|
|
8094
|
+
score: passed ? 1 : 0,
|
|
8095
|
+
reasoning: `p(expected)=${expectedProbability.toFixed(4)}; confidence=${prediction.confidence.toFixed(4)}`,
|
|
8096
|
+
latency_ms: Math.max(0, Math.round(prediction.latency_ms)),
|
|
8097
|
+
scored_by: "exact_match"
|
|
8098
|
+
};
|
|
8099
|
+
});
|
|
8100
|
+
for (const entry of Object.values(perLabel)) entry.recall = entry.support ? entry.correct / entry.support : 0;
|
|
8101
|
+
const total = results.length;
|
|
8102
|
+
const mean = (value) => total ? value / total : 0;
|
|
8103
|
+
const accuracy = mean(results.filter((result2) => result2.passed).length);
|
|
8104
|
+
const report = evalReportSchema.parse({
|
|
8105
|
+
kind: args2.kind,
|
|
8106
|
+
model_id: args2.modelId,
|
|
8107
|
+
total,
|
|
8108
|
+
eval_examples_total: total,
|
|
8109
|
+
eval_examples_used: total,
|
|
8110
|
+
eval_truncated: false,
|
|
8111
|
+
eval_split: "spec_holdout",
|
|
8112
|
+
eval_sample_seed: args2.sampleSeed,
|
|
8113
|
+
avg_score: accuracy,
|
|
8114
|
+
pass_rate: accuracy,
|
|
8115
|
+
exact_match_rate: accuracy,
|
|
8116
|
+
avg_latency_ms: Math.round(mean(results.reduce((sum, result2) => sum + result2.latency_ms, 0))),
|
|
8117
|
+
results,
|
|
8118
|
+
artifact_uri: fileUri(args2.outputPath),
|
|
8119
|
+
scoring_method: "exact_match",
|
|
8120
|
+
scoring_mode: "exact_match"
|
|
8121
|
+
});
|
|
8122
|
+
return {
|
|
8123
|
+
report,
|
|
8124
|
+
metrics: {
|
|
8125
|
+
accuracy,
|
|
8126
|
+
log_loss: mean(logLoss),
|
|
8127
|
+
brier: mean(brier),
|
|
8128
|
+
mean_confidence: mean(confidence),
|
|
8129
|
+
per_label: perLabel
|
|
8130
|
+
}
|
|
8131
|
+
};
|
|
8132
|
+
}
|
|
8133
|
+
async function defaultDecisionSpawn(args2) {
|
|
8134
|
+
const launched = buildDecisionPythonCommand(args2.entrypoint, ["--config", args2.configPath]);
|
|
8135
|
+
const result2 = await runLoggedProcess({
|
|
8136
|
+
command: launched.command,
|
|
8137
|
+
commandArgs: launched.commandArgs,
|
|
8138
|
+
env: withDecisionPythonEnvironment(process.env),
|
|
8139
|
+
logPath: args2.logPath,
|
|
8140
|
+
stage: args2.stepId,
|
|
8141
|
+
appendLog: true,
|
|
8142
|
+
onLine: (line, stream) => {
|
|
8143
|
+
if (stream !== "stdout" || !args2.onProgress) return;
|
|
8144
|
+
try {
|
|
8145
|
+
const event = JSON.parse(line);
|
|
8146
|
+
if (event.event === "step") {
|
|
8147
|
+
args2.onProgress(`${args2.stepId}: step ${event.step}/${event.total_steps} (epoch ${event.epoch}) loss ${event.loss}`);
|
|
8148
|
+
}
|
|
8149
|
+
} catch {
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
});
|
|
8153
|
+
if (result2.exitCode !== 0) {
|
|
8154
|
+
throw new Error(
|
|
8155
|
+
`Decision step "${args2.stepId}" failed (exit ${result2.exitCode}). Log: ${args2.logPath}${result2.stderr ? `
|
|
8156
|
+
${result2.stderr}` : ""}`
|
|
8157
|
+
);
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
async function requireRegularNonemptyFile2(path, description) {
|
|
8161
|
+
const info = await lstat7(path).catch(() => null);
|
|
8162
|
+
if (!info || info.isSymbolicLink() || !info.isFile() || info.size === 0) {
|
|
8163
|
+
throw new Error(`${description} must be a non-empty regular file: ${path}`);
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
async function readJsonObject2(path, description) {
|
|
8167
|
+
const parsed = JSON.parse(await readFile7(path, "utf8").catch((error) => {
|
|
8168
|
+
throw new Error(`${description} is missing or unreadable: ${path}`, { cause: error });
|
|
8169
|
+
}));
|
|
8170
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8171
|
+
throw new Error(`${description} must be a JSON object: ${path}`);
|
|
8172
|
+
}
|
|
8173
|
+
return parsed;
|
|
8174
|
+
}
|
|
8175
|
+
async function writePrivateAtomic(path, content) {
|
|
8176
|
+
await mkdir5(dirname8(path), { recursive: true, mode: 448 });
|
|
8177
|
+
const temporary = `${path}.${process.pid}.${randomUUID9()}.tmp`;
|
|
8178
|
+
try {
|
|
8179
|
+
await writeFile6(temporary, content, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
8180
|
+
await rename5(temporary, path);
|
|
8181
|
+
} finally {
|
|
8182
|
+
await rm4(temporary, { force: true });
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
async function writePrivateJson2(path, value) {
|
|
8186
|
+
await writePrivateAtomic(path, `${JSON.stringify(value, null, 2)}
|
|
8187
|
+
`);
|
|
8188
|
+
}
|
|
8189
|
+
function producer(reference) {
|
|
8190
|
+
return reference.slice(0, reference.lastIndexOf("."));
|
|
8191
|
+
}
|
|
8192
|
+
function jsonl(rows2) {
|
|
8193
|
+
return rows2.map((row) => JSON.stringify(row)).join("\n") + "\n";
|
|
8194
|
+
}
|
|
8195
|
+
async function runDecisionPipeline(args2) {
|
|
8196
|
+
assertDecisionPlanSupported(args2.plan);
|
|
8197
|
+
const model = resolveDecisionModel(args2.spec.base_model);
|
|
8198
|
+
const specDir = dirname8(resolve7(args2.specPath));
|
|
8199
|
+
const outputDir = resolve7(args2.outputDir ?? (args2.spec.runtime?.artifactRoot ? join12(resolve7(specDir, args2.spec.runtime.artifactRoot), "decision-runs", randomUUID9()) : join12(specDir, DEFAULT_DECISION_RUNS_DIR, randomUUID9())));
|
|
8200
|
+
if (await lstat7(outputDir).catch(() => null)) {
|
|
8201
|
+
throw new Error(`Decision output directory already exists: ${outputDir}`);
|
|
8202
|
+
}
|
|
8203
|
+
await mkdir5(outputDir, { recursive: true, mode: 448 });
|
|
8204
|
+
const labels = decisionLabels(args2.spec.decision);
|
|
8205
|
+
const question = decisionQuestion(args2.spec);
|
|
8206
|
+
const examples = args2.spec.examples.map((example) => ({
|
|
8207
|
+
input: example.input,
|
|
8208
|
+
output: canonicalLabel(labels, example.output)
|
|
8209
|
+
}));
|
|
8210
|
+
const seed = deriveSampleSeed(args2.spec.id ?? args2.spec.name);
|
|
8211
|
+
const split = splitSpecExamples(examples, seed);
|
|
8212
|
+
if (!split.train.length || !split.holdout.length) {
|
|
8213
|
+
throw new Error("Decision runs need at least one training and one held-out example.");
|
|
8214
|
+
}
|
|
8215
|
+
const hp = args2.spec.hyperparameters ?? {};
|
|
8216
|
+
const device = hp.device ?? "auto";
|
|
8217
|
+
const evalIds = split.holdout.map((_, index) => `eval-${index}`);
|
|
8218
|
+
const dataDir = join12(outputDir, "data");
|
|
8219
|
+
await mkdir5(dataDir, { recursive: true, mode: 448 });
|
|
8220
|
+
const trainPath = join12(dataDir, "train.jsonl");
|
|
8221
|
+
const evalInputsPath = join12(dataDir, "eval-inputs.jsonl");
|
|
8222
|
+
await writePrivateAtomic(trainPath, jsonl(split.train));
|
|
8223
|
+
await writePrivateAtomic(evalInputsPath, jsonl(split.holdout.map((example, index) => ({ id: evalIds[index], input: example.input }))));
|
|
8224
|
+
await writePrivateJson2(join12(outputDir, "resolved-workflow.json"), {
|
|
8225
|
+
spec: args2.spec,
|
|
8226
|
+
plan: args2.plan,
|
|
8227
|
+
model: { id: model.id, revision: model.revision },
|
|
8228
|
+
question,
|
|
8229
|
+
split: { seed, train: split.train.length, eval: split.holdout.length }
|
|
8230
|
+
});
|
|
8231
|
+
const spawnStep = args2.spawnStep ?? defaultDecisionSpawn;
|
|
8232
|
+
const producedModels = /* @__PURE__ */ new Map();
|
|
8233
|
+
const reports = /* @__PURE__ */ new Map();
|
|
8234
|
+
const steps = [];
|
|
8235
|
+
let tunedModelDir;
|
|
8236
|
+
for (const step of args2.plan.steps) {
|
|
8237
|
+
const stepDir = join12(outputDir, step.id);
|
|
8238
|
+
await mkdir5(stepDir, { recursive: true, mode: 448 });
|
|
8239
|
+
const configPath2 = join12(stepDir, "config.json");
|
|
8240
|
+
const logPath = join12(stepDir, "step.log");
|
|
8241
|
+
const metricsPath = join12(stepDir, "metrics.json");
|
|
8242
|
+
if (step.uses === "train") {
|
|
8243
|
+
const modelDir = join12(stepDir, "model");
|
|
8244
|
+
args2.onProgress?.(`${step.id}: fine-tuning ${model.id} on ${split.train.length} examples (log: ${logPath})`);
|
|
8245
|
+
await writePrivateJson2(configPath2, {
|
|
8246
|
+
model: model.id,
|
|
8247
|
+
revision: model.revision,
|
|
8248
|
+
question,
|
|
8249
|
+
train_path: trainPath,
|
|
8250
|
+
output_dir: modelDir,
|
|
8251
|
+
metrics_path: metricsPath,
|
|
8252
|
+
epochs: hp.n_epochs ?? model.defaultEpochs,
|
|
8253
|
+
learning_rate: hp.learning_rate ?? model.defaultLearningRate,
|
|
8254
|
+
batch_size: hp.batch_size ?? model.defaultBatchSize,
|
|
8255
|
+
freeze_encoder: hp.freeze_encoder ?? false,
|
|
8256
|
+
shuffle_options: hp.shuffle_options ?? true,
|
|
8257
|
+
seed: hp.seed ?? 0,
|
|
8258
|
+
device
|
|
8259
|
+
});
|
|
8260
|
+
await spawnStep({ entrypoint: "train.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
8261
|
+
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
8262
|
+
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
8263
|
+
await requireRegularNonemptyFile2(join12(modelDir, "model.safetensors"), `Decision step "${step.id}" model.safetensors`);
|
|
8264
|
+
await readJsonObject2(join12(modelDir, "rl_agent_config.json"), `Decision step "${step.id}" rl_agent_config.json`);
|
|
8265
|
+
producedModels.set(step.id, modelDir);
|
|
8266
|
+
tunedModelDir = modelDir;
|
|
8267
|
+
steps.push({ id: step.id, uses: step.uses, model: modelDir, training: metrics });
|
|
8268
|
+
continue;
|
|
8269
|
+
}
|
|
8270
|
+
if (step.uses === "evaluate") {
|
|
8271
|
+
const source = step.with.model;
|
|
8272
|
+
const tuned = source !== "base";
|
|
8273
|
+
const modelDir = tuned ? producedModels.get(producer(source.from)) : void 0;
|
|
8274
|
+
if (tuned && !modelDir) throw new Error(`Step ${step.id} needs a model from ${source.from}.`);
|
|
8275
|
+
const predictionsPath = join12(stepDir, "predictions.jsonl");
|
|
8276
|
+
args2.onProgress?.(`${step.id}: evaluating ${tuned ? "tuned" : "base"} model on ${split.holdout.length} held-out examples`);
|
|
8277
|
+
await writePrivateJson2(configPath2, {
|
|
8278
|
+
model: modelDir ?? model.id,
|
|
8279
|
+
...tuned ? {} : { revision: model.revision },
|
|
8280
|
+
question,
|
|
8281
|
+
inputs_path: evalInputsPath,
|
|
8282
|
+
output_path: predictionsPath,
|
|
8283
|
+
metrics_path: metricsPath,
|
|
8284
|
+
device
|
|
8285
|
+
});
|
|
8286
|
+
await spawnStep({ entrypoint: "evaluate.py", configPath: configPath2, logPath, stepId: step.id, onProgress: args2.onProgress });
|
|
8287
|
+
const metrics = await readJsonObject2(metricsPath, `Decision step "${step.id}" metrics`);
|
|
8288
|
+
if (metrics.ok !== true) throw new Error(`Decision step "${step.id}" metrics must report ok: true.`);
|
|
8289
|
+
const reportPath2 = join12(stepDir, "report.json");
|
|
8290
|
+
const scored = scoreDecisionPredictions({
|
|
8291
|
+
kind: tuned ? "candidate" : "baseline",
|
|
8292
|
+
modelId: modelDir ? fileUri(modelDir) : model.id,
|
|
8293
|
+
labels,
|
|
8294
|
+
examples: split.holdout,
|
|
8295
|
+
predictions: await readPredictions(predictionsPath, evalIds, labels),
|
|
8296
|
+
outputPath: reportPath2,
|
|
8297
|
+
sampleSeed: seed
|
|
8298
|
+
});
|
|
8299
|
+
await writePrivateJson2(reportPath2, { ...scored.report, decision_metrics: scored.metrics });
|
|
8300
|
+
reports.set(step.id, scored);
|
|
8301
|
+
steps.push({ id: step.id, uses: step.uses, model: modelDir ?? model.id, report: scored.report, decision_metrics: scored.metrics });
|
|
8302
|
+
continue;
|
|
8303
|
+
}
|
|
8304
|
+
if (step.uses !== "compare") throw new Error(`Decision runner does not implement ${step.uses} (step ${step.id}).`);
|
|
8305
|
+
const before = reports.get(producer(step.with.before.from));
|
|
8306
|
+
const after = reports.get(producer(step.with.after.from));
|
|
8307
|
+
if (!before || !after) throw new Error(`Step ${step.id} needs both evaluation reports.`);
|
|
8308
|
+
const comparison = {
|
|
8309
|
+
...compareEvalReports(before.report, after.report),
|
|
8310
|
+
log_loss_delta: after.metrics.log_loss - before.metrics.log_loss,
|
|
8311
|
+
brier_delta: after.metrics.brier - before.metrics.brier
|
|
8312
|
+
};
|
|
8313
|
+
await writePrivateJson2(join12(stepDir, "comparison.json"), comparison);
|
|
8314
|
+
steps.push({ id: step.id, uses: step.uses, comparison });
|
|
8315
|
+
}
|
|
8316
|
+
const reportPath = join12(outputDir, "report.json");
|
|
8317
|
+
const report = {
|
|
8318
|
+
status: "succeeded",
|
|
8319
|
+
engine: "decision",
|
|
8320
|
+
...args2.plan.name ? { name: args2.plan.name } : {},
|
|
8321
|
+
spec: resolve7(args2.specPath),
|
|
8322
|
+
base_model: model.id,
|
|
8323
|
+
base_model_revision: model.revision,
|
|
8324
|
+
question: { type: question.type, labels },
|
|
8325
|
+
split: { seed, train: split.train.length, eval: split.holdout.length, eval_split: "spec_holdout" },
|
|
8326
|
+
...tunedModelDir ? { model_dir: tunedModelDir } : {},
|
|
8327
|
+
steps,
|
|
8328
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8329
|
+
};
|
|
8330
|
+
await writePrivateJson2(reportPath, report);
|
|
8331
|
+
return {
|
|
8332
|
+
status: "succeeded",
|
|
8333
|
+
engine: "decision",
|
|
8334
|
+
...args2.plan.name ? { name: args2.plan.name } : {},
|
|
8335
|
+
run_dir: outputDir,
|
|
8336
|
+
report_path: reportPath,
|
|
8337
|
+
...tunedModelDir ? { model_dir: tunedModelDir } : {},
|
|
8338
|
+
steps
|
|
8339
|
+
};
|
|
8340
|
+
}
|
|
8341
|
+
|
|
6859
8342
|
// src/commands/pipeline.ts
|
|
6860
8343
|
var DEFAULT_SPEC_FILE = "tunedtensor.json";
|
|
6861
8344
|
function parseList(value) {
|
|
@@ -6865,10 +8348,10 @@ function parseList(value) {
|
|
|
6865
8348
|
return ids;
|
|
6866
8349
|
}
|
|
6867
8350
|
function loadPipelineFile(path) {
|
|
6868
|
-
const resolved =
|
|
8351
|
+
const resolved = resolve8(path);
|
|
6869
8352
|
if (!existsSync4(resolved)) throw new Error(`Pipeline file not found: ${path}. Run \`tt pipeline init\` to create one.`);
|
|
6870
8353
|
try {
|
|
6871
|
-
return JSON.parse(
|
|
8354
|
+
return JSON.parse(readFileSync6(resolved, "utf8"));
|
|
6872
8355
|
} catch (error) {
|
|
6873
8356
|
throw new Error(`Pipeline file must be JSON: ${error.message}`);
|
|
6874
8357
|
}
|
|
@@ -6877,23 +8360,27 @@ function pipelineFromSpec(input) {
|
|
|
6877
8360
|
return pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation);
|
|
6878
8361
|
}
|
|
6879
8362
|
function resolvePipelineDocument(options, specRequired) {
|
|
6880
|
-
const specPath =
|
|
8363
|
+
const specPath = resolve8(options.spec);
|
|
6881
8364
|
const requested = options.file ? loadPipelineFile(options.file) : void 0;
|
|
6882
8365
|
if (!existsSync4(specPath)) {
|
|
6883
8366
|
if (specRequired) throw new Error(`Behavior spec not found: ${options.spec}.`);
|
|
6884
8367
|
if (requested !== void 0) return { document: parsePipeline(requested) };
|
|
6885
8368
|
throw new Error(`Behavior spec not found: ${options.spec}. Create tunedtensor.json or pass an explicit --file for a recipe-only preview.`);
|
|
6886
8369
|
}
|
|
6887
|
-
const source =
|
|
8370
|
+
const source = readFileSync6(specPath, "utf8");
|
|
6888
8371
|
const input = parseLocalRunInput(JSON.parse(source), specPath);
|
|
6889
8372
|
const document = pipelineForRunInput(input, requested);
|
|
6890
8373
|
const spec = {
|
|
6891
8374
|
path: specPath,
|
|
6892
8375
|
sha256: specHash(source),
|
|
6893
|
-
engine: input
|
|
8376
|
+
engine: specEngine(input)
|
|
6894
8377
|
};
|
|
6895
8378
|
return { document, input, spec };
|
|
6896
8379
|
}
|
|
8380
|
+
function specEngine(input) {
|
|
8381
|
+
if (input.kind === "foundation-spec") return "foundation";
|
|
8382
|
+
return input.kind === "decision-spec" ? "decision" : "adapter";
|
|
8383
|
+
}
|
|
6897
8384
|
function outputPlan(plan, hostWarnings = []) {
|
|
6898
8385
|
if (isJsonMode()) {
|
|
6899
8386
|
return printJson(hostWarnings.length ? { ...plan, host_warnings: hostWarnings } : plan);
|
|
@@ -6910,16 +8397,35 @@ function outputPlan(plan, hostWarnings = []) {
|
|
|
6910
8397
|
async function hostWarningsForPipeline(document, input) {
|
|
6911
8398
|
const snapshot = await readHardwareSnapshot();
|
|
6912
8399
|
if (!snapshot) return [];
|
|
8400
|
+
if (input?.kind === "decision-spec") return warningsFromSnapshot(snapshot.capabilities, { engine: "decision" });
|
|
6913
8401
|
const engine = isFoundationPipeline(parsePipeline(document)) ? "foundation" : "adapter";
|
|
6914
8402
|
const baseModel = input && input.kind !== "foundation-spec" ? input.request.spec_snapshot.base_model : void 0;
|
|
6915
8403
|
return warningsFromSnapshot(snapshot.capabilities, { engine, baseModel });
|
|
6916
8404
|
}
|
|
8405
|
+
function percent(value) {
|
|
8406
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
8407
|
+
}
|
|
8408
|
+
function printDecisionSummary(result2) {
|
|
8409
|
+
for (const step of result2.steps) {
|
|
8410
|
+
if (step.decision_metrics) {
|
|
8411
|
+
const metrics = step.decision_metrics;
|
|
8412
|
+
console.log(`${step.id.padEnd(12)} accuracy ${percent(metrics.accuracy)} log-loss ${metrics.log_loss.toFixed(3)} brier ${metrics.brier.toFixed(3)} (${step.report?.total ?? 0} held-out)`);
|
|
8413
|
+
}
|
|
8414
|
+
if (step.comparison) {
|
|
8415
|
+
const comparison = step.comparison;
|
|
8416
|
+
const sign = (value) => value >= 0 ? "+" : "";
|
|
8417
|
+
console.log(`${step.id.padEnd(12)} accuracy ${sign(comparison.pass_rate_delta)}${percent(comparison.pass_rate_delta)} log-loss ${sign(comparison.log_loss_delta)}${comparison.log_loss_delta.toFixed(3)} improved ${comparison.improvements}, regressed ${comparison.regressions}`);
|
|
8418
|
+
}
|
|
8419
|
+
}
|
|
8420
|
+
if (result2.model_dir) console.log(`Tuned decision model: ${result2.model_dir}`);
|
|
8421
|
+
printSuccess(`Decision pipeline completed. Report: ${result2.report_path}`);
|
|
8422
|
+
}
|
|
6917
8423
|
function registerPipelineCommands(parent) {
|
|
6918
8424
|
const pipeline2 = parent.command("pipeline").description("Create, validate, inspect, and safely dry-run ordered pipeline recipes");
|
|
6919
8425
|
pipeline2.command("init").description("Write a canonical v1 pipeline recipe").option("-f, --file <path>", "Legacy standalone recipe output (default: embed in tunedtensor.json)").option("--engine <engine>", "adapter (default) or foundation").option("--spec <path>", "Foundation spec whose hyperparameters stamp the DAG").action(async (options) => {
|
|
6920
8426
|
if (!options.file) {
|
|
6921
|
-
const path2 =
|
|
6922
|
-
const raw = JSON.parse(
|
|
8427
|
+
const path2 = resolve8(options.spec ?? DEFAULT_SPEC_FILE);
|
|
8428
|
+
const raw = JSON.parse(readFileSync6(path2, "utf8"));
|
|
6923
8429
|
const input = parseLocalRunInput(raw, path2);
|
|
6924
8430
|
if (input.kind === "request") throw new Error("Pipeline init requires a behavior spec.");
|
|
6925
8431
|
if (options.engine && options.engine !== (input.spec.engine ?? "adapter")) throw new Error("--engine must match the spec.");
|
|
@@ -6937,12 +8443,12 @@ function registerPipelineCommands(parent) {
|
|
|
6937
8443
|
if (engine !== "foundation" && options.spec) {
|
|
6938
8444
|
throw new Error("--spec is only valid with --engine foundation.");
|
|
6939
8445
|
}
|
|
6940
|
-
const path =
|
|
8446
|
+
const path = resolve8(options.file);
|
|
6941
8447
|
if (existsSync4(path)) throw new Error(`Pipeline file already exists: ${options.file}.`);
|
|
6942
8448
|
let recipe;
|
|
6943
8449
|
if (engine === "foundation") {
|
|
6944
8450
|
if (options.spec) {
|
|
6945
|
-
const input = await loadLocalRunInput(
|
|
8451
|
+
const input = await loadLocalRunInput(resolve8(options.spec));
|
|
6946
8452
|
if (input.kind !== "foundation-spec") {
|
|
6947
8453
|
throw new Error("--spec must be a foundation tunedtensor.json.");
|
|
6948
8454
|
}
|
|
@@ -6959,7 +8465,7 @@ function registerPipelineCommands(parent) {
|
|
|
6959
8465
|
printSuccess(`Created ${options.file}`);
|
|
6960
8466
|
});
|
|
6961
8467
|
pipeline2.command("migrate").description("Move legacy configuration into tunedtensor.json, retaining .bak copies").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).action(async (options) => {
|
|
6962
|
-
const result2 = await migrateProjectWorkflow(
|
|
8468
|
+
const result2 = await migrateProjectWorkflow(resolve8(options.spec));
|
|
6963
8469
|
if (isJsonMode()) return printJson(result2);
|
|
6964
8470
|
printSuccess(result2.migrated ? `Migrated ${result2.path}; originals retained as .bak files.` : "No legacy configuration found.");
|
|
6965
8471
|
});
|
|
@@ -6983,7 +8489,7 @@ function registerPipelineCommands(parent) {
|
|
|
6983
8489
|
const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
|
|
6984
8490
|
outputPlan({ ...plan, ...spec ? { spec } : {} }, await hostWarningsForPipeline(document, input));
|
|
6985
8491
|
});
|
|
6986
|
-
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>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options, command) => {
|
|
8492
|
+
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>", "Explicit pipeline recipe (default: derive from behavior spec)").option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation or decision run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").option("--verbose", "Also stream training and evaluation process logs").option("--quiet", "Do not print live stage progress").action(async (options, command) => {
|
|
6987
8493
|
if (options.output && options.resume) {
|
|
6988
8494
|
throw new Error("--output and --resume are mutually exclusive.");
|
|
6989
8495
|
}
|
|
@@ -7016,8 +8522,8 @@ function registerPipelineCommands(parent) {
|
|
|
7016
8522
|
const result3 = await runFoundationPipeline({
|
|
7017
8523
|
spec: input.spec,
|
|
7018
8524
|
plan,
|
|
7019
|
-
specPath:
|
|
7020
|
-
...options.output || options.resume ? { outputDir:
|
|
8525
|
+
specPath: resolve8(options.spec),
|
|
8526
|
+
...options.output || options.resume ? { outputDir: resolve8(options.resume ?? options.output) } : {},
|
|
7021
8527
|
resume: Boolean(options.resume),
|
|
7022
8528
|
gpu: config.gpu
|
|
7023
8529
|
});
|
|
@@ -7025,15 +8531,40 @@ function registerPipelineCommands(parent) {
|
|
|
7025
8531
|
printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
|
|
7026
8532
|
return;
|
|
7027
8533
|
}
|
|
8534
|
+
if (input.kind === "decision-spec") {
|
|
8535
|
+
if (options.resume) throw new Error("--resume is only valid for foundation pipelines.");
|
|
8536
|
+
const result3 = await runDecisionPipeline({
|
|
8537
|
+
spec: input.spec,
|
|
8538
|
+
plan,
|
|
8539
|
+
specPath: resolve8(options.spec),
|
|
8540
|
+
...options.output ? { outputDir: resolve8(options.output) } : {},
|
|
8541
|
+
onProgress: isJsonMode() ? void 0 : (message) => console.error(message)
|
|
8542
|
+
});
|
|
8543
|
+
if (isJsonMode()) return printJson({ ...result3, spec });
|
|
8544
|
+
printDecisionSummary(result3);
|
|
8545
|
+
return;
|
|
8546
|
+
}
|
|
7028
8547
|
if (options.output || options.resume) {
|
|
7029
|
-
throw new Error("--output and --resume
|
|
8548
|
+
throw new Error("--output is only valid for foundation and decision pipelines; --resume only for foundation.");
|
|
7030
8549
|
}
|
|
7031
8550
|
const localPipeline = {
|
|
7032
8551
|
version: 1,
|
|
7033
8552
|
...plan.name ? { name: plan.name } : {},
|
|
7034
8553
|
steps: plan.steps.map(({ transfers: _transfers, ...step }) => step)
|
|
7035
8554
|
};
|
|
7036
|
-
const
|
|
8555
|
+
const reporter = options.quiet ? void 0 : createRunConsoleReporter({ verbose: options.verbose });
|
|
8556
|
+
const { request: request2 } = await ensureDatasetCached({ request: input.request, config, reporter });
|
|
8557
|
+
const result2 = await runLocalPipeline({
|
|
8558
|
+
request: request2,
|
|
8559
|
+
config,
|
|
8560
|
+
pipeline: localPipeline,
|
|
8561
|
+
...input.kind === "spec" ? { projectSpec: input.spec } : {},
|
|
8562
|
+
...spec ? { specFile: { path: spec.path, sha256: spec.sha256 } } : {},
|
|
8563
|
+
...reporter ? { reporter } : {}
|
|
8564
|
+
});
|
|
8565
|
+
if (!isJsonMode()) {
|
|
8566
|
+
console.error(`Run ${result2.request.run_id}: \`tt runs audit ${result2.request.run_id}\` verifies its record and shows timing and provenance.`);
|
|
8567
|
+
}
|
|
7037
8568
|
if (isJsonMode()) return printJson({ ...result2, spec });
|
|
7038
8569
|
printSuccess(`Pipeline completed with status ${result2.status}.`);
|
|
7039
8570
|
});
|
|
@@ -7043,7 +8574,7 @@ function registerPipelineCommands(parent) {
|
|
|
7043
8574
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
7044
8575
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
7045
8576
|
import { Writable as Writable2 } from "stream";
|
|
7046
|
-
import
|
|
8577
|
+
import chalk10 from "chalk";
|
|
7047
8578
|
async function promptForApiKey(input = stdin2, output = stdout2) {
|
|
7048
8579
|
if (input.isTTY !== true || output.isTTY !== true) {
|
|
7049
8580
|
throw new Error(
|
|
@@ -7085,9 +8616,9 @@ function registerAuthCommands(parent) {
|
|
|
7085
8616
|
const settingsUrl = `${getBaseUrl(parent.optsWithGlobals()).replace(/\/$/, "")}/dashboard/settings`;
|
|
7086
8617
|
console.log();
|
|
7087
8618
|
console.log(
|
|
7088
|
-
` To get your TT access token, go to ${
|
|
8619
|
+
` To get your TT access token, go to ${chalk10.bold("Settings \u2192 API Keys")} in the Tuned Tensor dashboard:`
|
|
7089
8620
|
);
|
|
7090
|
-
console.log(` ${
|
|
8621
|
+
console.log(` ${chalk10.hex("#A78BFA").underline(settingsUrl)}`);
|
|
7091
8622
|
console.log();
|
|
7092
8623
|
apiKey = await promptForApiKey();
|
|
7093
8624
|
}
|
|
@@ -7143,7 +8674,7 @@ function registerAuthCommands(parent) {
|
|
|
7143
8674
|
}
|
|
7144
8675
|
|
|
7145
8676
|
// src/commands/balance.ts
|
|
7146
|
-
import
|
|
8677
|
+
import chalk11 from "chalk";
|
|
7147
8678
|
var KIND_LABELS = {
|
|
7148
8679
|
signup_bonus: "Signup bonus",
|
|
7149
8680
|
topup: "Top-up",
|
|
@@ -7158,8 +8689,8 @@ function formatCents(cents) {
|
|
|
7158
8689
|
return `${sign}$${(abs / 100).toFixed(2)}`;
|
|
7159
8690
|
}
|
|
7160
8691
|
function formatSigned(cents) {
|
|
7161
|
-
if (cents > 0) return
|
|
7162
|
-
if (cents < 0) return
|
|
8692
|
+
if (cents > 0) return chalk11.green(`+${formatCents(cents)}`);
|
|
8693
|
+
if (cents < 0) return chalk11.red(formatCents(cents));
|
|
7163
8694
|
return formatCents(cents);
|
|
7164
8695
|
}
|
|
7165
8696
|
function registerBalanceCommands(parent) {
|
|
@@ -7174,7 +8705,7 @@ function registerBalanceCommands(parent) {
|
|
|
7174
8705
|
return printJson({ balance, transactions });
|
|
7175
8706
|
}
|
|
7176
8707
|
const lowBalance = balance.balance_cents < 100;
|
|
7177
|
-
const balanceLine = lowBalance ?
|
|
8708
|
+
const balanceLine = lowBalance ? chalk11.red(formatCents(balance.balance_cents)) + chalk11.dim(" (low)") : chalk11.bold.green(formatCents(balance.balance_cents));
|
|
7178
8709
|
printDetail([
|
|
7179
8710
|
["Credits", balanceLine],
|
|
7180
8711
|
[
|
|
@@ -7189,7 +8720,7 @@ function registerBalanceCommands(parent) {
|
|
|
7189
8720
|
]);
|
|
7190
8721
|
if (transactions && transactions.length > 0) {
|
|
7191
8722
|
console.log(`
|
|
7192
|
-
${
|
|
8723
|
+
${chalk11.bold("Recent transactions")}`);
|
|
7193
8724
|
printTable(
|
|
7194
8725
|
["Date", "Type", "Amount", "Balance", "Reference"],
|
|
7195
8726
|
transactions.map((row) => [
|
|
@@ -7197,19 +8728,19 @@ ${chalk8.bold("Recent transactions")}`);
|
|
|
7197
8728
|
KIND_LABELS[row.kind] ?? row.kind,
|
|
7198
8729
|
formatSigned(row.amount_cents),
|
|
7199
8730
|
formatCents(row.balance_after_cents),
|
|
7200
|
-
row.reference_id ? shortId(row.reference_id) :
|
|
8731
|
+
row.reference_id ? shortId(row.reference_id) : chalk11.dim("\u2014")
|
|
7201
8732
|
])
|
|
7202
8733
|
);
|
|
7203
8734
|
} else {
|
|
7204
8735
|
console.log(
|
|
7205
8736
|
`
|
|
7206
|
-
${
|
|
8737
|
+
${chalk11.dim("No transactions yet \u2014 run `tt topup` to add credits.")}`
|
|
7207
8738
|
);
|
|
7208
8739
|
}
|
|
7209
8740
|
if (lowBalance) {
|
|
7210
8741
|
console.log(
|
|
7211
8742
|
`
|
|
7212
|
-
${formatStatus("preparing")} ${
|
|
8743
|
+
${formatStatus("preparing")} ${chalk11.yellow(
|
|
7213
8744
|
"Run `tt topup` to add more credits before starting a run."
|
|
7214
8745
|
)}`
|
|
7215
8746
|
);
|
|
@@ -7218,7 +8749,7 @@ ${formatStatus("preparing")} ${chalk8.yellow(
|
|
|
7218
8749
|
}
|
|
7219
8750
|
|
|
7220
8751
|
// src/commands/topup.ts
|
|
7221
|
-
import
|
|
8752
|
+
import chalk12 from "chalk";
|
|
7222
8753
|
import open4 from "open";
|
|
7223
8754
|
var PRESETS_USD = [10, 25, 50, 100];
|
|
7224
8755
|
var MIN_USD = 5;
|
|
@@ -7257,9 +8788,9 @@ function registerTopupCommands(parent) {
|
|
|
7257
8788
|
"amount is required in --json mode (use --amount <usd>)"
|
|
7258
8789
|
);
|
|
7259
8790
|
}
|
|
7260
|
-
console.log(
|
|
8791
|
+
console.log(chalk12.bold("Add credits via Stripe Checkout"));
|
|
7261
8792
|
console.log(
|
|
7262
|
-
|
|
8793
|
+
chalk12.dim(
|
|
7263
8794
|
`Quick picks: ${PRESETS_USD.map((n) => `$${n}`).join(", ")}`
|
|
7264
8795
|
)
|
|
7265
8796
|
);
|
|
@@ -7286,8 +8817,8 @@ function registerTopupCommands(parent) {
|
|
|
7286
8817
|
}
|
|
7287
8818
|
const url = data3.checkout_url;
|
|
7288
8819
|
console.log(
|
|
7289
|
-
`${
|
|
7290
|
-
` +
|
|
8820
|
+
`${chalk12.bold("Checkout URL:")} ${chalk12.hex("#A78BFA")(url)}
|
|
8821
|
+
` + chalk12.dim("Complete the payment to credit your account.")
|
|
7291
8822
|
);
|
|
7292
8823
|
if (options.open !== false) {
|
|
7293
8824
|
try {
|
|
@@ -7295,7 +8826,7 @@ function registerTopupCommands(parent) {
|
|
|
7295
8826
|
printSuccess("Opened Stripe Checkout in your browser.");
|
|
7296
8827
|
} catch {
|
|
7297
8828
|
console.log(
|
|
7298
|
-
|
|
8829
|
+
chalk12.yellow(
|
|
7299
8830
|
"Could not open browser automatically \u2014 copy the URL above."
|
|
7300
8831
|
)
|
|
7301
8832
|
);
|
|
@@ -7306,30 +8837,30 @@ function registerTopupCommands(parent) {
|
|
|
7306
8837
|
|
|
7307
8838
|
// src/commands/publish.ts
|
|
7308
8839
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
7309
|
-
import { existsSync as existsSync5, readFileSync as
|
|
7310
|
-
import { homedir as
|
|
7311
|
-
import { isAbsolute as isAbsolute5, join as
|
|
8840
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
|
|
8841
|
+
import { homedir as homedir3 } from "os";
|
|
8842
|
+
import { isAbsolute as isAbsolute5, join as join14, resolve as resolve9 } from "path";
|
|
7312
8843
|
import { stdin as defaultStdin, stdout as defaultStdout } from "process";
|
|
7313
8844
|
var MAX_PUBLISH_BYTES = 2 * 1024 * 1024;
|
|
7314
8845
|
function expandPath2(value, baseDirectory, homeDirectory) {
|
|
7315
8846
|
if (value === "~") return homeDirectory;
|
|
7316
|
-
if (value.startsWith("~/")) return
|
|
7317
|
-
return isAbsolute5(value) ?
|
|
8847
|
+
if (value.startsWith("~/")) return resolve9(homeDirectory, value.slice(2));
|
|
8848
|
+
return isAbsolute5(value) ? resolve9(value) : resolve9(baseDirectory, value);
|
|
7318
8849
|
}
|
|
7319
8850
|
function resolvePublishStoreRoot(options) {
|
|
7320
8851
|
const cwd = options?.cwd ?? process.cwd();
|
|
7321
8852
|
const env = options?.env ?? process.env;
|
|
7322
|
-
const homeDirectory = options?.homeDirectory ?? (env.HOME ?
|
|
7323
|
-
const localConfigPath =
|
|
7324
|
-
const projectPath =
|
|
8853
|
+
const homeDirectory = options?.homeDirectory ?? (env.HOME ? resolve9(env.HOME) : homedir3());
|
|
8854
|
+
const localConfigPath = join14(cwd, "local-runner.json");
|
|
8855
|
+
const projectPath = join14(cwd, "tunedtensor.json");
|
|
7325
8856
|
if (existsSync5(projectPath)) {
|
|
7326
|
-
const spec = localBehaviorSpecFileSchema.parse(JSON.parse(
|
|
8857
|
+
const spec = localBehaviorSpecFileSchema.parse(JSON.parse(readFileSync7(projectPath, "utf8")));
|
|
7327
8858
|
if (existsSync5(localConfigPath) && (spec.runtime || spec.evaluation)) throw new Error("Conflicting configuration sources; migrate local-runner.json first.");
|
|
7328
8859
|
if (!existsSync5(localConfigPath)) return expandPath2(spec.runtime?.storeRoot ?? (env.TT_LOCAL_HOME?.trim() || DEFAULT_PROJECT_STORE_ROOT), cwd, homeDirectory);
|
|
7329
8860
|
}
|
|
7330
8861
|
if (existsSync5(localConfigPath)) {
|
|
7331
8862
|
try {
|
|
7332
|
-
const raw = JSON.parse(
|
|
8863
|
+
const raw = JSON.parse(readFileSync7(localConfigPath, "utf-8"));
|
|
7333
8864
|
if (typeof raw.storeRoot === "string" && raw.storeRoot.trim()) {
|
|
7334
8865
|
return expandPath2(raw.storeRoot, cwd, homeDirectory);
|
|
7335
8866
|
}
|
|
@@ -7393,7 +8924,7 @@ function assertPublishPayloadSize(payload) {
|
|
|
7393
8924
|
}
|
|
7394
8925
|
}
|
|
7395
8926
|
function registerPublishCommand(parent) {
|
|
7396
|
-
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) => {
|
|
8927
|
+
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").option("--allow-unverified", "Publish evidence that cannot be fully verified (older TT versions, unreadable events)").action(async (runId, cmdOpts) => {
|
|
7397
8928
|
const inherited = parent.optsWithGlobals();
|
|
7398
8929
|
const opts = {
|
|
7399
8930
|
apiKey: cmdOpts.apiKey ?? inherited.apiKey,
|
|
@@ -7412,12 +8943,27 @@ function registerPublishCommand(parent) {
|
|
|
7412
8943
|
const report = runReportSchema.parse(rawReport);
|
|
7413
8944
|
const specRecord = await store.getSpec(run.behavior_spec_id);
|
|
7414
8945
|
const exampleCount = report.candidate.results.length || report.baseline.results.length;
|
|
8946
|
+
const audit = await auditRun(store, run.id);
|
|
8947
|
+
if (audit.verdict === "tampered") {
|
|
8948
|
+
throw new Error(
|
|
8949
|
+
`Run ${run.id} evidence does not verify: ${audit.findings.join(" ")} Inspect it with \`tt runs audit ${run.id}\`; tampered runs are not published.`
|
|
8950
|
+
);
|
|
8951
|
+
}
|
|
7415
8952
|
const payload = {
|
|
7416
8953
|
spec: {
|
|
7417
8954
|
id: specRecord.id,
|
|
7418
8955
|
...specRecord.spec
|
|
7419
8956
|
},
|
|
7420
|
-
report
|
|
8957
|
+
report: {
|
|
8958
|
+
...report,
|
|
8959
|
+
audit: {
|
|
8960
|
+
verdict: audit.verdict,
|
|
8961
|
+
findings: audit.findings,
|
|
8962
|
+
event_chain: { status: audit.chain.status, events: audit.chain.events, head_hash: audit.chain.head_hash },
|
|
8963
|
+
report_sha256: audit.report.recorded_sha256,
|
|
8964
|
+
timeline: audit.timeline
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
7421
8967
|
};
|
|
7422
8968
|
assertPublishPayloadSize(payload);
|
|
7423
8969
|
if (isJsonMode() && cmdOpts.dryRun) {
|
|
@@ -7445,6 +8991,14 @@ function registerPublishCommand(parent) {
|
|
|
7445
8991
|
printWarning(
|
|
7446
8992
|
"Prompts and model outputs from the run report will be uploaded to your Tuned Tensor account."
|
|
7447
8993
|
);
|
|
8994
|
+
if (audit.verdict !== "verified") {
|
|
8995
|
+
printWarning(`Run evidence is unverified: ${audit.findings.join(" ")}`);
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
if (audit.verdict !== "verified" && !cmdOpts.allowUnverified && !cmdOpts.dryRun) {
|
|
8999
|
+
throw new Error(
|
|
9000
|
+
`Run ${run.id} evidence cannot be fully verified: ${audit.findings.join(" ")} Review it with \`tt runs audit\`, then pass --allow-unverified to publish it anyway; the dashboard will show it as unverified.`
|
|
9001
|
+
);
|
|
7448
9002
|
}
|
|
7449
9003
|
if (cmdOpts.dryRun) {
|
|
7450
9004
|
if (isJsonMode()) {
|
|
@@ -7489,7 +9043,7 @@ function registerPublishCommand(parent) {
|
|
|
7489
9043
|
}
|
|
7490
9044
|
|
|
7491
9045
|
// src/commands/specs.ts
|
|
7492
|
-
import { readFileSync as
|
|
9046
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
7493
9047
|
|
|
7494
9048
|
// src/resolve.ts
|
|
7495
9049
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -7593,7 +9147,7 @@ var SpecBodyError = class extends Error {
|
|
|
7593
9147
|
function loadSpecBody(filePath, mode) {
|
|
7594
9148
|
let parsed;
|
|
7595
9149
|
try {
|
|
7596
|
-
parsed = JSON.parse(
|
|
9150
|
+
parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
7597
9151
|
} catch (e) {
|
|
7598
9152
|
throw new SpecBodyError(
|
|
7599
9153
|
`Could not parse ${filePath}: ${e.message}`
|
|
@@ -7722,7 +9276,7 @@ function registerSpecsCommands(parent) {
|
|
|
7722
9276
|
}
|
|
7723
9277
|
|
|
7724
9278
|
// src/commands/datasets.ts
|
|
7725
|
-
import { existsSync as existsSync6, readFileSync as
|
|
9279
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
|
|
7726
9280
|
var DOCUMENT_OCR_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
7727
9281
|
function formatBytes(bytes) {
|
|
7728
9282
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -7852,7 +9406,7 @@ function parseDatasetFormat(value) {
|
|
|
7852
9406
|
throw new Error("--format must be one of: jsonl, document_ocr_jsonl");
|
|
7853
9407
|
}
|
|
7854
9408
|
function validateDatasetFile(file, requestedFormat) {
|
|
7855
|
-
const lines =
|
|
9409
|
+
const lines = readFileSync9(file, "utf8").split(/\r?\n/);
|
|
7856
9410
|
const errors = [];
|
|
7857
9411
|
let rowCount = 0;
|
|
7858
9412
|
let detectedFormat = null;
|
|
@@ -7984,7 +9538,7 @@ function registerDatasetsCommands(parent) {
|
|
|
7984
9538
|
}
|
|
7985
9539
|
const format = validateDatasetFile(file, parseDatasetFormat(cmdOpts.format));
|
|
7986
9540
|
const name = cmdOpts.name || file.split("/").pop().replace(/\.jsonl?$/, "");
|
|
7987
|
-
const fileBytes =
|
|
9541
|
+
const fileBytes = readFileSync9(file);
|
|
7988
9542
|
const uploadUrl = await post(
|
|
7989
9543
|
"/datasets/upload-url",
|
|
7990
9544
|
{
|
|
@@ -8029,12 +9583,12 @@ function registerDatasetsCommands(parent) {
|
|
|
8029
9583
|
}
|
|
8030
9584
|
|
|
8031
9585
|
// src/commands/label.ts
|
|
8032
|
-
import { existsSync as existsSync7, readFileSync as
|
|
9586
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
8033
9587
|
function formatCents2(cents) {
|
|
8034
9588
|
return `$${(cents / 100).toFixed(2)}`;
|
|
8035
9589
|
}
|
|
8036
9590
|
function sleep(ms) {
|
|
8037
|
-
return new Promise((
|
|
9591
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
8038
9592
|
}
|
|
8039
9593
|
function parseCsvHeader(line) {
|
|
8040
9594
|
const fields = [];
|
|
@@ -8073,7 +9627,7 @@ function validateUnlabeledFile(file, inputColumn) {
|
|
|
8073
9627
|
"CSV uploads need --input-column <name> to say which column holds the input text."
|
|
8074
9628
|
);
|
|
8075
9629
|
}
|
|
8076
|
-
const firstLine =
|
|
9630
|
+
const firstLine = readFileSync10(file, "utf8").split(/\r?\n/, 1)[0] ?? "";
|
|
8077
9631
|
const columns = parseCsvHeader(firstLine);
|
|
8078
9632
|
if (!columns.includes(inputColumn)) {
|
|
8079
9633
|
throw new Error(
|
|
@@ -8085,7 +9639,7 @@ function validateUnlabeledFile(file, inputColumn) {
|
|
|
8085
9639
|
if (!lower.endsWith(".jsonl") && !lower.endsWith(".json")) {
|
|
8086
9640
|
throw new Error("Labeling source file must be .jsonl or .csv");
|
|
8087
9641
|
}
|
|
8088
|
-
const lines =
|
|
9642
|
+
const lines = readFileSync10(file, "utf8").split(/\r?\n/);
|
|
8089
9643
|
const errors = [];
|
|
8090
9644
|
let rowCount = 0;
|
|
8091
9645
|
for (const [index, rawLine] of lines.entries()) {
|
|
@@ -8208,7 +9762,7 @@ function registerLabelCommands(parent) {
|
|
|
8208
9762
|
const { format } = validateUnlabeledFile(file, cmdOpts.inputColumn);
|
|
8209
9763
|
const specId = await resolveSpecId(cmdOpts.spec, opts);
|
|
8210
9764
|
const name = cmdOpts.name || file.split("/").pop().replace(/\.(jsonl|json|csv)$/, "");
|
|
8211
|
-
const fileBytes =
|
|
9765
|
+
const fileBytes = readFileSync10(file);
|
|
8212
9766
|
const contentType = format === "csv" ? "text/csv" : "application/jsonl";
|
|
8213
9767
|
const { data: uploadUrl } = await post(
|
|
8214
9768
|
"/labeling-jobs/upload-url",
|
|
@@ -8381,7 +9935,7 @@ function registerLabelCommands(parent) {
|
|
|
8381
9935
|
printError("Provide --output <text> or --file <path>.");
|
|
8382
9936
|
process.exit(1);
|
|
8383
9937
|
}
|
|
8384
|
-
const output = cmdOpts.output ??
|
|
9938
|
+
const output = cmdOpts.output ?? readFileSync10(cmdOpts.file, "utf8");
|
|
8385
9939
|
const jobId = await resolveLabelingJobId(id, opts);
|
|
8386
9940
|
const indexes = parseIndexList(cmdOpts.row);
|
|
8387
9941
|
const rowIds = await resolveRowIds(jobId, indexes, opts);
|
|
@@ -8871,22 +10425,22 @@ import {
|
|
|
8871
10425
|
statSync as statSync4,
|
|
8872
10426
|
createWriteStream,
|
|
8873
10427
|
unlinkSync,
|
|
8874
|
-
readFileSync as
|
|
10428
|
+
readFileSync as readFileSync12,
|
|
8875
10429
|
writeFileSync as writeFileSync5,
|
|
8876
10430
|
readdirSync,
|
|
8877
10431
|
rmSync
|
|
8878
10432
|
} from "fs";
|
|
8879
|
-
import { homedir as
|
|
8880
|
-
import { dirname as
|
|
10433
|
+
import { homedir as homedir4 } from "os";
|
|
10434
|
+
import { dirname as dirname11, join as join15, resolve as resolve11, basename as basename5, normalize, isAbsolute as isAbsolute6 } from "path";
|
|
8881
10435
|
import { Readable, Transform } from "stream";
|
|
8882
10436
|
import { pipeline } from "stream/promises";
|
|
8883
10437
|
|
|
8884
10438
|
// src/commands/init.ts
|
|
8885
|
-
import { existsSync as existsSync8, readFileSync as
|
|
8886
|
-
import { resolve as
|
|
10439
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
10440
|
+
import { resolve as resolve10 } from "path";
|
|
8887
10441
|
var DEFAULT_SPEC_FILE2 = "tunedtensor.json";
|
|
8888
10442
|
function loadSpec(filePath) {
|
|
8889
|
-
const resolved =
|
|
10443
|
+
const resolved = resolve10(filePath || DEFAULT_SPEC_FILE2);
|
|
8890
10444
|
if (!existsSync8(resolved)) {
|
|
8891
10445
|
printError(
|
|
8892
10446
|
`Spec file not found: ${filePath || DEFAULT_SPEC_FILE2}
|
|
@@ -8894,29 +10448,29 @@ Run \`tt init\` to create one.`
|
|
|
8894
10448
|
);
|
|
8895
10449
|
process.exit(1);
|
|
8896
10450
|
}
|
|
8897
|
-
const raw = JSON.parse(
|
|
10451
|
+
const raw = JSON.parse(readFileSync11(resolved, "utf-8"));
|
|
8898
10452
|
return raw;
|
|
8899
10453
|
}
|
|
8900
10454
|
|
|
8901
10455
|
// src/serve-manager.ts
|
|
8902
10456
|
import { spawn as spawn2 } from "child_process";
|
|
8903
|
-
import { randomUUID as
|
|
10457
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
8904
10458
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
8905
10459
|
import { createServer } from "http";
|
|
8906
10460
|
import { createServer as createNetServer } from "net";
|
|
8907
|
-
import { dirname as
|
|
10461
|
+
import { dirname as dirname10 } from "path";
|
|
8908
10462
|
var COMPLETION_ROUTES = /* @__PURE__ */ new Set(["/v1/chat/completions", "/chat/completions"]);
|
|
8909
10463
|
function parsePositiveInteger(value, fallback) {
|
|
8910
10464
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
8911
10465
|
}
|
|
8912
10466
|
function sleep2(ms) {
|
|
8913
|
-
return new Promise((
|
|
10467
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
8914
10468
|
}
|
|
8915
10469
|
function readRequestBody(req) {
|
|
8916
|
-
return new Promise((
|
|
10470
|
+
return new Promise((resolve13, reject) => {
|
|
8917
10471
|
const chunks = [];
|
|
8918
10472
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
8919
|
-
req.on("end", () =>
|
|
10473
|
+
req.on("end", () => resolve13(Buffer.concat(chunks)));
|
|
8920
10474
|
req.on("error", reject);
|
|
8921
10475
|
});
|
|
8922
10476
|
}
|
|
@@ -8984,7 +10538,7 @@ function errorSummary(response, fallback) {
|
|
|
8984
10538
|
return typeof message === "string" ? message : fallback;
|
|
8985
10539
|
}
|
|
8986
10540
|
async function allocatePort(host) {
|
|
8987
|
-
return new Promise((
|
|
10541
|
+
return new Promise((resolve13, reject) => {
|
|
8988
10542
|
const server = createNetServer();
|
|
8989
10543
|
server.on("error", reject);
|
|
8990
10544
|
server.listen(0, host, () => {
|
|
@@ -8994,7 +10548,7 @@ async function allocatePort(host) {
|
|
|
8994
10548
|
return;
|
|
8995
10549
|
}
|
|
8996
10550
|
const port = address.port;
|
|
8997
|
-
server.close(() =>
|
|
10551
|
+
server.close(() => resolve13(port));
|
|
8998
10552
|
});
|
|
8999
10553
|
});
|
|
9000
10554
|
}
|
|
@@ -9034,11 +10588,11 @@ var ManagedModelServer = class {
|
|
|
9034
10588
|
sendJson(res, 500, { error: { message: err instanceof Error ? err.message : String(err) } });
|
|
9035
10589
|
});
|
|
9036
10590
|
});
|
|
9037
|
-
await new Promise((
|
|
10591
|
+
await new Promise((resolve13, reject) => {
|
|
9038
10592
|
this.server?.once("error", reject);
|
|
9039
10593
|
this.server?.listen(this.options.publicPort, this.options.publicHost, () => {
|
|
9040
10594
|
this.server?.off("error", reject);
|
|
9041
|
-
|
|
10595
|
+
resolve13();
|
|
9042
10596
|
});
|
|
9043
10597
|
});
|
|
9044
10598
|
}
|
|
@@ -9048,15 +10602,15 @@ var ManagedModelServer = class {
|
|
|
9048
10602
|
if (!this.server) return;
|
|
9049
10603
|
const server = this.server;
|
|
9050
10604
|
this.server = null;
|
|
9051
|
-
await new Promise((
|
|
9052
|
-
server.close((err) => err ? reject(err) :
|
|
10605
|
+
await new Promise((resolve13, reject) => {
|
|
10606
|
+
server.close((err) => err ? reject(err) : resolve13());
|
|
9053
10607
|
});
|
|
9054
10608
|
}
|
|
9055
10609
|
async run() {
|
|
9056
10610
|
await this.start();
|
|
9057
|
-
await new Promise((
|
|
10611
|
+
await new Promise((resolve13, reject) => {
|
|
9058
10612
|
this.server?.once("error", reject);
|
|
9059
|
-
this.server?.once("close",
|
|
10613
|
+
this.server?.once("close", resolve13);
|
|
9060
10614
|
});
|
|
9061
10615
|
}
|
|
9062
10616
|
async handle(req, res) {
|
|
@@ -9124,7 +10678,7 @@ var ManagedModelServer = class {
|
|
|
9124
10678
|
});
|
|
9125
10679
|
}
|
|
9126
10680
|
async processGeneration(route, body, res, receivedAt) {
|
|
9127
|
-
const requestId = `ttreq-${
|
|
10681
|
+
const requestId = `ttreq-${randomUUID10()}`;
|
|
9128
10682
|
const startedAt = Date.now();
|
|
9129
10683
|
const queuedMs = startedAt - receivedAt;
|
|
9130
10684
|
this.activeRequests += 1;
|
|
@@ -9230,7 +10784,7 @@ var ManagedModelServer = class {
|
|
|
9230
10784
|
try {
|
|
9231
10785
|
child.kill("SIGTERM");
|
|
9232
10786
|
await Promise.race([
|
|
9233
|
-
new Promise((
|
|
10787
|
+
new Promise((resolve13) => child.once("exit", () => resolve13())),
|
|
9234
10788
|
sleep2(5e3).then(() => {
|
|
9235
10789
|
if (child.exitCode == null && child.signalCode == null) child.kill("SIGKILL");
|
|
9236
10790
|
})
|
|
@@ -9308,7 +10862,7 @@ var ManagedModelServer = class {
|
|
|
9308
10862
|
const line = `${JSON.stringify(record4)}
|
|
9309
10863
|
`;
|
|
9310
10864
|
if (this.options.logFile) {
|
|
9311
|
-
mkdirSync3(
|
|
10865
|
+
mkdirSync3(dirname10(this.options.logFile), { recursive: true });
|
|
9312
10866
|
appendFileSync(this.options.logFile, line, "utf8");
|
|
9313
10867
|
return;
|
|
9314
10868
|
}
|
|
@@ -9856,7 +11410,7 @@ if __name__ == "__main__":
|
|
|
9856
11410
|
function resolveOutputPath(output, filename) {
|
|
9857
11411
|
if (!output) return filename;
|
|
9858
11412
|
if (existsSync9(output) && statSync4(output).isDirectory()) {
|
|
9859
|
-
return
|
|
11413
|
+
return join15(output, filename);
|
|
9860
11414
|
}
|
|
9861
11415
|
return output;
|
|
9862
11416
|
}
|
|
@@ -9932,7 +11486,7 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
9932
11486
|
if (!res.body) {
|
|
9933
11487
|
throw new Error("Download failed: response body was empty");
|
|
9934
11488
|
}
|
|
9935
|
-
mkdirSync4(
|
|
11489
|
+
mkdirSync4(dirname11(outputPath), { recursive: true });
|
|
9936
11490
|
const file = createWriteStream(outputPath);
|
|
9937
11491
|
const contentLength = parseContentLength(res.headers.get("content-length"));
|
|
9938
11492
|
const progress = createDownloadProgress(
|
|
@@ -9963,15 +11517,15 @@ async function downloadUrlToFile(url, outputPath) {
|
|
|
9963
11517
|
return contentLength;
|
|
9964
11518
|
}
|
|
9965
11519
|
function defaultCacheDir() {
|
|
9966
|
-
const root = process.env.XDG_CACHE_HOME ||
|
|
9967
|
-
return
|
|
11520
|
+
const root = process.env.XDG_CACHE_HOME || join15(homedir4(), ".cache");
|
|
11521
|
+
return join15(root, "tuned-tensor", "models");
|
|
9968
11522
|
}
|
|
9969
11523
|
function runtimeDir(cacheDir) {
|
|
9970
|
-
return
|
|
11524
|
+
return join15(cacheDir, "_runtime");
|
|
9971
11525
|
}
|
|
9972
11526
|
function runtimePythonPath(cacheDir) {
|
|
9973
11527
|
const dir = runtimeDir(cacheDir);
|
|
9974
|
-
return process.platform === "win32" ?
|
|
11528
|
+
return process.platform === "win32" ? join15(dir, "Scripts", "python.exe") : join15(dir, "bin", "python");
|
|
9975
11529
|
}
|
|
9976
11530
|
function resolveServePython(cacheDir, explicitPython) {
|
|
9977
11531
|
if (explicitPython) return explicitPython;
|
|
@@ -10044,7 +11598,7 @@ print(json.dumps({"missing": missing}))
|
|
|
10044
11598
|
}
|
|
10045
11599
|
}
|
|
10046
11600
|
function setupRuntimeCommands(python, venvDir, deps) {
|
|
10047
|
-
const venvPython = process.platform === "win32" ?
|
|
11601
|
+
const venvPython = process.platform === "win32" ? join15(venvDir, "Scripts", "python.exe") : join15(venvDir, "bin", "python");
|
|
10048
11602
|
return [
|
|
10049
11603
|
[python, "-m", "venv", venvDir],
|
|
10050
11604
|
[venvPython, "-m", "pip", "install", "--upgrade", "pip"],
|
|
@@ -10082,9 +11636,9 @@ function extractArchive(archivePath, targetDir, force) {
|
|
|
10082
11636
|
return targetDir;
|
|
10083
11637
|
}
|
|
10084
11638
|
function localArchiveCacheDir(archivePath, cacheDir) {
|
|
10085
|
-
const resolved =
|
|
11639
|
+
const resolved = resolve11(archivePath);
|
|
10086
11640
|
const digest = createHash6("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
10087
|
-
return
|
|
11641
|
+
return join15(cacheDir, `local-${safeSegment(basename5(archivePath))}-${digest}`);
|
|
10088
11642
|
}
|
|
10089
11643
|
function buildSystemPrompt(spec) {
|
|
10090
11644
|
const parts = [];
|
|
@@ -10100,39 +11654,39 @@ ${spec.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
|
10100
11654
|
return parts.join("\n\n");
|
|
10101
11655
|
}
|
|
10102
11656
|
function findSpecPath(explicitPath, modelPath) {
|
|
10103
|
-
const candidates = explicitPath ? [explicitPath] : [
|
|
11657
|
+
const candidates = explicitPath ? [explicitPath] : [join15(process.cwd(), DEFAULT_SPEC_FILE2), join15(modelPath, DEFAULT_SPEC_FILE2)];
|
|
10104
11658
|
for (const candidate of candidates) {
|
|
10105
|
-
const resolved =
|
|
11659
|
+
const resolved = resolve11(candidate);
|
|
10106
11660
|
if (existsSync9(resolved) && statSync4(resolved).isFile()) return resolved;
|
|
10107
11661
|
}
|
|
10108
11662
|
return null;
|
|
10109
11663
|
}
|
|
10110
11664
|
function loadSystemPromptFromSpec(specPath) {
|
|
10111
|
-
const spec = JSON.parse(
|
|
11665
|
+
const spec = JSON.parse(readFileSync12(specPath, "utf8"));
|
|
10112
11666
|
return buildSystemPrompt(spec);
|
|
10113
11667
|
}
|
|
10114
11668
|
function loadJsonSchemaForServe(schemaPath) {
|
|
10115
|
-
const resolved =
|
|
11669
|
+
const resolved = resolve11(schemaPath);
|
|
10116
11670
|
if (!existsSync9(resolved) || !statSync4(resolved).isFile()) {
|
|
10117
11671
|
throw new Error(`JSON schema file not found: ${schemaPath}`);
|
|
10118
11672
|
}
|
|
10119
11673
|
try {
|
|
10120
|
-
const schema = JSON.parse(
|
|
11674
|
+
const schema = JSON.parse(readFileSync12(resolved, "utf8"));
|
|
10121
11675
|
return JSON.stringify(schema);
|
|
10122
11676
|
} catch (err) {
|
|
10123
11677
|
throw new Error(`JSON schema file is not valid JSON: ${schemaPath}`);
|
|
10124
11678
|
}
|
|
10125
11679
|
}
|
|
10126
11680
|
function writeReferenceServerScript(cacheDir) {
|
|
10127
|
-
const scriptDir =
|
|
11681
|
+
const scriptDir = join15(cacheDir, "_server");
|
|
10128
11682
|
mkdirSync4(scriptDir, { recursive: true });
|
|
10129
|
-
const scriptPath =
|
|
11683
|
+
const scriptPath = join15(scriptDir, "openai_reference_server.py");
|
|
10130
11684
|
writeFileSync5(scriptPath, REFERENCE_SERVER_SCRIPT, "utf8");
|
|
10131
11685
|
return scriptPath;
|
|
10132
11686
|
}
|
|
10133
11687
|
async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
10134
11688
|
if (existsSync9(target)) {
|
|
10135
|
-
const resolved =
|
|
11689
|
+
const resolved = resolve11(target);
|
|
10136
11690
|
const stats = statSync4(resolved);
|
|
10137
11691
|
if (stats.isDirectory()) {
|
|
10138
11692
|
return {
|
|
@@ -10142,7 +11696,7 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
10142
11696
|
};
|
|
10143
11697
|
}
|
|
10144
11698
|
if (stats.isFile() && (resolved.endsWith(".tar.gz") || resolved.endsWith(".tgz"))) {
|
|
10145
|
-
const extractDir2 =
|
|
11699
|
+
const extractDir2 = join15(localArchiveCacheDir(resolved, cacheDir), "model");
|
|
10146
11700
|
return {
|
|
10147
11701
|
modelPath: extractArchive(resolved, extractDir2, forceDownload),
|
|
10148
11702
|
modelName: basename5(resolved).replace(/\.t(ar\.)?gz$/, ""),
|
|
@@ -10154,9 +11708,9 @@ async function resolveServeTarget(target, opts, cacheDir, forceDownload) {
|
|
|
10154
11708
|
const fullId = await resolveModelId(target, opts);
|
|
10155
11709
|
const { data: model } = await get(`/models/${fullId}`, void 0, opts);
|
|
10156
11710
|
const { data: data3 } = await get(`/models/${fullId}/download`, void 0, opts);
|
|
10157
|
-
const modelCacheDir =
|
|
10158
|
-
const archivePath =
|
|
10159
|
-
const extractDir =
|
|
11711
|
+
const modelCacheDir = join15(cacheDir, fullId);
|
|
11712
|
+
const archivePath = join15(modelCacheDir, data3.filename);
|
|
11713
|
+
const extractDir = join15(modelCacheDir, "model");
|
|
10160
11714
|
if (!existsSync9(archivePath) || forceDownload) {
|
|
10161
11715
|
await downloadUrlToFile(data3.url, archivePath);
|
|
10162
11716
|
}
|
|
@@ -10262,7 +11816,7 @@ function ollamaSlug(name) {
|
|
|
10262
11816
|
function firstExisting(candidates) {
|
|
10263
11817
|
for (const candidate of candidates) {
|
|
10264
11818
|
if (candidate && existsSync9(candidate) && statSync4(candidate).isFile()) {
|
|
10265
|
-
return
|
|
11819
|
+
return resolve11(candidate);
|
|
10266
11820
|
}
|
|
10267
11821
|
}
|
|
10268
11822
|
return null;
|
|
@@ -10272,14 +11826,14 @@ function llamaCppDir(explicit) {
|
|
|
10272
11826
|
}
|
|
10273
11827
|
function resolveConvertScript(opts, required) {
|
|
10274
11828
|
if (opts.convertScript) {
|
|
10275
|
-
const resolved =
|
|
11829
|
+
const resolved = resolve11(opts.convertScript);
|
|
10276
11830
|
if (required && !existsSync9(resolved)) {
|
|
10277
11831
|
throw new Error(`Conversion script not found: ${opts.convertScript}`);
|
|
10278
11832
|
}
|
|
10279
11833
|
return resolved;
|
|
10280
11834
|
}
|
|
10281
11835
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
10282
|
-
const candidates = dir ? [
|
|
11836
|
+
const candidates = dir ? [join15(dir, "convert_hf_to_gguf.py"), join15(dir, "convert-hf-to-gguf.py")] : [];
|
|
10283
11837
|
const found = firstExisting(candidates);
|
|
10284
11838
|
if (found) return found;
|
|
10285
11839
|
if (required) {
|
|
@@ -10287,11 +11841,11 @@ function resolveConvertScript(opts, required) {
|
|
|
10287
11841
|
"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."
|
|
10288
11842
|
);
|
|
10289
11843
|
}
|
|
10290
|
-
return dir ?
|
|
11844
|
+
return dir ? join15(dir, "convert_hf_to_gguf.py") : "convert_hf_to_gguf.py";
|
|
10291
11845
|
}
|
|
10292
11846
|
function resolveQuantizeBin(opts, required) {
|
|
10293
11847
|
if (opts.quantizeBin) {
|
|
10294
|
-
const resolved =
|
|
11848
|
+
const resolved = resolve11(opts.quantizeBin);
|
|
10295
11849
|
if (required && !existsSync9(resolved)) {
|
|
10296
11850
|
throw new Error(`llama-quantize binary not found: ${opts.quantizeBin}`);
|
|
10297
11851
|
}
|
|
@@ -10300,10 +11854,10 @@ function resolveQuantizeBin(opts, required) {
|
|
|
10300
11854
|
const dir = llamaCppDir(opts.llamaCpp);
|
|
10301
11855
|
if (dir) {
|
|
10302
11856
|
const candidates = [
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
11857
|
+
join15(dir, "build", "bin", "llama-quantize"),
|
|
11858
|
+
join15(dir, "build", "bin", "quantize"),
|
|
11859
|
+
join15(dir, "llama-quantize"),
|
|
11860
|
+
join15(dir, "quantize")
|
|
10307
11861
|
];
|
|
10308
11862
|
const found = firstExisting(candidates);
|
|
10309
11863
|
if (found) return found;
|
|
@@ -10451,7 +12005,7 @@ function registerModelsCommands(parent) {
|
|
|
10451
12005
|
}
|
|
10452
12006
|
const quantPlan = planQuant(String(cmdOpts.quant));
|
|
10453
12007
|
const printOnly = Boolean(cmdOpts.printCommand);
|
|
10454
|
-
const cacheDir =
|
|
12008
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10455
12009
|
mkdirSync4(cacheDir, { recursive: true });
|
|
10456
12010
|
const serveTarget = await resolveServeTarget(
|
|
10457
12011
|
target,
|
|
@@ -10460,9 +12014,9 @@ function registerModelsCommands(parent) {
|
|
|
10460
12014
|
Boolean(cmdOpts.forceDownload)
|
|
10461
12015
|
);
|
|
10462
12016
|
const slug = ollamaSlug(serveTarget.modelName);
|
|
10463
|
-
const outputDir =
|
|
10464
|
-
const finalPath =
|
|
10465
|
-
const intermediatePath = quantPlan.requiresQuantize ?
|
|
12017
|
+
const outputDir = resolve11(cmdOpts.output || join15(process.cwd(), `${slug}-gguf`));
|
|
12018
|
+
const finalPath = join15(outputDir, `${slug}.${quantPlan.quant}.gguf`);
|
|
12019
|
+
const intermediatePath = quantPlan.requiresQuantize ? join15(outputDir, `${slug}.f16.gguf`) : void 0;
|
|
10466
12020
|
const convertScript = resolveConvertScript(cmdOpts, !printOnly);
|
|
10467
12021
|
const convertOutfile = intermediatePath ?? finalPath;
|
|
10468
12022
|
const steps = [
|
|
@@ -10500,7 +12054,7 @@ function registerModelsCommands(parent) {
|
|
|
10500
12054
|
}
|
|
10501
12055
|
if (specPath) systemPrompt2 = loadSystemPromptFromSpec(specPath);
|
|
10502
12056
|
}
|
|
10503
|
-
modelfilePath =
|
|
12057
|
+
modelfilePath = join15(outputDir, "Modelfile");
|
|
10504
12058
|
modelfileContent = buildModelfile(basename5(finalPath), systemPrompt2);
|
|
10505
12059
|
const createModel = cmdOpts.ollamaCreate !== false;
|
|
10506
12060
|
if (createModel) {
|
|
@@ -10587,7 +12141,7 @@ function registerModelsCommands(parent) {
|
|
|
10587
12141
|
}
|
|
10588
12142
|
});
|
|
10589
12143
|
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) => {
|
|
10590
|
-
const cacheDir =
|
|
12144
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10591
12145
|
const venvDir = runtimeDir(cacheDir);
|
|
10592
12146
|
const venvPython = runtimePythonPath(cacheDir);
|
|
10593
12147
|
const python = pickRuntimePython(cmdOpts.python);
|
|
@@ -10633,7 +12187,7 @@ function registerModelsCommands(parent) {
|
|
|
10633
12187
|
});
|
|
10634
12188
|
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) => {
|
|
10635
12189
|
const opts = parent.optsWithGlobals();
|
|
10636
|
-
const cacheDir =
|
|
12190
|
+
const cacheDir = resolve11(cmdOpts.cacheDir || defaultCacheDir());
|
|
10637
12191
|
mkdirSync4(cacheDir, { recursive: true });
|
|
10638
12192
|
const python = resolveServePython(cacheDir, cmdOpts.python);
|
|
10639
12193
|
if (!cmdOpts.printCommand) ensureServingRuntime(python, cacheDir);
|
|
@@ -10682,7 +12236,7 @@ function registerModelsCommands(parent) {
|
|
|
10682
12236
|
idle_timeout_seconds: idleTimeoutSeconds,
|
|
10683
12237
|
restart_after_requests: restartAfterRequests,
|
|
10684
12238
|
gate_field: String(cmdOpts.gateField),
|
|
10685
|
-
log_file: cmdOpts.logFile ?
|
|
12239
|
+
log_file: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
|
|
10686
12240
|
} : void 0;
|
|
10687
12241
|
if (cmdOpts.printCommand) return printServeCommand(python, args2, env, managedConfig);
|
|
10688
12242
|
if (!isJsonMode()) {
|
|
@@ -10719,7 +12273,7 @@ function registerModelsCommands(parent) {
|
|
|
10719
12273
|
idleTimeoutSeconds,
|
|
10720
12274
|
restartAfterRequests,
|
|
10721
12275
|
gateField: String(cmdOpts.gateField),
|
|
10722
|
-
logFile: cmdOpts.logFile ?
|
|
12276
|
+
logFile: cmdOpts.logFile ? resolve11(cmdOpts.logFile) : void 0
|
|
10723
12277
|
});
|
|
10724
12278
|
return;
|
|
10725
12279
|
}
|
|
@@ -10745,17 +12299,17 @@ function registerModelsCommands(parent) {
|
|
|
10745
12299
|
}
|
|
10746
12300
|
|
|
10747
12301
|
// src/commands/push.ts
|
|
10748
|
-
import { readFileSync as
|
|
10749
|
-
import { resolve as
|
|
12302
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
12303
|
+
import { resolve as resolve12 } from "path";
|
|
10750
12304
|
function persistRemoteId(filePath, id) {
|
|
10751
|
-
const raw = JSON.parse(
|
|
12305
|
+
const raw = JSON.parse(readFileSync13(filePath, "utf-8"));
|
|
10752
12306
|
raw.id = id;
|
|
10753
12307
|
writeFileSync6(filePath, JSON.stringify(raw, null, 2) + "\n");
|
|
10754
12308
|
}
|
|
10755
12309
|
function registerPushCommand(parent) {
|
|
10756
12310
|
parent.command("push").description("Push local spec to the Tuned Tensor API").option("-f, --file <path>", "Spec file path", DEFAULT_SPEC_FILE2).action(async (cmdOpts) => {
|
|
10757
12311
|
const opts = parent.optsWithGlobals();
|
|
10758
|
-
const filePath =
|
|
12312
|
+
const filePath = resolve12(cmdOpts.file);
|
|
10759
12313
|
const spec = loadSpec(cmdOpts.file);
|
|
10760
12314
|
const evalCaseErrors = validateEvalCases(spec);
|
|
10761
12315
|
if (evalCaseErrors.length > 0) {
|
|
@@ -10901,10 +12455,10 @@ async function checkForCliUpdate(currentVersion, options = {}) {
|
|
|
10901
12455
|
return null;
|
|
10902
12456
|
}
|
|
10903
12457
|
})();
|
|
10904
|
-
const deadline = new Promise((
|
|
12458
|
+
const deadline = new Promise((resolve13) => {
|
|
10905
12459
|
timeout = setTimeout(() => {
|
|
10906
12460
|
controller.abort();
|
|
10907
|
-
|
|
12461
|
+
resolve13(null);
|
|
10908
12462
|
}, timeoutMs);
|
|
10909
12463
|
});
|
|
10910
12464
|
try {
|
|
@@ -11029,7 +12583,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
11029
12583
|
if (!entrypoint) {
|
|
11030
12584
|
throw new Error("Cannot locate the tt CLI entrypoint.");
|
|
11031
12585
|
}
|
|
11032
|
-
return await new Promise((
|
|
12586
|
+
return await new Promise((resolve13, reject) => {
|
|
11033
12587
|
const child = spawn4(process.execPath, [entrypoint, ...args2], {
|
|
11034
12588
|
cwd: options.cwd,
|
|
11035
12589
|
env: options.env ?? process.env,
|
|
@@ -11069,7 +12623,7 @@ async function runSelfCommand(args2, options = {}) {
|
|
|
11069
12623
|
child.once("close", (exitCode, signal) => {
|
|
11070
12624
|
cleanup();
|
|
11071
12625
|
const signalNumber = signal ? osConstants2.signals[signal] : void 0;
|
|
11072
|
-
|
|
12626
|
+
resolve13({
|
|
11073
12627
|
exitCode: exitCode ?? (typeof signalNumber === "number" ? 128 + signalNumber : 1),
|
|
11074
12628
|
signal
|
|
11075
12629
|
});
|
|
@@ -11132,7 +12686,7 @@ function createProgram(version, runtime = {}) {
|
|
|
11132
12686
|
if (root.json) setJsonMode(true);
|
|
11133
12687
|
if (root.color === false) {
|
|
11134
12688
|
process.env.FORCE_COLOR = "0";
|
|
11135
|
-
|
|
12689
|
+
chalk13.level = 0;
|
|
11136
12690
|
}
|
|
11137
12691
|
});
|
|
11138
12692
|
registerLocalCommands(program, {
|
|
@@ -11170,12 +12724,12 @@ function createProgram(version, runtime = {}) {
|
|
|
11170
12724
|
`);
|
|
11171
12725
|
return;
|
|
11172
12726
|
}
|
|
11173
|
-
output.write(`${
|
|
12727
|
+
output.write(`${chalk13.bold.hex("#8B5CF6")("Tuned Tensor status")}
|
|
11174
12728
|
`);
|
|
11175
12729
|
output.write(`${formatShellStatus(context).join("\n")}
|
|
11176
12730
|
|
|
11177
12731
|
`);
|
|
11178
|
-
output.write(`${
|
|
12732
|
+
output.write(`${chalk13.bold("Context")}
|
|
11179
12733
|
`);
|
|
11180
12734
|
output.write(`${formatShellContext(context).join("\n")}
|
|
11181
12735
|
`);
|
|
@@ -11276,7 +12830,7 @@ async function runCli(version, runtime = {}) {
|
|
|
11276
12830
|
}
|
|
11277
12831
|
|
|
11278
12832
|
// src/index.ts
|
|
11279
|
-
runCli("0.
|
|
12833
|
+
runCli("0.22.0").catch((err) => {
|
|
11280
12834
|
reportError(err);
|
|
11281
12835
|
process.exit(1);
|
|
11282
12836
|
});
|