ticketlens 0.9.15 → 0.9.17

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.
@@ -34,15 +34,26 @@ import { browserLogin } from '../skills/jtb/scripts/lib/browser-login.mjs';
34
34
  import { syncProfiles, getApiBase, getConsoleBase } from '../skills/jtb/scripts/lib/sync.mjs';
35
35
  import { promptSecret, promptText } from '../skills/jtb/scripts/lib/prompt-helpers.mjs';
36
36
  import { checkForUpdate, getUpdateHint } from '../skills/jtb/scripts/lib/update-check.mjs';
37
- import { incrementInvocation } from '../skills/jtb/scripts/lib/activity-counter.mjs';
37
+ import { incrementInvocation, incrementCommand } from '../skills/jtb/scripts/lib/activity-counter.mjs';
38
38
  import { DEFAULT_CONFIG_DIR } from '../skills/jtb/scripts/lib/config.mjs';
39
39
 
40
+ const TRACKED_COMMANDS = new Set([
41
+ 'triage', 'fetch', 'get', 'compliance', 'review', 'standup',
42
+ 'pr', 'ledger', 'stats', 'collisions', 'history', 'schedule',
43
+ 'brief', 'sync',
44
+ ]);
45
+
40
46
  const args = process.argv.slice(2);
41
47
  const { command, args: cmdArgs } = parseCommand(args);
42
48
 
43
49
  // Best-effort invocation counter — non-fatal
44
50
  try { incrementInvocation(DEFAULT_CONFIG_DIR); } catch { /* non-fatal */ }
45
51
 
52
+ // Per-command + per-flag tracking — non-fatal
53
+ if (TRACKED_COMMANDS.has(command)) {
54
+ try { incrementCommand(DEFAULT_CONFIG_DIR, command, cmdArgs); } catch { /* non-fatal */ }
55
+ }
56
+
46
57
  // Fire-and-forget: silently refresh license.json at startup if >7 days since last validation
47
58
  revalidateIfStale();
48
59
  // Fire-and-forget: refresh the cached latest npm version once per 24h
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticketlens",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "description": "Jira CLI for developers — fetch ticket context, triage your queue, and stop tab-switching. Zero dependencies, all local.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- <!-- jtb-skill-version: 0.9.14 -->
1
+ <!-- jtb-skill-version: 0.9.16 -->
2
2
  ---
3
3
  name: jtb
4
4
  description: Fetch a Jira ticket's full context (description, comments, linked issues, code references) and assemble a structured TicketBrief for implementation planning. Use when user types /jtb, mentions a Jira ticket key, or wants to plan work from a Jira ticket.
@@ -11,6 +11,7 @@ import { styleTriageSummary } from './lib/styled-assembler.mjs';
11
11
  import { resolveConnection, loadProfiles, saveProfile } from './lib/profile-resolver.mjs';
12
12
  import { resolveAdapter } from './lib/resolve-adapter.mjs';
13
13
  import { incrementTriageRun, readAndResetActivity } from './lib/activity-counter.mjs';
14
+ import { DEFAULT_CONFIG_DIR } from './lib/config.mjs';
14
15
  import { writeFileSync, mkdirSync, statSync } from 'node:fs';
15
16
  import { resolve as resolvePath, dirname } from 'node:path';
16
17
  import { createSpinner } from './lib/spinner.mjs';
@@ -485,7 +486,7 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
485
486
  const cliToken = opts.cliToken ?? readCliToken(configDir) ?? null;
486
487
  const printFn = opts.print ?? ((s) => process.stdout.write(s));
487
488
  let cliActivity = null;
488
- try { cliActivity = readAndResetActivity(configDir); } catch { /* non-fatal */ }
489
+ try { cliActivity = readAndResetActivity(configDir ?? DEFAULT_CONFIG_DIR); } catch { /* non-fatal */ }
489
490
  await pushFn({
490
491
  sorted,
491
492
  rawTicketMap,
@@ -12,7 +12,7 @@ import { extractCodeReferences } from './lib/code-ref-parser.mjs';
12
12
  import { assembleBrief } from './lib/brief-assembler.mjs';
13
13
  import { styleBrief } from './lib/styled-assembler.mjs';
14
14
  import { resolveConnection, loadProfiles, loadCredentials, saveProfile } from './lib/profile-resolver.mjs';
15
- import { buildJiraEnv } from './lib/config.mjs';
15
+ import { buildJiraEnv, DEFAULT_CONFIG_DIR } from './lib/config.mjs';
16
16
  import { resolveAdapter } from './lib/resolve-adapter.mjs';
17
17
  import { createSession } from './lib/banner.mjs';
18
18
  import { classifyError } from './lib/error-classifier.mjs';
@@ -24,6 +24,7 @@ import { handleUnknownFlags } from './lib/arg-validator.mjs';
24
24
  import { TICKET_KEY_PATTERN } from './lib/cli.mjs';
25
25
  import { downloadAttachments } from './lib/attachment-downloader.mjs';
26
26
  import { readBriefCache, writeBriefCache, readSummaryCache, writeSummaryCache, briefCacheAge, BRIEF_TTL_MS } from './lib/brief-cache.mjs';
27
+ import { recordTokensSaved } from './lib/activity-counter.mjs';
27
28
  import { readCliToken } from './lib/cli-auth.mjs';
28
29
  import { resolveTemplate } from './lib/template-resolver.mjs';
29
30
  import { parseAge } from './lib/cache-manager.mjs';
@@ -1054,6 +1055,7 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
1054
1055
  }
1055
1056
  }
