@tuned-tensor/cli 0.24.0 → 0.26.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.
@@ -2,113 +2,130 @@
2
2
  import {
3
3
  DEFAULT_LOCAL_SPEC_PATH,
4
4
  DatasetNotDownloadedError,
5
- assertArtifactManifest,
6
5
  assertFoundationSpecReady,
7
6
  assertLocalRunInputReady,
8
- assertUsableModelArtifact,
9
7
  assessHardware,
10
8
  auditRun,
9
+ buildSystemMessage,
10
+ canonicalLocalPipeline,
11
+ checkAwsGpu,
12
+ compileSpecToJsonl,
13
+ createLocalStore,
14
+ createRunConsoleReporter,
15
+ datasetInputIdentity,
16
+ decisionPlaceholderIssues,
17
+ defaultLocalHome,
18
+ ensureDatasetCached,
19
+ evaluateGeneralRegressionGate,
20
+ evaluationSuiteFromChatJsonl,
21
+ exampleToChatRow,
22
+ examplesFromChatJsonl,
23
+ examplesFromSpec,
24
+ executeDatasetSplit,
25
+ fingerprintLocalBaseModel,
26
+ foundationPlaceholderIssues,
27
+ generatedPlaceholderIssues,
28
+ hardenExistingLocalStore,
29
+ huggingFaceDataset,
30
+ initLocalRunnerConfigFile,
31
+ initLocalSpecFile,
32
+ inspectDatasetFile,
33
+ isTerminalRunState,
34
+ loadJsonFile,
35
+ loadLocalRunInput,
36
+ loadLocalRunnerConfig,
37
+ loadRunRequest,
38
+ localPathsOverlap,
39
+ localStorePaths,
40
+ normalizeChatJsonlForRelocation,
41
+ parseLocalRunInput,
42
+ parseLocalRunnerConfig,
43
+ pipelineForRunInput,
44
+ prefetchHuggingFaceDataset,
45
+ prepareDatasetSplit,
46
+ resolveDownloadConfig,
47
+ resolveLocalRunInputPaths,
48
+ resolveProjectConfig,
49
+ runGpuProcess,
50
+ runLocalFineTune,
51
+ runLocalPipeline,
52
+ runRequestFromLocalSpec,
53
+ specHash,
54
+ tokenF1,
55
+ validateBehaviorSpec,
56
+ validateLocalFineTuneInput,
57
+ validateLocalPipeline,
58
+ writePrivateJsonAtomic
59
+ } from "./chunk-FLRRDP3M.js";
60
+ import {
61
+ applySpecLock,
62
+ assertArtifactManifest,
63
+ assertUsableModelArtifact,
11
64
  baseModelRevisionSchema,
12
65
  behaviorSpecExampleSchema,
13
66
  behaviorSpecSchema,
14
67
  buildBundledPythonCommand,
15
68
  buildFoundationPythonCommand,
16
69
  buildModelPrefetchPayload,
17
- buildSystemMessage,
18
- canonicalLocalPipeline,
19
- checkAwsGpu,
70
+ buildModelSourcePrefetchPayload,
20
71
  comparisonReportSchema,
21
- compileSpecToJsonl,
22
- createLocalStore,
23
- createRunConsoleReporter,
24
72
  datasetColumnsSchema,
25
73
  datasetFormatSchema,
26
- datasetInputIdentity,
27
74
  datasetPrebuiltSchema,
28
75
  datasetSourceSchema,
29
76
  decisionHyperparametersSchema,
30
77
  decisionLabels,
31
- decisionPlaceholderIssues,
32
78
  decisionQuestionSchema,
33
79
  defaultBaseModelRevision,
34
- defaultLocalHome,
35
- ensureDatasetCached,
80
+ enforceSpecLock,
36
81
  evalExampleResultSchema,
37
82
  evalReportSchema,
38
83
  evalSplitSchema,
39
- evaluateGeneralRegressionGate,
40
- evaluationSuiteFromChatJsonl,
41
- exampleToChatRow,
42
- examplesFromChatJsonl,
43
- examplesFromSpec,
84
+ findAsset,
44
85
  fineTuneHyperparametersSchema,
45
86
  fineTuneRunRequestSchema,
46
- fingerprintLocalBaseModel,
47
87
  forwardStreamLines,
48
88
  foundationHyperparametersSchema,
49
- foundationPlaceholderIssues,
50
- generatedPlaceholderIssues,
51
- hardenExistingLocalStore,
52
- huggingFaceDataset,
53
89
  huggingFaceDatasetSourceSchema,
54
90
  huggingFaceRepoIdSchema,
55
- initLocalRunnerConfigFile,
56
- initLocalSpecFile,
57
91
  isDecisionSpecFile,
58
92
  isFoundationSpecFile,
59
93
  isRepoRelativePath,
60
- isTerminalRunState,
61
94
  jsonFieldMetricsSchema,
62
- loadJsonFile,
63
- loadLocalRunInput,
64
- loadLocalRunnerConfig,
65
- loadRunRequest,
95
+ listAssets,
66
96
  localAdapterSpecFileSchema,
67
97
  localBehaviorSpecFileSchema,
68
98
  localDecisionSpecFileSchema,
69
99
  localFoundationSpecFileSchema,
70
- localPathsOverlap,
71
100
  localRunnerConfigSchema,
72
101
  localRuntimePackageRoot,
73
- localStorePaths,
74
102
  minimalMachineLearningEnvironment,
75
- normalizeChatJsonlForRelocation,
76
- parseLocalRunInput,
77
- parseLocalRunnerConfig,
78
- pipelineForRunInput,
79
103
  prefetchBaseModel,
80
- prefetchHuggingFaceDataset,
81
104
  projectRuntimeSchema,
82
105
  pythonEnvironmentPath,
106
+ readSpecLock,
83
107
  reportInBackground,
84
- resolveLocalRunInputPaths,
85
108
  resolveModelLoader,
86
- resolveProjectConfig,
87
109
  resolveRequestedBaseModelRevision,
88
110
  resolveTrainingModel,
89
- runGpuProcess,
90
- runLocalFineTune,
91
- runLocalPipeline,
92
111
  runProvenanceSchema,
93
112
  runReportSchema,
94
- runRequestFromLocalSpec,
95
113
  safeName,
96
114
  sanitizeLogLine,
97
- specHash,
115
+ snapshotDigests,
116
+ specLockPath,
98
117
  specSnapshotSchema,
99
- tokenF1,
100
118
  trainingReportSchema,
101
- validateBehaviorSpec,
102
- validateLocalFineTuneInput,
103
- validateLocalPipeline,
119
+ verifyAsset,
104
120
  verifyLocalBaseModel,
105
121
  verifyModelPrefetchCacheReport,
106
122
  withBundledPythonEnvironment,
107
123
  withFoundationPythonEnvironment,
108
124
  withHuggingFaceCacheEnvironment,
109
125
  withOfflineHuggingFaceCacheEnvironment,
110
- writePrivateJsonAtomic
111
- } from "./chunk-RPWOCVIL.js";
126
+ withProgressHeartbeat,
127
+ writeSpecLockEntry
128
+ } from "./chunk-4BHDPRYD.js";
112
129
 
