@trazum/cli 1.50.0 → 1.50.2

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/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
2
3
  import { open, readdir, readFile, stat, writeFile } from 'node:fs/promises';
3
- import { join, resolve as resolvePath } from 'node:path';
4
+ import { dirname, join, resolve as resolvePath } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
4
6
  import { gunzipSync } from 'node:zlib';
5
7
  import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
6
8
  import { cacheDir, cacheStats, cachingProvider, clearCache } from './suggest-cache.js';
@@ -287,7 +289,7 @@ function disabledRules(args, config) {
287
289
  * a threshold is set — `--max-growh 5` would have been ignored and the build
288
290
  * gone green. Silence is the wrong answer for a typo.
289
291
  */
290
- const GLOBAL_FLAGS = ['help', 'h', 'locale', 'json', 'config', 'pricing', 'pricing-live'];
292
+ const GLOBAL_FLAGS = ['help', 'h', 'version', 'v', 'locale', 'json', 'config', 'pricing', 'pricing-live'];
291
293
  const COMMAND_FLAGS = {
292
294
  optimize: [
293
295
  'level', 'model', 'calls', 'output-tokens', 'cache-hit-rate', 'batch',
@@ -313,6 +315,7 @@ const COMMAND_FLAGS = {
313
315
  rank: ['level', 'model', 'calls', 'output-tokens', 'batch', 'disable', 'prompt', 'markdown-out'],
314
316
  init: ['dry-run', 'yes', 'json', 'pricing', 'pricing-live'],
315
317
  conform: ['contract', 'json'],
318
+ feedback: [],
316
319
  where: [],
317
320
  rules: [],
318
321
  blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
@@ -1039,6 +1042,43 @@ const INIT_LOG_CANDIDATES = [
1039
1042
  * answers for a file somebody named while this answers for a repository
1040
1043
  * nobody has vouched for.
1041
1044
  */
1045
+ /**
1046
+ * Where feedback goes. Compiled in, never configurable.
1047
+ *
1048
+ * A flag or a config key naming this host would let a fork — or anything that
1049
+ * had rewritten a config on disk — point somebody's bug report, and the
1050
+ * prefilled body with it, at a machine they did not choose. It is one string
1051
+ * and it stays one string.
1052
+ */
1053
+ /**
1054
+ * Which Trazum this is.
1055
+ *
1056
+ * Read from the manifest beside the built entry point rather than baked in by
1057
+ * a generator, so it cannot drift from what npm installed — the one number a
1058
+ * bug report is useless without is the one that must not be a copy.
1059
+ *
1060
+ * `readFileSync` at module load, deliberately: every other read in this file
1061
+ * is async and inside a command, but a version has to be available to
1062
+ * `--version` before any command is chosen, and one small synchronous read at
1063
+ * startup is cheaper than making the whole entry point await.
1064
+ *
1065
+ * A failure falls back to `unknown` rather than throwing. A tool that will not
1066
+ * start because it cannot find its own manifest is worse than one that admits
1067
+ * it does not know — and `unknown` in a bug report is itself a useful fact
1068
+ * about how somebody installed it.
1069
+ */
1070
+ const VERSION = (() => {
1071
+ try {
1072
+ const here = dirname(fileURLToPath(import.meta.url));
1073
+ const manifest = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
1074
+ const found = manifest.version;
1075
+ return typeof found === 'string' ? found : 'unknown';
1076
+ }
1077
+ catch {
1078
+ return 'unknown';
1079
+ }
1080
+ })();
1081
+ const FEEDBACK_REPO = 'https://github.com/Davmunrey/Trazum';
1042
1082
  /** Problems listed before the rest are counted. A wall of them helps nobody. */
1043
1083
  const MAX_CONFORM_PROBLEMS = 20;
1044
1084
  /** The contracts `--contract` accepts, so a typo is refused with the list. */
@@ -1467,6 +1507,74 @@ async function commandConform(args, t) {
1467
1507
  }
1468
1508
  console.log();
1469
1509
  }
1510
+ /**
1511
+ * `trazum feedback` — where to say it, and what to say.
1512
+ *
1513
+ * **This command sends nothing.** Trazum has no telemetry: the CLI makes no
1514
+ * network call it was not explicitly asked to make, and there is no ping, no
1515
+ * install hook and no anonymous counter anywhere in it. That is not an
1516
+ * omission somebody has been meaning to fix — a tool whose entire argument is
1517
+ * that it reads your bill without uploading it cannot also be quietly
1518
+ * reporting on you, and the security suite fails the build if this command
1519
+ * ever reaches the network.
1520
+ *
1521
+ * So the loop is closed the only honest way: the person decides to send
1522
+ * something, and this makes that as cheap as possible. It prints the four
1523
+ * places worth writing to, and a **prefilled link** carrying the facts a
1524
+ * maintainer always has to ask for — version, runtime, platform — printed in
1525
+ * full first, so nothing travels that the sender has not read.
1526
+ *
1527
+ * Nothing about *their work* is in it. Not the config, not a prompt, not a
1528
+ * label, not a figure. Those are the things a bug report needs and the things
1529
+ * only the reporter can decide to share, and a command that helpfully attached
1530
+ * them would be the leak this product exists not to be.
1531
+ */
1532
+ function commandFeedback(t) {
1533
+ const version = VERSION;
1534
+ /**
1535
+ * Facts about the machine, and nothing about the person.
1536
+ *
1537
+ * `process.platform` and the Node version are what every "cannot reproduce"
1538
+ * thread eventually asks for. The locale is here because Trazum ships two
1539
+ * languages and a report reading wrong in one of them is a real bug class.
1540
+ */
1541
+ const environment = [
1542
+ `Trazum ${version}`,
1543
+ `Node ${process.version}`,
1544
+ `${process.platform} ${process.arch}`,
1545
+ `locale ${t.locale}`,
1546
+ ];
1547
+ const body = [
1548
+ '<!-- What happened, and what you expected instead. -->',
1549
+ '',
1550
+ '',
1551
+ '---',
1552
+ ...environment.map((line) => `- ${line}`),
1553
+ ].join('\n');
1554
+ const url = `${FEEDBACK_REPO}/issues/new?body=${encodeURIComponent(body)}`;
1555
+ console.log();
1556
+ console.log(c.bold(t.feedback.heading()));
1557
+ console.log(` ${c.dim(wrap(t.feedback.sendsNothing(), 74, ' '))}`);
1558
+ console.log();
1559
+ console.log(c.bold(t.feedback.whereHeading()));
1560
+ console.log(` ${t.feedback.wrongOptimisation()}`);
1561
+ console.log(` ${c.dim(`${FEEDBACK_REPO}/issues/new?template=wrong_optimisation.yml`)}`);
1562
+ console.log(` ${t.feedback.bug()}`);
1563
+ console.log(` ${c.dim(`${FEEDBACK_REPO}/issues/new?template=bug_report.yml`)}`);
1564
+ console.log(` ${t.feedback.question()}`);
1565
+ console.log(` ${c.dim(`${FEEDBACK_REPO}/discussions`)}`);
1566
+ console.log(` ${t.feedback.security()}`);
1567
+ console.log(` ${c.dim(`${FEEDBACK_REPO}/security/advisories/new`)}`);
1568
+ console.log();
1569
+ console.log(c.bold(t.feedback.environmentHeading()));
1570
+ for (const line of environment)
1571
+ console.log(` ${line}`);
1572
+ console.log(` ${c.dim(wrap(t.feedback.environmentOnly(), 74, ' '))}`);
1573
+ console.log();
1574
+ console.log(c.bold(t.feedback.linkHeading()));
1575
+ console.log(` ${url}`);
1576
+ console.log();
1577
+ }
1470
1578
  function commandModels(t, pricing) {
1471
1579
  const n = (value) => value.toLocaleString(t.numberLocale);
1472
1580
  const col = t.models.columns;
@@ -6265,6 +6373,19 @@ async function main() {
6265
6373
  console.log(t.cache.cleared(removed, before.bytes, dir));
6266
6374
  return;
6267
6375
  }
6376
+ /**
6377
+ * Before the help branch, and before the config loads.
6378
+ *
6379
+ * `trazum --version` on its own is how somebody answers "which one is
6380
+ * installed", and it has to work when the config is broken — that is
6381
+ * precisely the moment they are being asked. Placed above `!args.command`
6382
+ * for the same reason `--clear-suggestion-cache` is: with nothing else on
6383
+ * the line, the help branch would have swallowed it.
6384
+ */
6385
+ if (boolFlag(args, 'version') || boolFlag(args, 'v')) {
6386
+ console.log(VERSION);
6387
+ return;
6388
+ }
6268
6389
  if (boolFlag(args, 'help') || boolFlag(args, 'h') || !args.command) {
6269
6390
  console.log(t.help({
6270
6391
  model: DEFAULT_USAGE.model,
@@ -6365,6 +6486,9 @@ async function main() {
6365
6486
  case 'models':
6366
6487
  commandModels(t, pricing);
6367
6488
  break;
6489
+ case 'feedback':
6490
+ commandFeedback(t);
6491
+ break;
6368
6492
  case 'conform':
6369
6493
  await commandConform(args, t);
6370
6494
  break;