1056
1057
 
1058
+ recordTokensSaved(configDir ?? DEFAULT_CONFIG_DIR, 'fetch', Math.round(brief.length / 4));
1057
1059
  printFn(brief + '\n');
1058
1060
  return;
1059
1061
  }
@@ -1241,6 +1243,7 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
1241
1243
  }
1242
1244
  }
1243
1245
 
1246
+ recordTokensSaved(configDir ?? DEFAULT_CONFIG_DIR, 'fetch', Math.round(output.length / 4));
1244
1247
  printFn(output + '\n');
1245
1248
 
1246
1249
  // Contextual upsell: after a deep traversal with a substantial graph, nudge toward --summarize
@@ -24,7 +24,7 @@ function read(configDir) {
24
24
  try {
25
25
  return JSON.parse(fs.readFileSync(path.join(configDir, ACTIVITY_FILE), 'utf8'));
26
26
  } catch {
27
- return { fetch_count: 0, triage_run_count: 0, invocations: 0 };
27
+ return { fetch_count: 0, triage_run_count: 0, invocations: 0, commands: {} };
28
28
  }
29
29
  }
30
30
 
@@ -59,12 +59,53 @@ export function incrementInvocation(configDir) {
59
59
  increment(configDir, 'invocations');
60
60
  }
61
61
 
62
+ /**
63
+ * Records one invocation of a named command, plus each --flag present in
64
+ * flagArgs. Flag values are stripped so "--depth=2" tracks as "--depth".
65
+ *
66
+ * @param {string} configDir
67
+ * @param {string} command - e.g. "triage", "fetch"
68
+ * @param {string[]} flagArgs - the raw args passed to the command
69
+ */
70
+ export function incrementCommand(configDir, command, flagArgs = []) {
71
+ const data = read(configDir);
72
+ if (!data.commands) data.commands = {};
73
+ if (!data.commands[command]) data.commands[command] = { count: 0 };
74
+
75
+ data.commands[command].count += 1;
76
+
77
+ for (const arg of flagArgs) {
78
+ if (!arg.startsWith('-')) continue;
79
+ const flag = arg.replace(/=.*$/, '');
80
+ data.commands[command][flag] = (data.commands[command][flag] ?? 0) + 1;
81
+ }
82
+
83
+ write(configDir, data);
84
+ }
85
+
86
+ /**
87
+ * Accumulates estimated tokens saved for a named command.
88
+ * Called from fetch-ticket.mjs after assembling the brief.
89
+ * Best-effort — swallows write errors like all other counters.
90
+ *
91
+ * @param {string} configDir
92
+ * @param {string} command - e.g. "fetch"
93
+ * @param {number} tokens - estimated tokens saved (brief.length / 4)
94
+ */
95
+ export function recordTokensSaved(configDir, command, tokens) {
96
+ const data = read(configDir);
97
+ if (!data.commands) data.commands = {};
98
+ if (!data.commands[command]) data.commands[command] = { count: 0 };
99
+ data.commands[command].tokens_saved = (data.commands[command].tokens_saved ?? 0) + tokens;
100
+ write(configDir, data);
101
+ }
102
+
62
103
  /**
63
104
  * Returns the current counters and resets them to zero.
64
105
  * Call only after a confirmed successful push.
65
106
  *
66
107
  * @param {string} configDir
67
- * @returns {{ fetch_count: number, triage_run_count: number, invocations: number }}
108
+ * @returns {{ fetch_count: number, triage_run_count: number, invocations: number, commands: object }}
68
109
  */
69
110
  export function readAndResetActivity(configDir) {
70
111
  const data = read(configDir);
@@ -72,7 +113,8 @@ export function readAndResetActivity(configDir) {
72
113
  fetch_count: data.fetch_count ?? 0,
73
114
  triage_run_count: data.triage_run_count ?? 0,
74
115
  invocations: data.invocations ?? 0,
116
+ commands: data.commands ?? {},
75
117
  };
76
- write(configDir, { fetch_count: 0, triage_run_count: 0, invocations: 0 });
118
+ write(configDir, { fetch_count: 0, triage_run_count: 0, invocations: 0, commands: {} });
77
119
  return snapshot;
78
120
  }