113
130
  // src/local-runtime/index.ts
114
131
  import { readFileSync as readFileSync3, realpathSync } from "fs";
@@ -948,7 +965,9 @@ Commands:
948
965
  serve <model-id|active|base|foundation> [--config local-runner.json] [--host 127.0.0.1] [--port 8000]
949
966
  runs list|get|events|report|compare|audit [args] [--config local-runner.json]
950
967
  models list|get|verify|prefetch|verify-base|active|activate|rollback|serve [args] [--config local-runner.json]
951
- datasets prefetch|verify [tunedtensor.json]
968
+ datasets inspect|split <data.jsonl> [--output directory --train .8 --validation .1 --test .1]
969
+ datasets prefetch|verify [tunedtensor.json] [--repo org/name --training path ...]
970
+ assets list|path|verify [org/name[@revision]]
952
971
 
953
972
  Global options:
954
973
  -h, --help Show help
@@ -961,6 +980,9 @@ TT_LOCAL_HOME overrides the default store. --config is a legacy migration option
961
980
  var CONFIG_OPTION = { name: "--config", value: "path", description: "Legacy runner config (prefer runtime/evaluation in tunedtensor.json)" };
962
981
  var VERBOSE_OPTION = { name: "--verbose", description: "Stream subprocess output" };
