@dzhechkov/harness-cli 0.3.198 → 0.3.200

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dzhechko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -376,7 +376,7 @@ dz vector import <file.rvf> [--project <dir>] [--json] # RVF import
376
376
  dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC merge of near-dups (dry-run default; --apply after a restorable backup)
377
377
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
378
378
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
379
- dz usage [--json] [--project <dir>] # ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when limits unconfigured
379
+ dz usage [--json] [--project <dir>] | --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] # ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS
380
380
  dz brain list [--json] # the durable cross-project knowledge brain
381
381
  dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first). Auto-broadens: if strict all-terms match yields 0 hits, it retries once as OR and labels the result "broadened" (text note + broadened:true in --json) instead of returning empty. Explicit --any is OR from the start (never labeled broadened).
382
382
  dz brain add [--source <slug>] [--project <dir>] [--from-slice <f>|--from-pack <p>|--from-kus <f> --slug <s> --kind <k> --license <spdx> [--override]] [--json] # grow the brain
@@ -467,29 +467,66 @@ Modeled on the Agentic QE statusline pattern (e.g. AQE's `🎓 12 patterns`).
467
467
 
468
468
  ### Usage estimate (`dz usage`)
469
469
 
470
- `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION (active 5h-block) and WEEKLY
471
- (rolling 7d) token usage, aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts:
470
+ `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION and WEEKLY token usage,
471
+ aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts. Weekly counts start at the
472
+ configured fixed local anchor (`memory.usage.weeklyResetAnchor`, for example `Wed 08:59`). Session
473
+ counts use the active fixed-length transcript block (`memory.usage.sessionBlockHours`, normally `5`).
472
474
 
473
475
  ```bash
474
- dz usage # session ~74% (resets 19:00) · week ~52% (resets Sat) · estimated
475
- dz usage --json # {"sessionPct":74,"weeklyPct":52,"sessionTokens":…,"resetsAt":{},"limits":{},"estimated":true}
476
+ dz usage # session ~74% (resets 19:00) · week ~76% fable-bound (resets 08:59) · estimated
477
+ dz usage --json # {"sessionPct":74,"weeklyPct":76,"sessionTokens":...,"weeklyTokens":...,"resetsAt":{...},"limits":{...},"estimated":true}
476
478
  ```
477
479
 
478
480
  **Exit code is 0 ALWAYS** (even unconfigured/error → all-null JSON) — a probe must never distinguish
479
- "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** (there is no
480
- official usage API); they are `null` until you configure the plan-dependent limits in `.dz/config.json`:
481
+ "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** from local
482
+ transcripts; claude.ai/settings/usage is authoritative. They are `null` until you configure the
483
+ plan-dependent limits in `.dz/config.json`:
481
484
 
482
485
  ```json
483
- { "memory": { "usage": { "sessionTokenLimit": 200000000, "weeklyTokenLimit": 1000000000 } } }
486
+ {
487
+ "memory": {
488
+ "usage": {
489
+ "sessionTokenLimit": 200000000,
490
+ "weeklyTokenLimit": 1000000000,
491
+ "weeklyResetAnchor": "Wed 08:59",
492
+ "sessionBlockHours": 5,
493
+ "weeklyTokenLimitByModel": { "fable": 500000000 }
494
+ }
495
+ }
496
+ }
497
+ ```
498
+
499
+ `sessionTokenLimit` and `weeklyTokenLimit` are optional; absent means that pct is `null` (unknown,
500
+ never `0`). `weeklyTokenLimit` is the all-model weekly limit. `weeklyTokenLimitByModel` is optional;
501
+ when it has at least one valid model limit (`fable`, `opus`, `sonnet`, or `haiku`), `weeklyPct` is the
502
+ binding per-model percentage and `--json` adds:
503
+
504
+ ```json
505
+ {
506
+ "weeklyByModel": { "fable": { "tokens": 380, "pct": 76 } },
507
+ "limits": { "session": 200000000, "weekly": 1000000000, "weeklyByModel": { "fable": 500 } }
508
+ }
509
+ ```
510
+
511
+ When no per-model limits are configured, the JSON shape stays at the legacy fields:
512
+ `sessionPct`, `weeklyPct`, `sessionTokens`, `weeklyTokens`, `resetsAt`, `limits`, `estimated`.
513
+
514
+ Calibrate from human-transcribed claude.ai percentages with one command:
515
+
516
+ ```bash
517
+ dz usage --calibrate --session 20 --weekly 50 --model fable=76 --project .
484
518
  ```
