@temet/cli 0.3.0 → 0.3.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/assets/macos/README.md +8 -0
- package/assets/macos/Temet.app.zip +0 -0
- package/dist/audit.d.ts +3 -1
- package/dist/audit.js +214 -26
- package/dist/index.js +60 -12
- package/dist/lib/audit-tracking.js +13 -2
- package/dist/lib/cli-args.d.ts +1 -0
- package/dist/lib/cli-args.js +13 -3
- package/dist/lib/diagnostic-runner.d.ts +13 -0
- package/dist/lib/diagnostic-runner.js +95 -0
- package/dist/lib/editorial-taxonomy.d.ts +17 -0
- package/dist/lib/editorial-taxonomy.js +91 -0
- package/dist/lib/menubar-installer.d.ts +19 -0
- package/dist/lib/menubar-installer.js +99 -0
- package/dist/lib/menubar-state.d.ts +78 -0
- package/dist/lib/menubar-state.js +275 -0
- package/dist/lib/notifier.d.ts +2 -0
- package/dist/lib/notifier.js +11 -0
- package/dist/lib/path-resolver.d.ts +22 -1
- package/dist/lib/path-resolver.js +120 -24
- package/dist/lib/profile-report.d.ts +18 -0
- package/dist/lib/profile-report.js +148 -0
- package/dist/lib/report-writer.d.ts +9 -0
- package/dist/lib/report-writer.js +91 -0
- package/dist/lib/session-audit.d.ts +1 -0
- package/dist/lib/session-audit.js +2 -0
- package/dist/plan.d.ts +33 -0
- package/dist/plan.js +90 -0
- package/dist/traces.d.ts +41 -0
- package/dist/traces.js +119 -0
- package/package.json +3 -2
|
Binary file
|
package/dist/audit.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type AuditOptions = {
|
|
|
5
5
|
path: string;
|
|
6
6
|
track: boolean;
|
|
7
7
|
narrate: boolean;
|
|
8
|
+
openReport: boolean;
|
|
8
9
|
json: boolean;
|
|
9
10
|
quiet: boolean;
|
|
10
11
|
notify: boolean;
|
|
@@ -15,7 +16,7 @@ export type AuditOptions = {
|
|
|
15
16
|
token: string;
|
|
16
17
|
relayUrl: string;
|
|
17
18
|
};
|
|
18
|
-
export declare function buildAuditJsonOutput(result: Pick<AuditResult, "sessionCount" | "messageCount" | "toolCallCount" | "workflows">, competencies: CompetencyEntry[], bilan?: string, tracking?: Pick<TrackingResult, "changes" | "latestPath">): {
|
|
19
|
+
export declare function buildAuditJsonOutput(result: Pick<AuditResult, "sessionCount" | "messageCount" | "promptCount" | "toolCallCount" | "workflows">, competencies: CompetencyEntry[], bilan?: string, tracking?: Pick<TrackingResult, "changes" | "latestPath">): {
|
|
19
20
|
tracking?: {
|
|
20
21
|
latestPath: string;
|
|
21
22
|
changes: AuditChange[];
|
|
@@ -23,6 +24,7 @@ export declare function buildAuditJsonOutput(result: Pick<AuditResult, "sessionC
|
|
|
23
24
|
bilan?: string | undefined;
|
|
24
25
|
sessions: number;
|
|
25
26
|
messages: number;
|
|
27
|
+
prompts: number;
|
|
26
28
|
toolCalls: number;
|
|
27
29
|
competencies: CompetencyEntry[];
|
|
28
30
|
workflows: import("./lib/workflow-detector.js").DetectedWorkflow[];
|
package/dist/audit.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* temet audit — analyze your coding sessions, surface your real skills.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { basename, join } from "node:path";
|
|
5
7
|
import { clearLine, createInterface, cursorTo } from "node:readline";
|
|
6
8
|
import { findSessionFiles, runAudit, } from "./lib/session-audit.js";
|
|
9
|
+
import { resolveSessionPath, } from "./lib/path-resolver.js";
|
|
7
10
|
import { trackAuditSnapshot, } from "./lib/audit-tracking.js";
|
|
11
|
+
import { loadMenubarState, resolveLastReportPath, writeMenubarState, } from "./lib/menubar-state.js";
|
|
8
12
|
export function buildAuditJsonOutput(result, competencies, bilan, tracking) {
|
|
9
13
|
return {
|
|
10
14
|
sessions: result.sessionCount,
|
|
11
15
|
messages: result.messageCount,
|
|
16
|
+
prompts: result.promptCount,
|
|
12
17
|
toolCalls: result.toolCallCount,
|
|
13
18
|
competencies,
|
|
14
19
|
workflows: result.workflows,
|
|
@@ -271,10 +276,11 @@ function printPretty(result, competencies, bilan, tracking) {
|
|
|
271
276
|
console.log(`${dim("─".repeat(44), process.stdout)}`);
|
|
272
277
|
console.log(`${BOLD}Next${RESET}`);
|
|
273
278
|
if (!tracking) {
|
|
274
|
-
console.log(` ${DIM}temet audit --path <dir> --track${RESET} Save a baseline and track changes`);
|
|
279
|
+
console.log(` ${DIM}temet audit --path <dir> --track${RESET} Save a baseline and track changes over time`);
|
|
275
280
|
}
|
|
276
|
-
console.log(` ${DIM}temet
|
|
277
|
-
console.log(` ${DIM}temet
|
|
281
|
+
console.log(` ${DIM}temet traces${RESET} See the proof behind this reading`);
|
|
282
|
+
console.log(` ${DIM}temet plan${RESET} Turn this audit into a focused next-step plan`);
|
|
283
|
+
console.log(` ${DIM}temet audit --path <dir> --publish${RESET} Publish this audit to your Temet card`);
|
|
278
284
|
console.log("");
|
|
279
285
|
console.log(`${ok("Audit complete:", process.stdout)} ${formatCount(competencies.length)} skills surfaced from ${formatCount(result.sessionCount)} sessions`);
|
|
280
286
|
if (tracking) {
|
|
@@ -282,7 +288,58 @@ function printPretty(result, competencies, bilan, tracking) {
|
|
|
282
288
|
}
|
|
283
289
|
console.log("");
|
|
284
290
|
}
|
|
291
|
+
// ---------- Hook decline persistence ----------
|
|
292
|
+
const PREFS_PATH = join(homedir(), ".temet", "preferences.json");
|
|
293
|
+
function readPrefs() {
|
|
294
|
+
try {
|
|
295
|
+
return JSON.parse(readFileSync(PREFS_PATH, "utf8"));
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function writePrefs(prefs) {
|
|
302
|
+
mkdirSync(join(homedir(), ".temet"), { recursive: true });
|
|
303
|
+
writeFileSync(PREFS_PATH, JSON.stringify(prefs, null, "\t") + "\n", "utf8");
|
|
304
|
+
}
|
|
305
|
+
function hasDeclinedHook() {
|
|
306
|
+
return readPrefs().hookDeclined === true;
|
|
307
|
+
}
|
|
308
|
+
function saveHookDeclined() {
|
|
309
|
+
writePrefs({ ...readPrefs(), hookDeclined: true });
|
|
310
|
+
}
|
|
311
|
+
function isMacOs() {
|
|
312
|
+
return process.platform === "darwin";
|
|
313
|
+
}
|
|
314
|
+
async function readHookInstalledSafe() {
|
|
315
|
+
try {
|
|
316
|
+
const hookModule = await import("./lib/hook-installer.js");
|
|
317
|
+
return hookModule.isHookInstalled(hookModule.readSettings(hookModule.getSettingsPath()));
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
285
323
|
// ---------- Confirmation ----------
|
|
324
|
+
async function askYesNo(question, defaultYes, hintOverride) {
|
|
325
|
+
if (!process.stdin.isTTY)
|
|
326
|
+
return false;
|
|
327
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
328
|
+
const hint = hintOverride ??
|
|
329
|
+
(defaultYes
|
|
330
|
+
? "Press Enter to continue, or type no to skip:"
|
|
331
|
+
: "Type yes to continue, or press Enter to cancel:");
|
|
332
|
+
return new Promise((resolve) => {
|
|
333
|
+
rl.question(`\n${BOLD}${question}${RESET}\n${dim(hint, process.stderr)} `, (answer) => {
|
|
334
|
+
rl.close();
|
|
335
|
+
const trimmed = answer.trim().toLowerCase();
|
|
336
|
+
if (trimmed === "")
|
|
337
|
+
resolve(defaultYes);
|
|
338
|
+
else
|
|
339
|
+
resolve(trimmed === "y" || trimmed === "yes");
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
286
343
|
async function confirmPublish(competencyCount, address) {
|
|
287
344
|
// Non-interactive (piped stdin) → refuse without --yes
|
|
288
345
|
if (!process.stdin.isTTY) {
|
|
@@ -291,9 +348,38 @@ async function confirmPublish(competencyCount, address) {
|
|
|
291
348
|
}
|
|
292
349
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
293
350
|
return new Promise((resolve) => {
|
|
294
|
-
rl.question(`\n${BOLD}Publish ${competencyCount} competencies to card ${address}?${RESET}
|
|
351
|
+
rl.question(`\n${BOLD}Publish ${competencyCount} competencies to card ${address}?${RESET}\n${dim("Type yes to publish, or press Enter to cancel:", process.stderr)} `, (answer) => {
|
|
295
352
|
rl.close();
|
|
296
|
-
|
|
353
|
+
const trimmed = answer.trim().toLowerCase();
|
|
354
|
+
resolve(trimmed === "y" || trimmed === "yes" || trimmed === "publish");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async function chooseSessionCandidate(candidates) {
|
|
359
|
+
if (!process.stdin.isTTY || candidates.length === 0)
|
|
360
|
+
return null;
|
|
361
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
362
|
+
process.stderr.write(`\n[temet] I found ${candidates.length} Claude Code projects with session history. Pick one to audit.\n\n`);
|
|
363
|
+
candidates.slice(0, 9).forEach((candidate, index) => {
|
|
364
|
+
process.stderr.write(` ${index + 1}. ${candidate.label}${index === 0 ? " (most recent)" : ""}\n`);
|
|
365
|
+
});
|
|
366
|
+
process.stderr.write("\n");
|
|
367
|
+
return new Promise((resolve) => {
|
|
368
|
+
rl.question("[temet] Choose 1-9, or press Enter for the most recent project: ", (answer) => {
|
|
369
|
+
rl.close();
|
|
370
|
+
const trimmed = answer.trim();
|
|
371
|
+
if (!trimmed) {
|
|
372
|
+
resolve(candidates[0] ?? null);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const index = Number.parseInt(trimmed, 10) - 1;
|
|
376
|
+
if (!Number.isFinite(index) ||
|
|
377
|
+
index < 0 ||
|
|
378
|
+
index >= candidates.length) {
|
|
379
|
+
resolve(candidates[0] ?? null);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
resolve(candidates[index] ?? null);
|
|
297
383
|
});
|
|
298
384
|
});
|
|
299
385
|
}
|
|
@@ -338,10 +424,45 @@ async function publishCompetencies(competencies, opts) {
|
|
|
338
424
|
}
|
|
339
425
|
// ---------- Main ----------
|
|
340
426
|
export async function runAuditCommand(opts) {
|
|
341
|
-
const
|
|
427
|
+
const totalSteps = 4 + (opts.narrate ? 1 : 0) + (opts.publish ? 1 : 0);
|
|
428
|
+
const commandStartedAt = Date.now();
|
|
429
|
+
let resolvedPath = opts.path;
|
|
430
|
+
if (!opts.quiet) {
|
|
431
|
+
const resolution = await withSpinner(1, totalSteps, "Finding your sessions", async () => resolveSessionPath(opts.path || undefined, process.env));
|
|
432
|
+
if (!resolution.path) {
|
|
433
|
+
console.error("[temet] No Claude Code sessions found here. Run this command from a project folder, or use --path <session-dir>.");
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
if (!opts.path &&
|
|
437
|
+
!opts.json &&
|
|
438
|
+
resolution.source === "recent" &&
|
|
439
|
+
resolution.candidates.length > 1) {
|
|
440
|
+
const selected = await chooseSessionCandidate(resolution.candidates);
|
|
441
|
+
resolvedPath = selected?.sessionDir ?? resolution.path;
|
|
442
|
+
const label = selected?.label ?? resolution.candidates[0]?.label;
|
|
443
|
+
if (label) {
|
|
444
|
+
process.stderr.write(`${dim(`[temet] using ${label}`, process.stderr)}\n`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
resolvedPath = resolution.path;
|
|
449
|
+
const label = resolution.candidates[0]?.label;
|
|
450
|
+
if (label && resolution.source !== "explicit") {
|
|
451
|
+
process.stderr.write(`${dim(`[temet] using ${label}`, process.stderr)}\n`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const resolution = resolveSessionPath(opts.path || undefined, process.env);
|
|
457
|
+
if (!resolution.path) {
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
resolvedPath = resolution.path;
|
|
461
|
+
}
|
|
462
|
+
const sessionFiles = findSessionFiles(resolvedPath);
|
|
342
463
|
if (sessionFiles.length === 0) {
|
|
343
464
|
if (!opts.quiet) {
|
|
344
|
-
console.error(`[temet] no .jsonl session files found in ${
|
|
465
|
+
console.error(`[temet] no .jsonl session files found in ${resolvedPath}`);
|
|
345
466
|
}
|
|
346
467
|
process.exit(1);
|
|
347
468
|
}
|
|
@@ -349,13 +470,11 @@ export async function runAuditCommand(opts) {
|
|
|
349
470
|
printBanner();
|
|
350
471
|
}
|
|
351
472
|
if (!opts.quiet) {
|
|
352
|
-
console.error(`[temet]
|
|
473
|
+
console.error(`[temet] reading ${sessionFiles.length} saved session file(s)...`);
|
|
353
474
|
}
|
|
354
|
-
const totalSteps = 3 + (opts.narrate ? 1 : 0) + (opts.publish ? 1 : 0);
|
|
355
475
|
let completedFiles = 0;
|
|
356
|
-
const commandStartedAt = Date.now();
|
|
357
476
|
if (!opts.quiet) {
|
|
358
|
-
writeStepProgress(
|
|
477
|
+
writeStepProgress(2, totalSteps, "Reading your sessions", 0, sessionFiles.length);
|
|
359
478
|
}
|
|
360
479
|
let scanDoneWritten = false;
|
|
361
480
|
const result = await runAudit(sessionFiles, (event) => {
|
|
@@ -363,19 +482,19 @@ export async function runAuditCommand(opts) {
|
|
|
363
482
|
return;
|
|
364
483
|
if (event.phase === "scan") {
|
|
365
484
|
completedFiles += 1;
|
|
366
|
-
writeStepProgress(
|
|
485
|
+
writeStepProgress(2, totalSteps, "Reading your sessions", completedFiles, sessionFiles.length, event.file ? basename(event.file) : undefined);
|
|
367
486
|
return;
|
|
368
487
|
}
|
|
369
488
|
if (!scanDoneWritten) {
|
|
370
489
|
scanDoneWritten = true;
|
|
371
|
-
writeStepDone(
|
|
490
|
+
writeStepDone(2, totalSteps, "Reading your sessions", `${sessionFiles.length} files`);
|
|
372
491
|
}
|
|
373
492
|
if (event.phase === "signals") {
|
|
374
|
-
writeStepDone(
|
|
493
|
+
writeStepDone(3, totalSteps, "Extracting signals", `${formatCount(event.current ?? 0)} signals`, event.elapsedMs);
|
|
375
494
|
return;
|
|
376
495
|
}
|
|
377
496
|
if (event.phase === "patterns") {
|
|
378
|
-
writeStepDone(
|
|
497
|
+
writeStepDone(4, totalSteps, "Finding repeated patterns", `${formatCount(event.current ?? 0)} workflows`, event.elapsedMs);
|
|
379
498
|
}
|
|
380
499
|
});
|
|
381
500
|
let { competencies } = result;
|
|
@@ -389,7 +508,7 @@ export async function runAuditCommand(opts) {
|
|
|
389
508
|
printWarningBox("--narrate requires model access. Set ANTHROPIC_API_KEY or CLAUDE_AUTH_TOKEN.");
|
|
390
509
|
}
|
|
391
510
|
else {
|
|
392
|
-
const narrated = await withSpinner(
|
|
511
|
+
const narrated = await withSpinner(5, totalSteps, "Writing your profile", async () => narratorModule.narrateCompetencies(competencies, result.combined, result.workflows, { model: opts.model || undefined }));
|
|
393
512
|
competencies = narrated.competencies;
|
|
394
513
|
bilan = narrated.bilan || undefined;
|
|
395
514
|
}
|
|
@@ -399,14 +518,32 @@ export async function runAuditCommand(opts) {
|
|
|
399
518
|
}
|
|
400
519
|
}
|
|
401
520
|
if (opts.track) {
|
|
402
|
-
tracking = await trackAuditSnapshot(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
521
|
+
tracking = await trackAuditSnapshot(resolvedPath, result, competencies);
|
|
522
|
+
}
|
|
523
|
+
const projectLabel = tracking?.current.projectLabel ?? basename(resolvedPath);
|
|
524
|
+
let reportPath = null;
|
|
525
|
+
if (opts.openReport && !opts.json) {
|
|
526
|
+
const reportModule = await import("./lib/report-writer.js");
|
|
527
|
+
const content = reportModule.buildAuditTextReport(result, competencies, bilan, tracking);
|
|
528
|
+
reportPath = await reportModule.saveAuditTextReport(projectLabel, content);
|
|
529
|
+
}
|
|
530
|
+
const previousMenubarState = await loadMenubarState();
|
|
531
|
+
const hookInstalled = (await readHookInstalledSafe()) || opts.track;
|
|
532
|
+
const menubarState = await writeMenubarState({
|
|
533
|
+
sourcePath: resolvedPath,
|
|
534
|
+
result,
|
|
535
|
+
competencies,
|
|
536
|
+
bilan,
|
|
537
|
+
tracking,
|
|
538
|
+
trackingInstalled: hookInstalled,
|
|
539
|
+
lastReportPath: reportPath ?? (await resolveLastReportPath(projectLabel)),
|
|
540
|
+
previousState: previousMenubarState,
|
|
541
|
+
});
|
|
542
|
+
if (opts.notify && opts.track && tracking && !tracking.skipped) {
|
|
543
|
+
const { formatProgressNotification, sendNotification } = await import("./lib/notifier.js");
|
|
544
|
+
const payload = formatProgressNotification(menubarState.progress);
|
|
545
|
+
if (payload) {
|
|
546
|
+
void sendNotification(payload).catch(() => { });
|
|
410
547
|
}
|
|
411
548
|
}
|
|
412
549
|
// Publish (runs even in quiet mode — it's a side-effect, not output)
|
|
@@ -443,7 +580,7 @@ export async function runAuditCommand(opts) {
|
|
|
443
580
|
}
|
|
444
581
|
}
|
|
445
582
|
}
|
|
446
|
-
// Quiet mode:
|
|
583
|
+
// Quiet mode: keep side-effects only
|
|
447
584
|
if (opts.quiet)
|
|
448
585
|
return;
|
|
449
586
|
// Output
|
|
@@ -454,6 +591,57 @@ export async function runAuditCommand(opts) {
|
|
|
454
591
|
else {
|
|
455
592
|
printPretty(result, competencies, bilan, tracking);
|
|
456
593
|
}
|
|
594
|
+
if (reportPath && !opts.json) {
|
|
595
|
+
const reportModule = await import("./lib/report-writer.js");
|
|
596
|
+
await reportModule.openReportFile(reportPath);
|
|
597
|
+
console.error(`${dim(`[temet] opened report: ${reportPath}`, process.stderr)}`);
|
|
598
|
+
}
|
|
599
|
+
// Post-audit onboarding
|
|
600
|
+
if (!opts.json && !opts.quiet && process.stdin.isTTY) {
|
|
601
|
+
try {
|
|
602
|
+
const hookModule = await import("./lib/hook-installer.js");
|
|
603
|
+
const settingsPath = hookModule.getSettingsPath();
|
|
604
|
+
const settings = hookModule.readSettings(settingsPath);
|
|
605
|
+
const hookAlreadyInstalled = hookModule.isHookInstalled(settings);
|
|
606
|
+
if (!isMacOs()) {
|
|
607
|
+
if (!opts.track && !hookAlreadyInstalled && !hasDeclinedHook()) {
|
|
608
|
+
const answer = await askYesNo("Keep tracking your skills after each Claude Code session?", true);
|
|
609
|
+
if (answer) {
|
|
610
|
+
const binary = hookModule.resolveTemetBinary();
|
|
611
|
+
if (binary) {
|
|
612
|
+
const updated = hookModule.installHook(settings, binary);
|
|
613
|
+
hookModule.writeSettings(settingsPath, updated);
|
|
614
|
+
console.error(`${ok("Tracking enabled", process.stderr)} ${dim("You'll get a notification when your skills meaningfully change.", process.stderr)}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
saveHookDeclined();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
const { isMenubarInstalled, installMenubar } = await import("./lib/menubar-installer.js");
|
|
624
|
+
if (!isMenubarInstalled()) {
|
|
625
|
+
const answer = await askYesNo("Show Temet in your menu bar?", true, hookAlreadyInstalled
|
|
626
|
+
? "Press Enter to install Temet, or type no to skip:"
|
|
627
|
+
: "Press Enter to install Temet and turn on automatic tracking, or type no to skip:");
|
|
628
|
+
if (answer) {
|
|
629
|
+
const result = await installMenubar({
|
|
630
|
+
ensureHook: !hookAlreadyInstalled,
|
|
631
|
+
});
|
|
632
|
+
const suffix = result.hookInstalled
|
|
633
|
+
? "Temet opened and automatic tracking is now enabled."
|
|
634
|
+
: "Temet opened.";
|
|
635
|
+
console.error(`${ok("Temet installed", process.stderr)} ${dim(suffix, process.stderr)}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
642
|
+
console.error(`${warn("Temet skipped", process.stderr)} ${dim(message, process.stderr)}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
457
645
|
if (!opts.json) {
|
|
458
646
|
console.error(`${ok("done", process.stderr)} ${dim(`in ${formatMs(Date.now() - commandStartedAt)}`, process.stderr)}`);
|
|
459
647
|
}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { buildAuditCliOptions, parseFlagBag, readOptionalString, } from "./lib/cli-args.js";
|
|
8
|
-
import { resolveSessionPath } from "./lib/path-resolver.js";
|
|
9
8
|
const execFileAsync = promisify(execFile);
|
|
10
9
|
const DEFAULT_RELAY_URL = "https://temet-relay.ramponneau.workers.dev/mcp";
|
|
11
10
|
const DEFAULT_SERVER_NAME = "temet";
|
|
@@ -16,14 +15,19 @@ const HELP = `Temet CLI — discover the skills you already demonstrate in AI wo
|
|
|
16
15
|
|
|
17
16
|
Commands:
|
|
18
17
|
\ttemet audit [--path <session-dir>] Analyze local sessions, surface skills and workflows
|
|
18
|
+
\ttemet traces [--path <session-dir>] Show the evidence behind the strongest signals
|
|
19
|
+
\ttemet plan [--path <session-dir>] Turn the current audit into an operating plan
|
|
19
20
|
\ttemet install-hook Auto-audit after every Claude Code session
|
|
20
21
|
\ttemet uninstall-hook Remove the SessionEnd hook
|
|
22
|
+
\ttemet install-menubar Install Temet in the macOS menu bar
|
|
23
|
+
\ttemet uninstall-menubar Remove Temet from the macOS menu bar
|
|
21
24
|
\ttemet connect --address <hex> --token <t> Connect your MCP client to your Temet card
|
|
22
25
|
\ttemet install-handler Register temet:// protocol handler
|
|
23
26
|
|
|
24
27
|
Audit options:
|
|
25
28
|
\t--path <dir> Directory containing .jsonl session files (auto-detected if omitted)
|
|
26
29
|
\t--track Save a local snapshot and compare against the previous audit
|
|
30
|
+
\t--open-report Explicitly save and open the text report (opened by default unless --json or --quiet)
|
|
27
31
|
\t--json Output structured JSON instead of terminal display
|
|
28
32
|
\t--quiet Suppress all output (for background hooks)
|
|
29
33
|
\t--notify Send an OS notification on skill changes (used with --track)
|
|
@@ -32,14 +36,20 @@ Audit options:
|
|
|
32
36
|
|
|
33
37
|
Advanced:
|
|
34
38
|
\t--narrate Enrich results with an LLM (requires model access: ANTHROPIC_API_KEY or CLAUDE_AUTH_TOKEN)
|
|
39
|
+
\t--no-narrate Disable automatic narration even if model access is available
|
|
35
40
|
\t--model <id> Model to use for narration (default: claude-haiku-4-5-20251001)
|
|
36
41
|
|
|
37
42
|
Examples:
|
|
38
43
|
\ttemet audit Auto-detect sessions from cwd
|
|
44
|
+
\ttemet audit Open the text report by default
|
|
45
|
+
\ttemet traces Inspect the proof behind the current reading
|
|
46
|
+
\ttemet plan Get the current operating plan
|
|
39
47
|
\ttemet audit --path ~/.claude/projects/my-project
|
|
40
48
|
\ttemet audit --path ~/.claude/projects/my-project --track
|
|
49
|
+
\ttemet audit --path ~/.claude/projects/my-project --open-report
|
|
41
50
|
\ttemet audit --path ~/.claude/projects/my-project --json
|
|
42
51
|
\ttemet install-hook Background audit on session end
|
|
52
|
+
\ttemet install-menubar Install and open Temet on macOS
|
|
43
53
|
`;
|
|
44
54
|
function printHelp(exitCode = 0) {
|
|
45
55
|
console.log(HELP);
|
|
@@ -144,19 +154,15 @@ function parseArgs(argv) {
|
|
|
144
154
|
const { flags, positionals } = parseFlagBag(rest);
|
|
145
155
|
const dryRun = Boolean(flags.get("dry-run"));
|
|
146
156
|
if (command === "audit") {
|
|
147
|
-
const options = buildAuditCliOptions(flags, process.env);
|
|
148
|
-
if (!options.path) {
|
|
149
|
-
// Auto-detect session path
|
|
150
|
-
const detected = resolveSessionPath(undefined, process.env);
|
|
151
|
-
if (!detected) {
|
|
152
|
-
console.error("[temet] could not auto-detect session directory. Use --path <session-dir>");
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
options.path = detected;
|
|
156
|
-
}
|
|
157
157
|
return {
|
|
158
158
|
command,
|
|
159
|
-
options,
|
|
159
|
+
options: buildAuditCliOptions(flags, process.env),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (command === "traces" || command === "plan") {
|
|
163
|
+
return {
|
|
164
|
+
command,
|
|
165
|
+
options: buildAuditCliOptions(flags, process.env),
|
|
160
166
|
};
|
|
161
167
|
}
|
|
162
168
|
if (command === "connect") {
|
|
@@ -177,6 +183,9 @@ function parseArgs(argv) {
|
|
|
177
183
|
if (command === "install-hook" || command === "uninstall-hook") {
|
|
178
184
|
return { command, dryRun };
|
|
179
185
|
}
|
|
186
|
+
if (command === "install-menubar" || command === "uninstall-menubar") {
|
|
187
|
+
return { command, dryRun };
|
|
188
|
+
}
|
|
180
189
|
console.error(`Unknown command: ${command}`);
|
|
181
190
|
printHelp(1);
|
|
182
191
|
}
|
|
@@ -502,6 +511,16 @@ async function run() {
|
|
|
502
511
|
await runAuditCommand(parsed.options);
|
|
503
512
|
return;
|
|
504
513
|
}
|
|
514
|
+
if (parsed.command === "traces") {
|
|
515
|
+
const { runTracesCommand } = await import("./traces.js");
|
|
516
|
+
await runTracesCommand(parsed.options);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (parsed.command === "plan") {
|
|
520
|
+
const { runPlanCommand } = await import("./plan.js");
|
|
521
|
+
await runPlanCommand(parsed.options);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
505
524
|
if (parsed.command === "connect" || parsed.command === "connect-url") {
|
|
506
525
|
await runWriteFlow(parsed.command, parsed.options);
|
|
507
526
|
return;
|
|
@@ -548,6 +567,35 @@ async function run() {
|
|
|
548
567
|
console.log("[temet] SessionEnd hook removed.");
|
|
549
568
|
return;
|
|
550
569
|
}
|
|
570
|
+
if (parsed.command === "install-menubar") {
|
|
571
|
+
const { installMenubar } = await import("./lib/menubar-installer.js");
|
|
572
|
+
const result = await installMenubar({
|
|
573
|
+
dryRun: parsed.dryRun,
|
|
574
|
+
ensureHook: true,
|
|
575
|
+
});
|
|
576
|
+
if (parsed.dryRun) {
|
|
577
|
+
console.log(`[temet] dry-run menubar install path: ${result.appPath}`);
|
|
578
|
+
console.log(`[temet] dry-run menubar config path: ${result.configPath}`);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
console.log(`[temet] Temet installed: ${result.appPath}`);
|
|
582
|
+
if (result.hookInstalled) {
|
|
583
|
+
console.log("[temet] Tracking enabled via SessionEnd hook.");
|
|
584
|
+
}
|
|
585
|
+
console.log("[temet] Temet opened.");
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (parsed.command === "uninstall-menubar") {
|
|
589
|
+
const { uninstallMenubar } = await import("./lib/menubar-installer.js");
|
|
590
|
+
const result = await uninstallMenubar({ dryRun: parsed.dryRun });
|
|
591
|
+
if (parsed.dryRun) {
|
|
592
|
+
console.log(`[temet] dry-run menubar uninstall path: ${result.appPath}`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
console.log("[temet] Temet removed.");
|
|
596
|
+
console.log("[temet] Tracking remains active — run temet uninstall-hook to disable it.");
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
551
599
|
if (parsed.command === "install-handler") {
|
|
552
600
|
await installProtocolHandler(parsed.dryRun);
|
|
553
601
|
console.log("[temet] next: click a Quick connect button in Temet.");
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { humanizeSessionDir } from "./path-resolver.js";
|
|
5
6
|
const PROFICIENCY_ORDER = {
|
|
6
7
|
novice: 0,
|
|
7
8
|
advanced_beginner: 1,
|
|
@@ -26,8 +27,18 @@ export function buildProjectKey(sourcePath) {
|
|
|
26
27
|
}
|
|
27
28
|
function buildProjectLabel(sourcePath) {
|
|
28
29
|
const resolved = path.resolve(sourcePath);
|
|
29
|
-
const
|
|
30
|
-
|
|
30
|
+
const slug = path.basename(resolved);
|
|
31
|
+
if (slug.startsWith("-")) {
|
|
32
|
+
const segments = slug.slice(1).split("-").filter(Boolean);
|
|
33
|
+
if (segments.length >= 2) {
|
|
34
|
+
return `${segments[segments.length - 2]}-${segments[segments.length - 1]}`;
|
|
35
|
+
}
|
|
36
|
+
if (segments.length === 1) {
|
|
37
|
+
return segments[0];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const humanized = humanizeSessionDir(resolved);
|
|
41
|
+
return path.basename(humanized) || path.basename(resolved) || resolved;
|
|
31
42
|
}
|
|
32
43
|
export function buildAuditSnapshot(sourcePath, result, competencies) {
|
|
33
44
|
return {
|
package/dist/lib/cli-args.d.ts
CHANGED
package/dist/lib/cli-args.js
CHANGED
|
@@ -14,6 +14,8 @@ export function parseFlagBag(args) {
|
|
|
14
14
|
if (arg === "--dry-run" ||
|
|
15
15
|
arg === "--track" ||
|
|
16
16
|
arg === "--narrate" ||
|
|
17
|
+
arg === "--no-narrate" ||
|
|
18
|
+
arg === "--open-report" ||
|
|
17
19
|
arg === "--json" ||
|
|
18
20
|
arg === "--quiet" ||
|
|
19
21
|
arg === "--notify" ||
|
|
@@ -41,12 +43,20 @@ export function readOptionalString(flags, key) {
|
|
|
41
43
|
}
|
|
42
44
|
export function buildAuditCliOptions(flags, env) {
|
|
43
45
|
const pathVal = readOptionalString(flags, "path") ?? "";
|
|
46
|
+
const json = Boolean(flags.get("json"));
|
|
47
|
+
const quiet = Boolean(flags.get("quiet"));
|
|
48
|
+
const explicitOpenReport = Boolean(flags.get("open-report"));
|
|
44
49
|
return {
|
|
45
50
|
path: pathVal,
|
|
46
51
|
track: Boolean(flags.get("track")),
|
|
47
|
-
narrate: Boolean(flags.get("narrate"))
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
narrate: !Boolean(flags.get("no-narrate")) &&
|
|
53
|
+
(Boolean(flags.get("narrate")) ||
|
|
54
|
+
(!json &&
|
|
55
|
+
!quiet &&
|
|
56
|
+
Boolean(env.CLAUDE_AUTH_TOKEN || env.ANTHROPIC_API_KEY))),
|
|
57
|
+
openReport: explicitOpenReport || (!json && !quiet),
|
|
58
|
+
json,
|
|
59
|
+
quiet,
|
|
50
60
|
notify: Boolean(flags.get("notify")),
|
|
51
61
|
publish: Boolean(flags.get("publish")),
|
|
52
62
|
yes: Boolean(flags.get("yes")),
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuditCliOptions } from "./cli-args.js";
|
|
2
|
+
import { type TrackingResult } from "./audit-tracking.js";
|
|
3
|
+
import { type AuditResult } from "./session-audit.js";
|
|
4
|
+
import type { CompetencyEntry } from "./types.js";
|
|
5
|
+
export type ResolvedDiagnostics = {
|
|
6
|
+
resolvedPath: string;
|
|
7
|
+
result: AuditResult;
|
|
8
|
+
competencies: CompetencyEntry[];
|
|
9
|
+
bilan?: string;
|
|
10
|
+
tracking?: TrackingResult;
|
|
11
|
+
};
|
|
12
|
+
export declare function resolveDiagnostics(opts: AuditCliOptions): Promise<ResolvedDiagnostics>;
|
|
13
|
+
export declare function defaultProjectLabel(resolvedPath: string): string;
|