963
982
  var QUIET_OPTION = { name: "--quiet", description: "Suppress progress output on stderr" };
983
+ var REPO_OPTION = { name: "--repo", value: "org/name", description: "Download this Hugging Face repo instead of the spec's (records a manifest; leaves the lock alone)" };
984
+ var REVISION_OPTION = { name: "--revision", value: "sha", description: "40-character commit to download with --repo (default: latest)" };
985
+ var SPLIT_OPTIONS = ["training", "validation", "test"].map((split) => ({ name: `--${split}`, value: "path", description: `Repo-relative ${split} file (.jsonl or .parquet) to download with --repo` }));
964
986
  var MODEL_SERVE_OPTIONS = [
965
987
  CONFIG_OPTION,
966
988
  { name: "--checkpoint", value: "path", description: "Foundation model directory (target: foundation)" },
@@ -1071,9 +1093,9 @@ var COMMAND_GROUPS = {
1071
1093
  missingPositionalsMessage: "models verify requires <model-id-or-artifact-path>"
1072
1094
  },
1073
1095
  prefetch: {
1074
- usage: "tt models prefetch [tunedtensor.json] [options]",
1075
- description: "Download the configured base model before a run.",
1076
- options: [CONFIG_OPTION, VERBOSE_OPTION, QUIET_OPTION],
1096
+ usage: "tt models prefetch [tunedtensor.json] [--repo org/name [--revision sha]] [options]",
1097
+ description: "Download the spec's base model and pin it in tunedtensor.lock.json, or download any model with --repo.",
1098
+ options: [CONFIG_OPTION, REPO_OPTION, REVISION_OPTION, VERBOSE_OPTION, QUIET_OPTION],
1077
1099
  maxPositionals: 1
1078
1100
  },
1079
1101
  "verify-base": {
@@ -1113,13 +1135,37 @@ var COMMAND_GROUPS = {
1113
1135
  }
1114
1136
  },
1115
1137
  datasets: {
1116
- description: "Download or verify the Hugging Face dataset a behavior spec trains on.",
1138
+ description: "Inspect and split local chat JSONL, or download and verify Hugging Face datasets.",
1117
1139
  defaultSubcommand: "prefetch",
1118
1140
  subcommands: {
1141
+ inspect: {
1142
+ usage: "tt datasets inspect <data.jsonl>",
1143
+ description: "Validate local chat JSONL and report row count, hash, and bounded samples without changes.",
1144
+ options: [],
1145
+ minPositionals: 1,
1146
+ maxPositionals: 1
1147
+ },
1148
+ split: {
1149
+ usage: "tt datasets split <data.jsonl> --output <new-directory> [options]",
1150
+ description: "Partition unsplit chat JSONL without changing the source or spec. Accepts a workspace path or pinned hf://datasets/org/repo@commit/file reference from a recorded download. Keep duplicate prompts together; preserve metadata.",
1151
+ options: [
1152
+ { name: "--output", value: "directory", description: "New output directory inside the workspace (parent must exist)" },
1153
+ { name: "--train", value: "ratio", description: "Training fraction (default 0.8)" },
1154
+ { name: "--validation", value: "ratio", description: "Validation fraction (default 0.1)" },
1155
+ { name: "--test", value: "ratio", description: "Test fraction (default 0.1); set 0 for train/validation only" },
1156
+ { name: "--seed", value: "seed", description: "Reproducible random group assignment (default 42)" },
1157
+ { name: "--method", value: "random|ordered", description: "Seeded group ordering or source order (default random)" },
1158
+ { name: "--group-by", value: "field", description: "Group field, with dotted paths supported; defaults to normalized user prompt" },
1159
+ { name: "--stratify-by", value: "field", description: "Keep similar ratios within each label/category" },
1160
+ { name: "--dry-run", description: "Show planned counts without writing files" }
1161
+ ],
1162
+ minPositionals: 1,
1163
+ maxPositionals: 1
1164
+ },
1119
1165
  prefetch: {
1120
- usage: "tt datasets prefetch [tunedtensor.json] [options]",
1121
- description: "Download dataset_prebuilt.huggingface files into the local Hugging Face cache. Resumes after interruption.",
1122
- options: [CONFIG_OPTION, VERBOSE_OPTION, QUIET_OPTION],
1166
+ usage: "tt datasets prefetch [tunedtensor.json] [--repo org/name --training path [--test path]] [options]",
1167
+ description: "Download dataset_prebuilt.huggingface files and pin them in tunedtensor.lock.json, or download any dataset files with --repo. Resumes after interruption.",
1168
+ options: [CONFIG_OPTION, REPO_OPTION, REVISION_OPTION, ...SPLIT_OPTIONS, VERBOSE_OPTION, QUIET_OPTION],
1123
1169
  maxPositionals: 1
1124
1170
  },
1125
1171
  verify: {
@@ -1129,6 +1175,27 @@ var COMMAND_GROUPS = {
1129
1175
  maxPositionals: 1
1130
1176
  }
1131
1177
  }
1178
+ },
1179
+ assets: {
1180
+ description: "Find and verify downloaded Hugging Face datasets and models (manifests under ~/.tuned-tensor/assets).",
1181
+ defaultSubcommand: "list",
1182
+ subcommands: {
1183
+ list: { usage: "tt assets list", description: "List downloads, newest first.", options: [], maxPositionals: 0 },
1184
+ path: {
1185
+ usage: "tt assets path <org/name[@revision]>",
1186
+ description: "Print where a download lives on disk.",
1187
+ options: [],
1188
+ minPositionals: 1,
1189
+ maxPositionals: 1,
1190
+ missingPositionalsMessage: "assets path requires <org/name[@revision]>"
1191
+ },
1192
+ verify: {
1193
+ usage: "tt assets verify [org/name[@revision]]",
1194
+ description: "Re-hash downloaded files and compare them with their manifests (all downloads by default).",
1195
+ options: [],
1196
+ maxPositionals: 1
1197
+ }
1198
+ }
1132
1199
  }
1133
1200
  };
1134
1201
  function hasHelpFlag(argv) {
@@ -1254,6 +1321,26 @@ function parseCli(argv) {
1254
1321
  definition: subcommandDefinition
1255
1322
  };
1256
1323
  }
1324
+ function datasetSourceFromArgv(argv) {
1325
+ const repo = readOption(argv, "--repo");
1326
+ if (!repo) return void 0;
1327
+ const revision = readOption(argv, "--revision");
1328
+ const splits = Object.fromEntries(["training", "validation", "test"].flatMap((split) => {
1329
+ const path = readOption(argv, `--${split}`);
1330
+ return path ? [[split, path]] : [];
1331
+ }));
1332
+ if (!splits.training) {
1333
+ throw new Error("--repo needs --training <path> (a repo-relative .jsonl or .parquet file); --validation and --test are optional.");
1334
+ }
1335
+ const invalid = Object.values(splits).find((path) => !isRepoRelativePath(path));
1336
+ if (invalid) throw new Error(`Dataset files must be paths inside the repo, such as data/train.jsonl: ${invalid}`);
1337
+ return {
1338
+ format: "chat_jsonl",
1339
+ huggingface: huggingFaceDatasetSourceSchema.parse({ repo, ...revision ? { revision } : {} }),
1340
+ ...splits,
1341
+ training: splits.training
1342
+ };
1343
+ }
1257
1344
  function readNumberOption(argv, name) {
1258
1345
  const value = readOption(argv, name);
1259
1346
  return value ? Number(value) : void 0;
@@ -1866,7 +1953,8 @@ ${validation.errors.join("\n")}`);
1866
1953
  verbose: hasFlag(argv, "--verbose"),
1867
1954
  quiet: hasFlag(argv, "--quiet")
1868
1955
  });
1869
- const ensured = await ensureDatasetCached({ request: input.request, config, reporter });
1956
+ const lock = await readSpecLock(input.path);
1957
+ const ensured = await ensureDatasetCached({ request: applySpecLock(input.request, lock), config, reporter });
1870
1958
  const validated = await validateLocalFineTuneInput({
1871
1959
  request: ensured.request,
1872
1960
  config
@@ -1889,6 +1977,7 @@ ${validation.errors.join("\n")}`);
1889
1977
  }