485
519
 
486
- Both keys are OPTIONAL absent that `pct` is `null` (unknown, never `0`). **Calibrate** by scaling a
487
- limit by `X/100` when a real limit-hit lands at an estimated `X%` (observed-exhaustion calibration).
520
+ It computes each limit as `currentTokens / (pct / 100)`, writes only `memory.usage` in
521
+ `.dz/config.json`, and records `calibratedAt` plus `source: "claude.ai/settings/usage"`. Invalid
522
+ percentages, unknown models, missing transcripts, and zero-token calibration requests are skipped with
523
+ exit code `0`.
524
+
488
525
  This feeds `/feature-adr`'s **usage-adaptive routing** — the pipeline probes `dz usage --json` at each
489
- phase boundary and pre-emptively routes the remaining stages to Codex when usage crosses the threshold
490
- (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex dispatch dies (`codex:codex-rescue`
491
- is a Claude wrapper), so the switch must happen BEFORE — the pre-emptive probe, not reactive detection,
492
- is the real defense.
526
+ phase boundary and pre-emptively routes the remaining stages to Codex when `sessionPct` or the binding
527
+ `weeklyPct` crosses the threshold (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex
528
+ dispatch dies (`codex:codex-rescue` is a Claude wrapper), so the switch must happen BEFORE — the
529
+ pre-emptive probe, not reactive detection, is the real defense.
493
530
 
494
531
  ### Targets (10 platforms — 5 lossless + Copilot + AGENTS.md + Cursor + Gemini + Windsurf)
495
532
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyJH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAitGD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgH5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2JH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAq5GD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgH5E"}
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { execSync } from 'node:child_process';
9
9
  import { homedir } from 'node:os';
10
10
  import { createRequire } from 'node:module';
11
11
  import { fileURLToPath } from 'node:url';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, readUsageLimits, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } from '@dzhechkov/harness-core';
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } from '@dzhechkov/harness-core';
13
13
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
14
14
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
15
15
  const USAGE = `dz - DZ cross-platform harness CLI
@@ -51,7 +51,7 @@ Usage:
51
51
  dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
52
52
  dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
53
53
  dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
54
- dz usage [--json] [--project <dir>] (ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when memory.usage.{sessionTokenLimit,weeklyTokenLimit} unconfigured in .dz/config.json)
54
+ dz usage [--json] [--project <dir>] | dz usage --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] [--project <dir>] (ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS; pct=null when limits unconfigured)
55
55
  dz pretrain [--project <dir>]
56
56
  dz recommend "<task description>"
57
57
  dz compose <preset1+preset2+...> [--target <name>]
@@ -81,6 +81,7 @@ Presets: ${PRESET_NAMES.join(', ')}`;
81
81
  /** Parse `<command> [--key value] [--flag]` argv. */
