ticketlens 0.21.9 → 0.21.11
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 +10 -7
- package/bin/ticketlens.mjs +25 -2
- package/package.json +1 -1
- package/skills/jtb/SKILL.md +8 -4
- package/skills/jtb/scripts/fetch-ticket.mjs +16 -0
- package/skills/jtb/scripts/lib/help.mjs +129 -5
- package/skills/jtb/scripts/lib/hook-installer.mjs +48 -0
- package/skills/jtb/scripts/lib/schedule-wizard.mjs +27 -0
package/README.md
CHANGED
|
@@ -260,8 +260,8 @@ Runs the same compliance check as `ticketlens CNV1-2 --compliance` but as a dedi
|
|
|
260
260
|
### Compliance Ledger
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
|
-
ticketlens ledger # View the local compliance audit ledger [Pro]
|
|
264
|
-
ticketlens ledger --
|
|
263
|
+
ticketlens ledger # View the local compliance audit ledger, JSON + signature [Pro]
|
|
264
|
+
ticketlens ledger --format=csv # Flat CSV export, no signature [Pro]
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
Displays the append-only local ledger of all compliance checks run on this machine. Useful for SOC 2 / HIPAA audit trails. Requires a Pro license.
|
|
@@ -365,9 +365,11 @@ Cache locations:
|
|
|
365
365
|
ticketlens schedule # Interactive wizard — set digest time, timezone, profile [Pro]
|
|
366
366
|
ticketlens schedule --stop # Cancel the scheduled digest
|
|
367
367
|
ticketlens schedule --status # Show current schedule
|
|
368
|
-
ticketlens schedule --local
|
|
368
|
+
ticketlens schedule --local --time=07:00 --save=./triage.txt # Local-only cron/LaunchAgent, no Console auth [Pro]
|
|
369
369
|
```
|
|
370
370
|
|
|
371
|
+
Not logged in? `ticketlens schedule` falls back to local-only mode automatically — same as passing `--local` explicitly.
|
|
372
|
+
|
|
371
373
|
Stores the schedule as a cron entry. Delivers your triage digest at the configured time without an open terminal. Requires a Pro license.
|
|
372
374
|
|
|
373
375
|
---
|
|
@@ -401,7 +403,7 @@ Every note is scanned before saving — anything shaped like a real secret (API
|
|
|
401
403
|
|
|
402
404
|
**Quality loop:** inside a Claude Code session using the jtb skill, a saved note can be silently refined afterward — a generator subagent drafts a more actionable version, a validator subagent checks it against other notes on the same ticket for duplication, up to 3 rounds — and the improved draft overwrites the original via the internal `note patch` command (not typically invoked by hand). This makes zero API calls and costs zero extra tokens beyond your already-running session; it never runs for a bare shell invocation of `note add`, which is skipped silently. Known limitation: a refined draft is not re-synced to your team even if the original was — teammates who already pulled the note keep the earlier draft.
|
|
403
405
|
|
|
404
|
-
**Team sync:** on
|
|
406
|
+
**Team sync:** included by default on Team/Enterprise; available on Pro too, as a separate add-on (owner-managed per-client, in case the default ever needs to flex). With it enabled, notes also sync to your team's shared pool — `note add` pushes in the background, `recall` always pulls the team's notes fresh before searching, so a manager's verify/delete in Console is visible on that very search. (Ticket-brief injection uses a separate, short-timeout 4h-cached pull so it never slows down your everyday `ticketlens PROJ-123` — it doesn't need to be instant the way an explicit search does.) A team manager reviews and verifies incoming notes at `console/admin/recall` before they're marked trusted. Without Team Recall entitlement, everything stays on your machine — no network call.
|
|
405
407
|
|
|
406
408
|
**Offline resilience:** if a team push fails for a transient reason (network error, timeout, or a 5xx from the backend), the note stays safely in your local vault and is queued for retry — nothing is lost. The queue flushes automatically in the background before every command, not just `recall`/`note add` — a short timeout on that check means it never stalls an unrelated command — or on demand with `ticketlens recall sync`. A session-expired (401) or not-entitled (403) push is never queued — those need you to act (`ticketlens login`, or an owner grant), not a retry. Switching accounts never flushes a note under the wrong login.
|
|
407
409
|
|
|
@@ -595,6 +597,7 @@ ticketlens CNV1-2 --template=quick # Apply quick template (meta + 2
|
|
|
595
597
|
ticketlens CNV1-2 --template=code-review # Apply code-review template (meta + desc + linked + code refs)
|
|
596
598
|
ticketlens CNV1-2 --template=full # Apply full template (all sections, default)
|
|
597
599
|
ticketlens CNV1-2 --template=my-team-template # Apply a custom team template [Team]
|
|
600
|
+
ticketlens CNV1-2 --budget=8000 # Trim brief to fit a token budget [Pro]
|
|
598
601
|
ticketlens CNV1-2 --depth=2 --profile=acme --plain # Combine flags freely
|
|
599
602
|
|
|
600
603
|
# Pipe plain output to clipboard, LLM, or file
|
|
@@ -675,7 +678,7 @@ ticketlens cache clear --older-than=30d --yes # Skip confirmation (CI
|
|
|
675
678
|
ticketlens schedule # Interactive wizard — set time, timezone, profile [Pro]
|
|
676
679
|
ticketlens schedule --stop # Cancel the scheduled digest [Pro]
|
|
677
680
|
ticketlens schedule --status # Show current schedule [Pro]
|
|
678
|
-
ticketlens schedule --local
|
|
681
|
+
ticketlens schedule --local --time=07:00 --save=./triage.txt # Local-only cron/LaunchAgent, no Console auth [Pro]
|
|
679
682
|
|
|
680
683
|
# ── History ───────────────────────────────────────────────────────────────────
|
|
681
684
|
ticketlens history <TICKET-KEY> # Show urgency timeline for a ticket [Pro]
|
|
@@ -685,8 +688,8 @@ echo "note body" | ticketlens note add --title="..." --ticket=CNV1-2 --tags=a,b
|
|
|
685
688
|
ticketlens note delete --id="..." --ticket=CNV1-2 # Remove a note from your local vault [Pro]
|
|
686
689
|
ticketlens recall CNV1-2 # Search saved notes by ticket key [Pro]
|
|
687
690
|
ticketlens recall "retry backoff" # Free-text search across all notes [Pro]
|
|
688
|
-
ticketlens recall sync # Retry any notes stuck in the local queue [
|
|
689
|
-
ticketlens recall settings # Show effective retry-queue settings, fetched live [
|
|
691
|
+
ticketlens recall sync # Retry any notes stuck in the local queue [Team+]
|
|
692
|
+
ticketlens recall settings # Show effective retry-queue settings, fetched live [Team+]
|
|
690
693
|
|
|
691
694
|
# ── Stats ──────────────────────────────────────────────────────────────────────
|
|
692
695
|
ticketlens stats # Response-time metrics from local history
|
package/bin/ticketlens.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
printProfilesHelp, printScheduleHelp,
|
|
25
25
|
printInitHelp, printSwitchHelp, printConfigHelp,
|
|
26
26
|
printReviewHelp, printStandupHelp, printUpdateSkillHelp,
|
|
27
|
+
printComplianceHelp, printLedgerHelp, printPrHelp, printInstallHooksHelp,
|
|
27
28
|
printCollisionsHelp, printStatsHelp,
|
|
28
29
|
printCloudKeysHelp,
|
|
29
30
|
printNoteHelp, printRecallHelp,
|
|
@@ -394,10 +395,28 @@ switch (command) {
|
|
|
394
395
|
break;
|
|
395
396
|
}
|
|
396
397
|
|
|
397
|
-
const
|
|
398
|
+
const cliTokenForSchedule = readCliToken();
|
|
399
|
+
const { shouldUseLocalSchedule, parseLocalScheduleFlags, runScheduleLocal, runScheduleWizard } = await import('../skills/jtb/scripts/lib/schedule-wizard.mjs');
|
|
400
|
+
|
|
401
|
+
if (shouldUseLocalSchedule({ cmdArgs, cliToken: cliTokenForSchedule })) {
|
|
402
|
+
const { time, outputFile } = parseLocalScheduleFlags(cmdArgs);
|
|
403
|
+
if (!time || !outputFile) {
|
|
404
|
+
process.stderr.write(` Local scheduling requires --time and --save.\n`);
|
|
405
|
+
process.stderr.write(` Usage: ticketlens schedule --local --time=HH:MM --save=FILE\n`);
|
|
406
|
+
if (!cliTokenForSchedule) {
|
|
407
|
+
process.stderr.write(` Or run \`ticketlens login\` first to use the Console-backed wizard.\n`);
|
|
408
|
+
}
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
const localResult = await runScheduleLocal({ answers: { time, outputFile } });
|
|
413
|
+
if (!localResult.ok) { process.exitCode = 1; }
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
|
|
398
417
|
const { promptScheduleAnswers } = await import('../skills/jtb/scripts/lib/prompt-helpers.mjs');
|
|
399
418
|
const answers = await promptScheduleAnswers(cmdArgs);
|
|
400
|
-
const result = await runScheduleWizard({ answers, cliToken:
|
|
419
|
+
const result = await runScheduleWizard({ answers, cliToken: cliTokenForSchedule });
|
|
401
420
|
if (!result.ok) { process.exitCode = 1; break; }
|
|
402
421
|
|
|
403
422
|
const s = createStyler({ isTTY: process.stdout.isTTY });
|
|
@@ -410,6 +429,7 @@ switch (command) {
|
|
|
410
429
|
}
|
|
411
430
|
|
|
412
431
|
case 'install-hooks':
|
|
432
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printInstallHooksHelp(); break; }
|
|
413
433
|
runFetch(['install-hooks', ...cmdArgs]).catch(err => {
|
|
414
434
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
415
435
|
process.exitCode = 1;
|
|
@@ -417,6 +437,7 @@ switch (command) {
|
|
|
417
437
|
break;
|
|
418
438
|
|
|
419
439
|
case 'pr':
|
|
440
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printPrHelp(); break; }
|
|
420
441
|
runFetch(['pr', ...cmdArgs]).catch(err => {
|
|
421
442
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
422
443
|
process.exitCode = 1;
|
|
@@ -440,6 +461,7 @@ switch (command) {
|
|
|
440
461
|
break;
|
|
441
462
|
|
|
442
463
|
case 'ledger':
|
|
464
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printLedgerHelp(); break; }
|
|
443
465
|
runFetch(['ledger', ...cmdArgs]).catch(err => {
|
|
444
466
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
445
467
|
process.exitCode = 1;
|
|
@@ -447,6 +469,7 @@ switch (command) {
|
|
|
447
469
|
break;
|
|
448
470
|
|
|
449
471
|
case 'compliance':
|
|
472
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) { printComplianceHelp(); break; }
|
|
450
473
|
runFetch(['compliance', ...cmdArgs]).catch(err => {
|
|
451
474
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
452
475
|
process.exitCode = 1;
|
package/package.json
CHANGED
package/skills/jtb/SKILL.md
CHANGED
|
@@ -44,6 +44,10 @@ Fetches a Jira ticket and produces a structured brief with code references, then
|
|
|
44
44
|
/jtb stats --format=json # JSON output for scripting
|
|
45
45
|
/jtb collisions # show branch collisions with teammates (Team)
|
|
46
46
|
/jtb collisions --json # machine-readable output
|
|
47
|
+
/jtb review # code-review context brief from current branch
|
|
48
|
+
/jtb review --branch=main # compare against a specific branch
|
|
49
|
+
/jtb standup # standup summary from git log (last 24h)
|
|
50
|
+
/jtb standup --format=pr # PR body instead of standup bullets
|
|
47
51
|
/jtb cloud-keys list # list configured AI provider keys (Pro)
|
|
48
52
|
/jtb cloud-keys add groq gsk_xxxx # add Groq key (free tier — console.groq.com)
|
|
49
53
|
/jtb cloud-keys add anthropic sk-ant-x # add Anthropic key
|
|
@@ -52,10 +56,10 @@ Fetches a Jira ticket and produces a structured brief with code references, then
|
|
|
52
56
|
/jtb cloud-keys remove groq # remove a provider
|
|
53
57
|
/jtb cloud-keys priority groq 1 # set provider priority (lower = tried first)
|
|
54
58
|
/jtb cloud-keys timeout anthropic 15 # set per-request timeout in seconds
|
|
55
|
-
/jtb note "gotcha text" --ticket=PROD-1234 # save a Recall note (Pro)
|
|
59
|
+
/jtb note add --title="gotcha text" --ticket=PROD-1234 # save a Recall note (Pro, body from stdin)
|
|
56
60
|
/jtb recall PROD-1234 # search saved Recall notes (Pro)
|
|
57
|
-
/jtb recall sync # retry any notes stuck in the local queue (
|
|
58
|
-
/jtb recall settings # show effective retry-queue settings, fetched live (
|
|
61
|
+
/jtb recall sync # retry any notes stuck in the local queue (Team+)
|
|
62
|
+
/jtb recall settings # show effective retry-queue settings, fetched live (Team+)
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
## Prerequisites
|
|
@@ -246,7 +250,7 @@ This never calls any external API or bills any tokens beyond the session you alr
|
|
|
246
250
|
**Known limitation:** `note patch` only updates the local vault copy. If `note add` already pushed the original draft to a team (Team Recall enabled), a later refinement from this loop is *not* re-pushed — teammates who already pulled the note keep the original draft until this is addressed in a future iteration.
|
|
247
251
|
|
|
248
252
|
### Privacy
|
|
249
|
-
Recall notes are stored locally at `~/.ticketlens/recall/`. On a
|
|
253
|
+
Recall notes are stored locally at `~/.ticketlens/recall/`. On a Pro account with no Team Recall entitlement, they never leave the machine — no network calls (Free tier can't use Recall at all). On Team/Enterprise, Recall's team sync is included by default (Pro accounts can get it too, as a separate add-on); notes also sync to the team's shared pool in the background so teammates can benefit from them too, and a team manager reviews and verifies each incoming note before it's marked trusted. If a team push fails for a transient reason (network error, timeout, 5xx), the note is queued locally and retried automatically in the background, or on demand with `ticketlens recall sync` [Team+] — a session-expired or not-entitled push is never queued, since retrying those can't succeed without the user acting first.
|
|
250
254
|
|
|
251
255
|
---
|
|
252
256
|
|
|
@@ -493,6 +493,22 @@ export async function run(args, envOrOpts = process.env, fetcher = globalThis.fe
|
|
|
493
493
|
|
|
494
494
|
// Early dispatch for non-ticket subcommands
|
|
495
495
|
if (args[0] === 'install-hooks') {
|
|
496
|
+
if (args.includes('--uninstall')) {
|
|
497
|
+
const { uninstallHook } = await import('./lib/hook-installer.mjs');
|
|
498
|
+
try {
|
|
499
|
+
const result = await uninstallHook({ cwd: process.cwd() });
|
|
500
|
+
if (result.skipped) {
|
|
501
|
+
process.stderr.write(` Hook uninstall skipped: ${result.reason}\n`);
|
|
502
|
+
} else {
|
|
503
|
+
process.stdout.write(` Hook removed: ${result.path}\n`);
|
|
504
|
+
}
|
|
505
|
+
} catch (err) {
|
|
506
|
+
process.stderr.write(` Error removing hook: ${err.message}\n`);
|
|
507
|
+
process.exitCode = 1;
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
496
512
|
const { installHook } = await import('./lib/hook-installer.mjs');
|
|
497
513
|
try {
|
|
498
514
|
const result = await installHook({ cwd: process.cwd() });
|
|
@@ -43,23 +43,30 @@ export function printHelp({ stream = process.stdout } = {}) {
|
|
|
43
43
|
` ${s.brand('ticketlens')} review ${s.dim('[--branch=BRANCH]')} Code-review context brief from current branch`,
|
|
44
44
|
` ${s.brand('ticketlens')} standup ${s.dim('[--since=N]')} Standup summary from git log ${s.dim('(last 24h by default)')}`,
|
|
45
45
|
` ${s.brand('ticketlens')} compliance ${s.dim('<TICKET-KEY>')} Check requirements coverage ${s.dim('[Pro/Free 3/mo]')}`,
|
|
46
|
+
` ${s.brand('ticketlens')} install-hooks ${s.dim('[--uninstall]')} Git pre-push compliance gate`,
|
|
47
|
+
` ${s.brand('ticketlens')} pr ${s.dim('<TICKET-KEY>')} Assemble a PR description from ticket context`,
|
|
48
|
+
` ${s.brand('ticketlens')} ledger ${s.dim('[--format=json|csv]')} Export your signed usage ledger ${s.dim('[Pro]')}`,
|
|
46
49
|
` ${s.brand('ticketlens')} history ${s.dim('<TICKET-KEY>')} Urgency timeline for a ticket ${s.dim('[Pro]')}`,
|
|
47
50
|
` ${s.brand('ticketlens')} stats ${s.dim('[options]')} Personal response-time metrics from local history`,
|
|
48
51
|
` ${s.brand('ticketlens')} note add ${s.dim('--title=... [--ticket=KEY]')} Save a Recall note ${s.dim('[Pro]')}`,
|
|
49
52
|
` ${s.brand('ticketlens')} note delete ${s.dim('--id=... [--ticket=KEY]')} Remove a note from your local vault ${s.dim('[Pro]')}`,
|
|
50
53
|
` ${s.brand('ticketlens')} recall ${s.dim('<query|TICKET-KEY>')} Search your saved Recall notes ${s.dim('[Pro]')}`,
|
|
54
|
+
` ${s.brand('ticketlens')} recall sync Retry any notes stuck in the local queue ${s.dim('[Team+]')}`,
|
|
55
|
+
` ${s.brand('ticketlens')} recall settings Show effective retry-queue settings, fetched live ${s.dim('[Team+]')}`,
|
|
51
56
|
'',
|
|
52
57
|
` ${s.brand('ticketlens')} delete ${s.dim('<PROFILE-NAME>')} Remove a profile`,
|
|
53
58
|
` ${s.brand('ticketlens')} activate ${s.dim('<KEY>')} Activate a license key`,
|
|
54
59
|
` ${s.brand('ticketlens')} license Show license status`,
|
|
55
60
|
` ${s.brand('ticketlens')} cache ${s.dim('[size|clear]')} Manage attachment cache ${s.dim('(try cache --help)')}`,
|
|
56
|
-
` ${s.brand('ticketlens')} schedule ${s.dim('[--stop|--status]')} Manage digest schedule ${s.dim('[Pro]')}`,
|
|
61
|
+
` ${s.brand('ticketlens')} schedule ${s.dim('[--stop|--status|--local]')} Manage digest schedule ${s.dim('[Pro]')}`,
|
|
57
62
|
` ${s.brand('ticketlens')} cloud-keys ${s.dim('[add|remove|list|test]')} Manage your encrypted AI provider keys ${s.dim('[Pro]')}`,
|
|
58
63
|
` ${s.brand('ticketlens')} update-skill ${s.dim('[--dry-run]')} Update /jtb skill in Claude Code and other AI assistants`,
|
|
59
64
|
'',
|
|
60
65
|
` ${s.bold('GLOBAL OPTIONS')}`,
|
|
61
66
|
'',
|
|
62
67
|
` ${s.brand('--no-input')} Force non-interactive behavior even in a terminal`,
|
|
68
|
+
` ${s.brand('-v')}, ${s.brand('--version')} Show version and exit ${s.dim('(same as ticketlens version)')}`,
|
|
69
|
+
` ${s.brand('clear')} Alias for ${s.brand('cache clear')}`,
|
|
63
70
|
'',
|
|
64
71
|
` ${s.bold('FETCH OPTIONS')}`,
|
|
65
72
|
'',
|
|
@@ -78,6 +85,7 @@ export function printHelp({ stream = process.stdout } = {}) {
|
|
|
78
85
|
` ${s.brand('--cloud')} Route AI request through TicketLens API ${s.dim('[Pro]')}`,
|
|
79
86
|
` ${s.brand('--provider')}=${s.dim('NAME')} Force AI provider ${s.dim('(anthropic|openai|groq)')}`,
|
|
80
87
|
` ${s.brand('--template')}=${s.dim('SLUG')} Apply a brief template ${s.dim('(full|quick|code-review, or custom [Team])')}`,
|
|
88
|
+
` ${s.brand('--budget')}=${s.dim('N')} Trim brief to fit a token budget ${s.dim('[Pro]')}`,
|
|
81
89
|
'',
|
|
82
90
|
` ${s.bold('TRIAGE OPTIONS')}`,
|
|
83
91
|
'',
|
|
@@ -100,6 +108,7 @@ export function printHelp({ stream = process.stdout } = {}) {
|
|
|
100
108
|
` ${s.brand('--digest')} POST scored results to digest endpoint ${s.dim('[Pro]')}`,
|
|
101
109
|
` ${s.brand('--static')} Static table output ${s.dim('(skip interactive mode)')}`,
|
|
102
110
|
` ${s.brand('--plain')} Plain markdown output ${s.dim('(for piping / LLM)')}`,
|
|
111
|
+
` ${s.brand('--styled')} Force ANSI-styled table output`,
|
|
103
112
|
'',
|
|
104
113
|
` ${s.bold('EXAMPLES')}`,
|
|
105
114
|
'',
|
|
@@ -444,15 +453,23 @@ export function printScheduleHelp({ stream = process.stdout } = {}) {
|
|
|
444
453
|
const s = createStyler({ isTTY: stream.isTTY });
|
|
445
454
|
const lines = [
|
|
446
455
|
'',
|
|
447
|
-
` ${s.bold(s.brand('ticketlens'))} ${s.bold('schedule')} ${s.dim('[--stop|--status]')} ${s.dim('[Pro]')}`,
|
|
456
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('schedule')} ${s.dim('[--stop|--status|--local]')} ${s.dim('[Pro]')}`,
|
|
448
457
|
'',
|
|
449
458
|
` Set up a recurring digest email with your triage results. ${s.dim('[Pro]')}`,
|
|
450
459
|
` Runs an interactive wizard to configure day, time, and timezone.`,
|
|
460
|
+
` Without Console login, falls back to local-only scheduling automatically —`,
|
|
461
|
+
` no interactive wizard, no digest email, just a cron/LaunchAgent entry that`,
|
|
462
|
+
` writes triage output to a file.`,
|
|
451
463
|
'',
|
|
452
464
|
` ${s.bold('OPTIONS')}`,
|
|
453
465
|
'',
|
|
454
466
|
` ${s.brand('--stop')} Cancel the active digest schedule`,
|
|
455
467
|
` ${s.brand('--status')} Show current schedule configuration`,
|
|
468
|
+
` ${s.brand('--time')}=${s.dim('HH:MM')} Pre-fill delivery/run time ${s.dim('(skips the interactive prompt)')}`,
|
|
469
|
+
` ${s.brand('--email')}=${s.dim('ADDR')} Pre-fill delivery email ${s.dim('(Console-backed wizard only)')}`,
|
|
470
|
+
` ${s.brand('--timezone')}=${s.dim('TZ')} Pre-fill timezone ${s.dim('(Console-backed wizard only)')}`,
|
|
471
|
+
` ${s.brand('--local')} Local-only cron/LaunchAgent, no Console auth ${s.dim('(auto-detected when logged out)')}`,
|
|
472
|
+
` ${s.brand('--save')}=${s.dim('FILE')} Output file for local mode ${s.dim('(required with --local)')}`,
|
|
456
473
|
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
457
474
|
'',
|
|
458
475
|
` ${s.bold('EXAMPLES')}`,
|
|
@@ -460,6 +477,7 @@ export function printScheduleHelp({ stream = process.stdout } = {}) {
|
|
|
460
477
|
` ${s.dim('$')} ticketlens schedule`,
|
|
461
478
|
` ${s.dim('$')} ticketlens schedule --status`,
|
|
462
479
|
` ${s.dim('$')} ticketlens schedule --stop`,
|
|
480
|
+
` ${s.dim('$')} ticketlens schedule --local --time=07:00 --save=./triage.txt`,
|
|
463
481
|
'',
|
|
464
482
|
];
|
|
465
483
|
stream.write(lines.join('\n') + '\n');
|
|
@@ -537,7 +555,9 @@ export function printRecallHelp({ stream = process.stdout } = {}) {
|
|
|
537
555
|
'',
|
|
538
556
|
` ${s.bold(s.brand('ticketlens'))} ${s.bold('recall')} ${s.dim('<query|TICKET-KEY>')} ${s.dim('[Pro]')}`,
|
|
539
557
|
'',
|
|
540
|
-
` Search your saved Recall notes.
|
|
558
|
+
` Search your saved Recall notes. ${s.dim('[Pro]')}`,
|
|
559
|
+
` Works fully offline if you have no team. Logged in with team sync enabled,`,
|
|
560
|
+
` pulls the team's notes fresh before every search.`,
|
|
541
561
|
'',
|
|
542
562
|
` ${s.bold('OPTIONS')}`,
|
|
543
563
|
'',
|
|
@@ -547,8 +567,10 @@ export function printRecallHelp({ stream = process.stdout } = {}) {
|
|
|
547
567
|
'',
|
|
548
568
|
` ${s.bold('COMMANDS')}`,
|
|
549
569
|
'',
|
|
550
|
-
` ${s.brand('sync')} Manually retry any team-synced notes stuck in the local retry queue ${s.dim('[
|
|
551
|
-
` ${s.brand('settings')} Show effective retry-queue settings (cooldown, timeout, queue limits) ${s.dim('[
|
|
570
|
+
` ${s.brand('sync')} Manually retry any team-synced notes stuck in the local retry queue ${s.dim('[Team+, requires login]')}`,
|
|
571
|
+
` ${s.brand('settings')} Show effective retry-queue settings (cooldown, timeout, queue limits) ${s.dim('[Team+]')}`,
|
|
572
|
+
'',
|
|
573
|
+
` ${s.dim('Team+ = included on Team/Enterprise; available on Pro as a separate Recall add-on.')}`,
|
|
552
574
|
'',
|
|
553
575
|
` ${s.bold('EXAMPLES')}`,
|
|
554
576
|
'',
|
|
@@ -710,6 +732,108 @@ export function printReviewHelp({ stream = process.stdout } = {}) {
|
|
|
710
732
|
stream.write(lines.join('\n') + '\n');
|
|
711
733
|
}
|
|
712
734
|
|
|
735
|
+
export function printComplianceHelp({ stream = process.stdout } = {}) {
|
|
736
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
737
|
+
const lines = [
|
|
738
|
+
'',
|
|
739
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('compliance')} ${s.dim('<TICKET-KEY> [--profile=NAME]')} ${s.dim('[Pro/Free 3/mo]')}`,
|
|
740
|
+
'',
|
|
741
|
+
` Check your current branch's diff against the ticket's requirements.`,
|
|
742
|
+
` Extracts candidate requirements from the ticket description and diffs`,
|
|
743
|
+
` them against what the local git diff actually covers, reporting a`,
|
|
744
|
+
` coverage percentage and a list of uncovered items.`,
|
|
745
|
+
'',
|
|
746
|
+
` Used internally by the pre-push hook installed via ${s.brand('ticketlens install-hooks')}`,
|
|
747
|
+
` (reads the threshold from ${s.dim('.ticketlens-hooks.json')}, default 80%).`,
|
|
748
|
+
'',
|
|
749
|
+
` ${s.bold('OPTIONS')}`,
|
|
750
|
+
'',
|
|
751
|
+
` ${s.brand('--profile')}=${s.dim('NAME')} Use a specific Jira profile`,
|
|
752
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
753
|
+
'',
|
|
754
|
+
` ${s.bold('EXAMPLES')}`,
|
|
755
|
+
'',
|
|
756
|
+
` ${s.dim('$')} ticketlens compliance PROJ-123`,
|
|
757
|
+
` ${s.dim('$')} ticketlens compliance PROJ-123 --profile=myteam`,
|
|
758
|
+
'',
|
|
759
|
+
];
|
|
760
|
+
stream.write(lines.join('\n') + '\n');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function printLedgerHelp({ stream = process.stdout } = {}) {
|
|
764
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
765
|
+
const lines = [
|
|
766
|
+
'',
|
|
767
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('ledger')} ${s.dim('[--format=json|csv]')} ${s.dim('[Pro]')}`,
|
|
768
|
+
'',
|
|
769
|
+
` Export your local usage ledger — a signed, tamper-evident record of`,
|
|
770
|
+
` billable actions (AI calls, exports, etc). Verifiable offline via an`,
|
|
771
|
+
` HMAC-SHA256 signature over {records, exportedAt}, keyed at ledger-key.`,
|
|
772
|
+
'',
|
|
773
|
+
` ${s.bold('OPTIONS')}`,
|
|
774
|
+
'',
|
|
775
|
+
` ${s.brand('--format')}=${s.dim('json')} Full record export with signature ${s.dim('(default)')}`,
|
|
776
|
+
` ${s.brand('--format')}=${s.dim('csv')} Flat CSV, no signature`,
|
|
777
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
778
|
+
'',
|
|
779
|
+
` ${s.bold('EXAMPLES')}`,
|
|
780
|
+
'',
|
|
781
|
+
` ${s.dim('$')} ticketlens ledger`,
|
|
782
|
+
` ${s.dim('$')} ticketlens ledger --format=csv`,
|
|
783
|
+
'',
|
|
784
|
+
];
|
|
785
|
+
stream.write(lines.join('\n') + '\n');
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function printPrHelp({ stream = process.stdout } = {}) {
|
|
789
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
790
|
+
const lines = [
|
|
791
|
+
'',
|
|
792
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('pr')} ${s.dim('<TICKET-KEY> [--profile=NAME]')}`,
|
|
793
|
+
'',
|
|
794
|
+
` Assemble a pull-request description from a ticket's context —`,
|
|
795
|
+
` summary, acceptance criteria, and linked issues, formatted as a`,
|
|
796
|
+
` ready-to-paste PR body.`,
|
|
797
|
+
'',
|
|
798
|
+
` ${s.bold('OPTIONS')}`,
|
|
799
|
+
'',
|
|
800
|
+
` ${s.brand('--profile')}=${s.dim('NAME')} Use a specific Jira profile`,
|
|
801
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
802
|
+
'',
|
|
803
|
+
` ${s.bold('EXAMPLES')}`,
|
|
804
|
+
'',
|
|
805
|
+
` ${s.dim('$')} ticketlens pr PROJ-123`,
|
|
806
|
+
` ${s.dim('$')} ticketlens pr PROJ-123 --profile=myteam`,
|
|
807
|
+
'',
|
|
808
|
+
];
|
|
809
|
+
stream.write(lines.join('\n') + '\n');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export function printInstallHooksHelp({ stream = process.stdout } = {}) {
|
|
813
|
+
const s = createStyler({ isTTY: stream.isTTY });
|
|
814
|
+
const lines = [
|
|
815
|
+
'',
|
|
816
|
+
` ${s.bold(s.brand('ticketlens'))} ${s.bold('install-hooks')} ${s.dim('[--uninstall]')}`,
|
|
817
|
+
'',
|
|
818
|
+
` Installs a git pre-push hook that blocks a push when this branch's`,
|
|
819
|
+
` compliance coverage (see ${s.brand('ticketlens compliance')}) is below 80%.`,
|
|
820
|
+
` Appends to an existing pre-push hook rather than overwriting it;`,
|
|
821
|
+
` calling it again is a no-op if already installed.`,
|
|
822
|
+
'',
|
|
823
|
+
` ${s.bold('OPTIONS')}`,
|
|
824
|
+
'',
|
|
825
|
+
` ${s.brand('--uninstall')} Remove the hook, restoring any pre-existing pre-push content`,
|
|
826
|
+
` ${s.brand('-h')}, ${s.brand('--help')} Show this help`,
|
|
827
|
+
'',
|
|
828
|
+
` ${s.bold('EXAMPLES')}`,
|
|
829
|
+
'',
|
|
830
|
+
` ${s.dim('$')} ticketlens install-hooks`,
|
|
831
|
+
` ${s.dim('$')} ticketlens install-hooks --uninstall`,
|
|
832
|
+
'',
|
|
833
|
+
];
|
|
834
|
+
stream.write(lines.join('\n') + '\n');
|
|
835
|
+
}
|
|
836
|
+
|
|
713
837
|
export function printUpdateSkillHelp({ stream = process.stdout } = {}) {
|
|
714
838
|
const s = createStyler({ isTTY: stream.isTTY });
|
|
715
839
|
const lines = [
|
|
@@ -79,3 +79,51 @@ export function installHook({ cwd = process.cwd(), threshold = 80, fsModule = fs
|
|
|
79
79
|
|
|
80
80
|
return { installed: true, path: hookPath };
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Removes a previously-installed compliance gate, leaving any pre-existing
|
|
85
|
+
* (non-ticketlens) pre-push hook content untouched. installHook() always
|
|
86
|
+
* appends its block last, prefixed with its own `#!/bin/sh` line — this
|
|
87
|
+
* strips from that shebang line (and one preceding blank line from the
|
|
88
|
+
* append's leading '\n', if present) through end of file. If nothing
|
|
89
|
+
* precedes that point, the file is deleted rather than left empty.
|
|
90
|
+
*
|
|
91
|
+
* @param {{ cwd?: string, fsModule?: typeof import('node:fs') }} [opts]
|
|
92
|
+
* @returns {{ uninstalled: true, path: string } | { skipped: true, reason: string }}
|
|
93
|
+
*/
|
|
94
|
+
export function uninstallHook({ cwd = process.cwd(), fsModule = fs } = {}) {
|
|
95
|
+
const hookPath = join(cwd, '.git', 'hooks', 'pre-push');
|
|
96
|
+
|
|
97
|
+
let existing;
|
|
98
|
+
try {
|
|
99
|
+
existing = fsModule.readFileSync(hookPath, 'utf8');
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (err.code === 'ENOENT') return { skipped: true, reason: 'No pre-push hook installed.' };
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!existing.includes(GUARD)) {
|
|
106
|
+
return { skipped: true, reason: 'No ticketlens compliance gate found in the pre-push hook.' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const guardIdx = existing.indexOf(GUARD);
|
|
110
|
+
const shebangIdx = existing.lastIndexOf('#!/bin/sh\n', guardIdx);
|
|
111
|
+
let cutStart = shebangIdx;
|
|
112
|
+
if (cutStart > 0 && existing[cutStart - 1] === '\n') cutStart -= 1;
|
|
113
|
+
|
|
114
|
+
const remaining = existing.slice(0, cutStart);
|
|
115
|
+
if (remaining.trim() === '') {
|
|
116
|
+
fsModule.unlinkSync(hookPath);
|
|
117
|
+
} else {
|
|
118
|
+
fsModule.writeFileSync(hookPath, remaining, 'utf8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const configPath = join(cwd, '.ticketlens-hooks.json');
|
|
122
|
+
try {
|
|
123
|
+
fsModule.unlinkSync(configPath);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
if (err.code !== 'ENOENT') throw err;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { uninstalled: true, path: hookPath };
|
|
129
|
+
}
|
|
@@ -252,6 +252,33 @@ export function buildLocalCronLine({ hour, minute, ticketlensBin, outputFile })
|
|
|
252
252
|
return `${minute} ${hour} * * * ${ticketlensBin} triage '--save=${outputFile}' >> '${outputFile}.err' 2>&1`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Decides whether `ticketlens schedule` should use the local-only path
|
|
257
|
+
* (runScheduleLocal) instead of the Console-backed wizard (runScheduleWizard)
|
|
258
|
+
* — explicit `--local`, or auto-detected whenever there's no cliToken at all,
|
|
259
|
+
* so a logged-out user gets a working local schedule instead of a hard
|
|
260
|
+
* "log in first" error.
|
|
261
|
+
*
|
|
262
|
+
* @param {{ cmdArgs?: string[], cliToken?: string|null }} opts
|
|
263
|
+
* @returns {boolean}
|
|
264
|
+
*/
|
|
265
|
+
export function shouldUseLocalSchedule({ cmdArgs = [], cliToken } = {}) {
|
|
266
|
+
return cmdArgs.includes('--local') || !cliToken;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Extracts the --time= and --save= flags local scheduling needs. Both are
|
|
271
|
+
* required by runScheduleLocal itself; this just does the string parsing.
|
|
272
|
+
*
|
|
273
|
+
* @param {string[]} cmdArgs
|
|
274
|
+
* @returns {{ time?: string, outputFile?: string }}
|
|
275
|
+
*/
|
|
276
|
+
export function parseLocalScheduleFlags(cmdArgs = []) {
|
|
277
|
+
const time = cmdArgs.find(a => a.startsWith('--time='))?.split('=')[1];
|
|
278
|
+
const outputFile = cmdArgs.find(a => a.startsWith('--save='))?.split('=')[1];
|
|
279
|
+
return { time, outputFile };
|
|
280
|
+
}
|
|
281
|
+
|
|
255
282
|
/**
|
|
256
283
|
* Set up a local-only scheduled triage (no Console auth, no cloud push).
|
|
257
284
|
* Writes a cron/LaunchAgent entry that runs `ticketlens triage --save=FILE`.
|