1890
1978
  });
1891
1979
  }
1980
+ if (!config.dryRun) await enforceSpecLock({ specPath: input.path, request, config, reporter });
1892
1981
  const specFile = /^\..+\.tt-local-/.test(basename(input.path)) ? void 0 : { path: input.path, sha256: specHash(await readFile2(input.path, "utf8")) };
1893
1982
  if (input.spec.pipeline) {
1894
1983
  const result2 = await runLocalPipeline({ request, config, reporter, pipeline: pipelineForRunInput(input), projectSpec: input.spec, specFile });
@@ -1965,25 +2054,74 @@ ${validation.errors.join("\n")}`);
1965
2054
  }
1966
2055
  if (command === "datasets") {
1967
2056
  const subcommand = cli.subcommand;
2057
+ if (subcommand === "inspect") return printJson(await inspectDatasetFile(cwd(), cli.positionals[0]));
2058
+ if (subcommand === "split") {
2059
+ const numeric = (name, fallback) => readOption(argv, name) === void 0 ? fallback : Number(readOption(argv, name));
2060
+ const prepared = await prepareDatasetSplit(cwd(), {
2061
+ file: cli.positionals[0],
2062
+ output: readOption(argv, "--output"),
2063
+ train: numeric("--train", 0.8),
2064
+ validation: numeric("--validation", 0.1),
2065
+ test: numeric("--test", 0.1),
2066
+ seed: readOption(argv, "--seed") ?? "42",
2067
+ method: readOption(argv, "--method") ?? "random",
2068
+ group_by: readOption(argv, "--group-by"),
2069
+ stratify_by: readOption(argv, "--stratify-by")
2070
+ });
2071
+ return printJson(hasFlag(argv, "--dry-run") ? prepared.preview : await executeDatasetSplit(cwd(), prepared));
2072
+ }
1968
2073
  const inputPath = resolve3(cli.positionals[0] ?? DEFAULT_LOCAL_SPEC_PATH);
1969
- const config = await configFromArgv(argv, inputPath);
1970
- const input = await loadCliBehaviorSpec(inputPath);
2074
+ const config = readOption(argv, "--repo") ? await resolveDownloadConfig(inputPath, readOption(argv, "--config"), (message) => console.error(message)) : await configFromArgv(argv, inputPath);
1971
2075
  if (subcommand !== "prefetch" && subcommand !== "verify") throw new Error(`Unknown datasets command: ${subcommand}`);
2076
+ const reporter = createConsoleReporter({
2077
+ verbose: hasFlag(argv, "--verbose"),
2078
+ quiet: hasFlag(argv, "--quiet")
2079
+ });
2080
+ const source = datasetSourceFromArgv(argv);
2081
+ if (source) {
2082
+ if (subcommand !== "prefetch") throw new Error("--repo applies to `tt datasets prefetch`; use `tt assets verify` for downloads.");
2083
+ return printJson(await prefetchHuggingFaceDataset({ request: { dataset_prebuilt: source }, config, reporter }));
2084
+ }
2085
+ const input = await loadCliBehaviorSpec(inputPath);
1972
2086
  const report = await prefetchHuggingFaceDataset({
1973
- request: input.request,
2087
+ request: applySpecLock(input.request, await readSpecLock(input.path)),
1974
2088
  config,
1975
2089
  localOnly: subcommand === "verify",
1976
- reporter: createConsoleReporter({
1977
- verbose: hasFlag(argv, "--verbose"),
1978
- quiet: hasFlag(argv, "--quiet")
1979
- })
2090
+ reporter
1980
2091
  });
1981
- return printJson({ ...report, input_path: input.path });
2092
+ if (subcommand === "prefetch") {
2093
+ await writeSpecLockEntry(input.path, "dataset", {
2094
+ repo: report.repo,
2095
+ revision: report.revision,
2096
+ files: (await snapshotDigests(report.snapshot_path, Object.values(report.files).flatMap((file) => file ? [file.path] : []))).files
2097
+ });
2098
+ }
2099
+ return printJson({ ...report, input_path: input.path, ...subcommand === "prefetch" ? { lock_path: specLockPath(input.path) } : {} });
2100
+ }
2101
+ if (command === "assets") {
2102
+ const subcommand = cli.subcommand;
2103
+ if (subcommand === "list") return printJson(await listAssets());
2104
+ if (subcommand === "path") {
2105
+ const asset = await findAsset(cli.positionals[0]);
2106
+ console.log(asset.snapshot_path);
2107
+ return;
2108
+ }
2109
+ if (subcommand === "verify") {
2110
+ const assets = cli.positionals[0] ? [await findAsset(cli.positionals[0])] : await listAssets();
2111
+ const results = [];
2112
+ for (const asset of assets) {
2113
+ results.push({ kind: asset.kind, repo: asset.repo, revision: asset.revision, ...await verifyAsset(asset) });
2114
+ }
2115
+ printJson({ ok: results.every((result) => result.ok), assets: results });
2116
+ if (!results.every((result) => result.ok)) process.exitCode = 2;
2117
+ return;
2118
+ }
2119
+ throw new Error(`Unknown assets command: ${subcommand}`);
1982
2120
  }
1983
2121
  if (command === "models") {
1984
2122
  const subcommand = cli.subcommand;
1985
2123
  const modelInputPath = subcommand === "prefetch" || subcommand === "verify-base" ? resolve3(cli.positionals[0] ?? DEFAULT_LOCAL_SPEC_PATH) : void 0;
1986
- const config = await configFromArgv(argv, modelInputPath);
2124
+ const config = subcommand === "prefetch" && readOption(argv, "--repo") ? await resolveDownloadConfig(modelInputPath, readOption(argv, "--config"), (message) => console.error(message)) : await configFromArgv(argv, modelInputPath);
1987
2125
  const store = createLocalStore(config.storeRoot);
1988
2126
  if (subcommand === "list") return printJson(await store.listModels());
1989
2127
  if (subcommand === "get") {
@@ -2040,19 +2178,35 @@ ${validation.errors.join("\n")}`);
2040
2178
  return printJson({ active: pointer.model_id ?? "base", pointer });