82
82
  function parseArgs(argv) {
83
83
  const options = new Map();
84
+ const optionLists = new Map();
84
85
  const flags = new Set();
85
86
  const positional = [];
86
87
  for (let index = 0; index < argv.length; index += 1) {
@@ -90,6 +91,9 @@ function parseArgs(argv) {
90
91
  const next = argv[index + 1];
91
92
  if (next !== undefined && !next.startsWith('--')) {
92
93
  options.set(key, next);
94
+ const list = optionLists.get(key) ?? [];
95
+ list.push(next);
96
+ optionLists.set(key, list);
93
97
  index += 1;
94
98
  }
95
99
  else {
@@ -104,7 +108,7 @@ function parseArgs(argv) {
104
108
  for (let pi = 1; pi < positional.length; pi += 1) {
105
109
  options.set(`_positional_${pi - 1}`, positional[pi] ?? '');
106
110
  }
107
- return { command: positional[0] ?? '', options, flags };
111
+ return { command: positional[0] ?? '', options, optionLists, flags };
108
112
  }
109
113
  /**
110
114
  * Discover skill source directories: explicit `--skills-dir` if given, else
@@ -947,18 +951,165 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
947
951
  return 0;
948
952
  }
949
953
  }
954
+ function isJsonRecord(value) {
955
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
956
+ }
957
+ function usageConfigPath(projectRoot) {
958
+ return join(projectRoot, '.dz', 'config.json');
959
+ }
960
+ function readProjectConfigForUsage(projectRoot) {
961
+ const path = usageConfigPath(projectRoot);
962
+ try {
963
+ if (!existsSync(path))
964
+ return { config: {} };
965
+ const parsed = JSON.parse(readFileSync(path, 'utf-8'));
966
+ if (isJsonRecord(parsed))
967
+ return { config: parsed };
968
+ return { config: {}, warning: 'existing config is not a JSON object; writing a minimal config' };
969
+ }
970
+ catch {
971
+ return { config: {}, warning: 'existing config could not be parsed; writing a minimal config' };
972
+ }
973
+ }
974
+ function applyUsageCalibrationToConfig(config, plan) {
975
+ const next = { ...config };
976
+ const memory = isJsonRecord(next['memory']) ? { ...next['memory'] } : {};
977
+ const usage = isJsonRecord(memory['usage']) ? { ...memory['usage'] } : {};
978
+ for (const change of plan.changes) {
979
+ if (change.key === 'session') {
980
+ usage['sessionTokenLimit'] = change.after;
981
+ }
982
+ else if (change.key === 'weekly') {
983
+ usage['weeklyTokenLimit'] = change.after;
984
+ }
985
+ else {
986
+ const model = normalizeClaudeUsageModelKey(change.key);
987
+ if (model) {
988
+ const existingByModel = isJsonRecord(usage['weeklyTokenLimitByModel']) ? { ...usage['weeklyTokenLimitByModel'] } : {};
989
+ existingByModel[model] = change.after;
990
+ usage['weeklyTokenLimitByModel'] = existingByModel;
991
+ }
992
+ }
993
+ }
994
+ if (plan.changes.length > 0) {
995
+ usage['calibratedAt'] = plan.after.calibratedAt;
996
+ usage['source'] = plan.after.source;
997
+ }
998
+ memory['usage'] = usage;
999
+ next['memory'] = memory;
1000
+ return next;
1001
+ }
1002
+ function parseUsageModelArgs(modelArgs) {
1003
+ const modelPct = {};
1004
+ const skipped = [];
1005
+ for (const raw of modelArgs) {
1006
+ const eq = raw.indexOf('=');
1007
+ if (eq <= 0 || eq === raw.length - 1) {
1008
+ skipped.push(`model ${raw}: skipped malformed model=pct argument`);
1009
+ continue;
1010
+ }
1011
+ const modelName = raw.slice(0, eq).trim();
1012
+ const model = normalizeClaudeUsageModelKey(modelName);
1013
+ if (!model) {
1014
+ skipped.push(`model ${modelName}: skipped unknown model`);
1015
+ continue;
1016
+ }
1017
+ modelPct[model] = raw.slice(eq + 1).trim();
1018
+ }
1019
+ return { modelPct, skipped };
1020
+ }
1021
+ function writeUsageCalibrationSummary(opts) {
1022
+ opts.write('usage calibrate: estimated local transcript counts; claude.ai/settings/usage is authoritative');
1023
+ opts.write(`usage calibrate: project ${opts.projectRoot}`);
1024
+ if (opts.configWarning)
1025
+ opts.write(`usage calibrate: ${opts.configWarning}`);
1026
+ for (const change of opts.plan.changes) {
1027
+ opts.write(`usage calibrate: ${change.key} tokens=${change.tokens} pct=${change.pct}% limit ${change.before ?? 'null'} -> ${change.after}`);
1028
+ }
1029
+ const skipped = [...opts.preSkipped, ...opts.plan.skipped];
1030
+ for (const item of skipped)
1031
+ opts.write(`usage calibrate: skipped ${item}`);
1032
+ if (opts.wrote) {
1033
+ opts.write('usage calibrate: wrote .dz/config.json with source claude.ai/settings/usage');
1034
+ }
1035
+ else {
1036
+ opts.write('usage calibrate: no config changes written');
1037
+ }
1038
+ }
1039
+ function cmdUsageCalibrate(options, optionLists, cwd, write) {
1040
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
1041
+ const suppliedModels = optionLists.get('model') ?? [];
1042
+ const parsedModels = parseUsageModelArgs(suppliedModels);
1043
+ const modelPct = parsedModels.modelPct;
1044
+ const hasModelPct = Object.keys(modelPct).length > 0;
1045
+ const input = {
1046
+ ...(options.has('session') ? { sessionPct: options.get('session') } : {}),
1047
+ ...(options.has('weekly') ? { weeklyPct: options.get('weekly') } : {}),
1048
+ ...(hasModelPct ? { modelPct } : {}),
1049
+ calibratedAt: new Date().toISOString(),
1050
+ source: 'claude.ai/settings/usage',
1051
+ };
1052
+ const missingInputs = [];
1053
+ if (!options.has('session') && !options.has('weekly') && !hasModelPct) {
1054
+ missingInputs.push('no calibration percentages supplied');
1055
+ }
1056
+ try {
1057
+ const current = computeUsage(projectRoot);
1058
+ const before = readUsageLimits(projectRoot);
1059
+ const plan = deriveUsageCalibration(current, before, input);
1060
+ if (plan.changes.length === 0) {
1061
+ writeUsageCalibrationSummary({
1062
+ projectRoot,
1063
+ plan,
1064
+ preSkipped: [...parsedModels.skipped, ...missingInputs],
1065
+ wrote: false,
1066
+ write,
1067
+ });
1068
+ return 0;
1069
+ }
1070
+ const existing = readProjectConfigForUsage(projectRoot);
1071
+ const nextConfig = applyUsageCalibrationToConfig(existing.config, plan);
1072
+ try {
1073
+ mkdirSync(join(projectRoot, '.dz'), { recursive: true });
1074
+ writeFileSync(usageConfigPath(projectRoot), JSON.stringify(nextConfig, null, 2) + '\n');
1075
+ writeUsageCalibrationSummary({
1076
+ projectRoot,
1077
+ plan,
1078
+ preSkipped: [...parsedModels.skipped, ...missingInputs],
1079
+ configWarning: existing.warning,
1080
+ wrote: true,
1081
+ write,
1082
+ });
1083
+ }
1084
+ catch {
1085
+ writeUsageCalibrationSummary({
1086
+ projectRoot,
1087
+ plan,
1088
+ preSkipped: [...parsedModels.skipped, ...missingInputs, 'write failed'],
1089
+ configWarning: existing.warning,
1090
+ wrote: false,
1091
+ write,
1092
+ });
1093
+ }
1094
+ return 0;
1095
+ }
1096
+ catch {
1097
+ write('usage calibrate: skipped internal error; no config changes written');
1098
+ return 0;
1099
+ }
1100
+ }
950
1101
  /**
951
- * `dz usage` — print an ESTIMATE of Claude session (active 5h-block) + weekly (rolling 7d) token
952
- * usage, aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json`
953
- * emits the single-line contract the feature-adr usage-probe agent parses; the human path prints a
954
- * compact `session ~74% (resets 19:00) · week ~52% (resets Sat)` or an unconfigured hint.
1102
+ * `dz usage` — print an ESTIMATE of Claude session + weekly usage from fixed reset windows,
1103
+ * aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json` emits
1104
+ * the single-line contract the feature-adr usage-probe agent parses; `--calibrate` is the only
1105
+ * write path and records human-transcribed claude.ai percentages in `.dz/config.json`.
955
1106
  *
956
1107
  * **Exit code is 0 ALWAYS** — including on internal error the whole body is guarded and prints the
957
1108
  * all-null JSON, so a probe can NEVER distinguish "usage unknown" from "command failed" via a
958
- * non-zero exit (unknown is a first-class value, INV-3). `--project <dir>` scopes ONLY the
959
- * `.dz/config.json` limits read; the measurement is account-wide (all projects, FR-1.6).
1109
+ * non-zero exit. `--project <dir>` scopes ONLY the `.dz/config.json` read/write; measurement is
1110
+ * account-wide (all projects).
960
1111
  */
961
- function cmdUsage(options, flags, cwd, write) {
1112
+ function cmdUsage(options, optionLists, flags, cwd, write) {
962
1113
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
963
1114
  const nullContract = () => JSON.stringify({
964
1115
  sessionPct: null,
@@ -970,18 +1121,28 @@ function cmdUsage(options, flags, cwd, write) {
970
1121
  estimated: true,
971
1122
  });
972
1123
  try {
1124
+ if (flags.has('calibrate'))
1125
+ return cmdUsageCalibrate(options, optionLists, cwd, write);
973
1126
  const u = computeUsage(projectRoot);
974
1127
  const lim = readUsageLimits(projectRoot);
1128
+ const modelLimits = lim.weeklyTokenLimitByModel;
1129
+ const hasModelLimits = modelLimits !== undefined && Object.keys(modelLimits).length > 0;
975
1130
  if (flags.has('json')) {
976
- write(JSON.stringify({
1131
+ const limitsPayload = { session: lim.sessionTokenLimit ?? null, weekly: lim.weeklyTokenLimit ?? null };
1132
+ if (hasModelLimits)
1133
+ limitsPayload.weeklyByModel = { ...modelLimits };
1134
+ const payload = {
977
1135
  sessionPct: u.sessionPct,
978
1136
  weeklyPct: u.weeklyPct,
979
1137
  sessionTokens: u.sessionTokens,
980
1138
  weeklyTokens: u.weeklyTokens,
981
1139
  resetsAt: { session: u.sessionResetsAt, weekly: u.weeklyResetsAt },
982
- limits: { session: lim.sessionTokenLimit ?? null, weekly: lim.weeklyTokenLimit ?? null },
1140
+ limits: limitsPayload,
983
1141
  estimated: true,
984
- }));
1142
+ };
1143
+ if (hasModelLimits && u.weeklyByModel !== undefined)
1144
+ payload.weeklyByModel = u.weeklyByModel;
1145
+ write(JSON.stringify(payload));
985
1146
  return 0;
986
1147
  }
987
1148
  if (u.sessionPct === null && u.weeklyPct === null) {
@@ -1000,7 +1161,8 @@ function cmdUsage(options, flags, cwd, write) {
1000
1161
  }
1001
1162
  };
1002
1163
  const s = u.sessionPct === null ? 'n/a' : '~' + u.sessionPct + '%';
1003
- const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%';
1164
+ const binding = hasModelLimits && u.weeklyBindingModel !== undefined ? ' ' + u.weeklyBindingModel + '-bound' : '';
1165
+ const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%' + binding;
1004
1166
  write('usage: session ' + s + ' (resets ' + clock(u.sessionResetsAt) + ') · week ' + w + ' (resets ' + clock(u.weeklyResetsAt) + ') · estimated');
1005
1167
  return 0;
1006
1168
  }
@@ -1014,7 +1176,7 @@ function cmdUsage(options, flags, cwd, write) {
1014
1176
  }
1015
1177
  }
1016
1178
  async function cmdTeach(options, flags, cwd, write) {
1017
- const projectRoot = options.get('project') ?? cwd;
1179
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
1018
1180
  // Vector tier (dz-rvf-vector-bridge FR-1): best-effort mirror AFTER the lexical write is
1019
1181
  // durable (I-3). Auto-gated on the agentdb memory backend / an explicit vector-engine config
1020
1182
  // (D3) — an unconfigured project runs ZERO vector code and its output stays byte-identical
@@ -3202,7 +3364,7 @@ export async function runCli(argv, io = {}) {
3202
3364
  return '';
3203
3365
  }
3204
3366
  };
3205
- const { command, options, flags } = parseArgs(argv);
3367
+ const { command, options, optionLists, flags } = parseArgs(argv);
3206
3368
  if (command === '' || command === 'help' || flags.has('help')) {
3207
3369
  write(USAGE);
3208
3370
  return 0;
@@ -3221,7 +3383,7 @@ export async function runCli(argv, io = {}) {
3221
3383
  case 'create-skill':
3222
3384
  return cmdCreateSkill(options, flags, cwd, write);
3223
3385
  case 'info':
3224
- return cmdInfo(options, { command, options, flags }, cwd, write);
3386
+ return cmdInfo(options, { command, options, optionLists, flags }, cwd, write);
3225
3387
  case 'scout':
3226
3388
  return await cmdScout(options, flags, cwd, write);
3227
3389
  case 'workflow':
@@ -3247,7 +3409,7 @@ export async function runCli(argv, io = {}) {
3247
3409
  case 'statusline':
3248
3410
  return cmdStatusline(options, flags, cwd, write, readStdin);
3249
3411
  case 'usage':
3250
- return cmdUsage(options, flags, cwd, write);
3412
+ return cmdUsage(options, optionLists, flags, cwd, write);
3251
3413
  case 'setup':
3252
3414
  return await cmdSetup(options, flags, cwd, write);
3253
3415
  case 'pretrain':