2041
2179
  }
2042
2180
  if (subcommand === "prefetch" || subcommand === "verify-base") {
2181
+ const reporter = createConsoleReporter({
2182
+ verbose: hasFlag(argv, "--verbose"),
2183
+ quiet: hasFlag(argv, "--quiet")
2184
+ });
2185
+ const repo = readOption(argv, "--repo");
2186
+ if (repo) {
2187
+ if (subcommand !== "prefetch") throw new Error("--repo applies to `tt models prefetch`; use `tt assets verify` for downloads.");
2188
+ const revision = readOption(argv, "--revision");
2189
+ return printJson(await prefetchBaseModel({ source: { repo, ...revision ? { revision } : {} }, config, reporter }));
2190
+ }
2043
2191
  const input = await loadCliBehaviorSpec(modelInputPath);
2044
2192
  const report = await prefetchBaseModel({
2045
2193
  request: input.request,
2046
2194
  config,
2047
2195
  localOnly: subcommand === "verify-base",
2048
- reporter: createConsoleReporter({
2049
- verbose: hasFlag(argv, "--verbose"),
2050
- quiet: hasFlag(argv, "--quiet")
2051
- })
2196
+ reporter
2052
2197
  });
2198
+ const locked = subcommand === "prefetch" && report.snapshot_path && report.snapshot_revision;
2199
+ if (locked) {
2200
+ await writeSpecLockEntry(input.path, "base_model", {
2201
+ repo: report.base_model,
2202
+ revision: report.snapshot_revision,
2203
+ files: (await snapshotDigests(report.snapshot_path)).files
2204
+ });
2205
+ }
2053
2206
  return printJson({
2054
2207
  ...report,
2055
- input_path: input.path
2208
+ input_path: input.path,
2209
+ ...locked ? { lock_path: specLockPath(input.path) } : {}
2056
2210
  });
2057
2211
  }
2058
2212
  if (subcommand === "serve") {
@@ -2089,6 +2243,7 @@ export {
2089
2243
  buildLocalBaseModelServerLaunch,
2090
2244
  buildLocalModelServerLaunch,
2091
2245
  buildModelPrefetchPayload,
2246
+ buildModelSourcePrefetchPayload,
2092
2247
  buildSystemMessage,
2093
2248
  canonicalLocalPipeline,
2094
2249
  compareRuns,
@@ -2168,6 +2323,7 @@ export {
2168
2323
  verifyLocalBaseModel,
2169
2324
  verifyLocalModelServerLaunch,
2170
2325
  verifyModelPrefetchCacheReport,
2326
+ withProgressHeartbeat,
2171
2327
  writePrivateJsonAtomic
2172
2328
  };
2173
2329
  //# sourceMappingURL=local-runtime.js.map