@workscribe/cli 1.7.2 → 1.9.0
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 +2 -0
- package/dist/index.js +283 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/capture/index.ts
|
|
30
|
-
var
|
|
30
|
+
var import_fs7 = __toESM(require("fs"));
|
|
31
31
|
|
|
32
32
|
// src/db/index.ts
|
|
33
33
|
var import_node_sqlite = require("node:sqlite");
|
|
@@ -121,7 +121,7 @@ function applyMigrations(db) {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
const applied = new Set(
|
|
124
|
-
db.prepare("SELECT version FROM schema_migrations").all().map((
|
|
124
|
+
db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version)
|
|
125
125
|
);
|
|
126
126
|
for (const migration of MIGRATIONS) {
|
|
127
127
|
if (applied.has(migration.version)) continue;
|
|
@@ -208,7 +208,7 @@ function updateSessionSummary(sessionId, summary) {
|
|
|
208
208
|
function getLastEventIds(n) {
|
|
209
209
|
const db = getDb();
|
|
210
210
|
const rows = db.prepare("SELECT id FROM events ORDER BY id DESC LIMIT $n").all({ n });
|
|
211
|
-
return rows.map((
|
|
211
|
+
return rows.map((row) => row.id);
|
|
212
212
|
}
|
|
213
213
|
function getEventsByIds(ids) {
|
|
214
214
|
if (ids.length === 0) return [];
|
|
@@ -345,6 +345,28 @@ function getCommitsSince(sha, cwd) {
|
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
// src/capture/aider.ts
|
|
349
|
+
var import_fs3 = __toESM(require("fs"));
|
|
350
|
+
var import_path3 = __toESM(require("path"));
|
|
351
|
+
var HISTORY_FILE = ".aider.chat.history.md";
|
|
352
|
+
var PROMPT_MAX_LENGTH = 200;
|
|
353
|
+
function readLastAiderPrompt(cwd) {
|
|
354
|
+
try {
|
|
355
|
+
const histPath = import_path3.default.join(cwd, HISTORY_FILE);
|
|
356
|
+
const content = import_fs3.default.readFileSync(histPath, "utf-8");
|
|
357
|
+
const matches = [...content.matchAll(/^> (.+)$/gm)];
|
|
358
|
+
if (!matches.length) return null;
|
|
359
|
+
return matches[matches.length - 1][1].trim().slice(0, PROMPT_MAX_LENGTH);
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/config/registry.ts
|
|
366
|
+
var import_fs5 = __toESM(require("fs"));
|
|
367
|
+
var import_os2 = __toESM(require("os"));
|
|
368
|
+
var import_path4 = __toESM(require("path"));
|
|
369
|
+
|
|
348
370
|
// src/config/ai-tools.json
|
|
349
371
|
var ai_tools_default = [
|
|
350
372
|
{
|
|
@@ -405,6 +427,54 @@ var ai_tools_default = [
|
|
|
405
427
|
}
|
|
406
428
|
];
|
|
407
429
|
|
|
430
|
+
// src/utils/logger.ts
|
|
431
|
+
var import_chalk = __toESM(require("chalk"));
|
|
432
|
+
var import_fs4 = __toESM(require("fs"));
|
|
433
|
+
var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
|
|
434
|
+
var log = {
|
|
435
|
+
info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
|
|
436
|
+
success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
|
|
437
|
+
warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
|
|
438
|
+
error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
|
|
439
|
+
plain: (msg) => console.log(msg)
|
|
440
|
+
};
|
|
441
|
+
function debugLog(context, error) {
|
|
442
|
+
if (!isDebug) return;
|
|
443
|
+
const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
|
|
444
|
+
`;
|
|
445
|
+
try {
|
|
446
|
+
import_fs4.default.appendFileSync(DEBUG_LOG_PATH, entry, { mode: 384 });
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/config/registry.ts
|
|
452
|
+
var USER_REGISTRY_PATH = import_path4.default.join(import_os2.default.homedir(), ".workscribe", "ai-tools.json");
|
|
453
|
+
function loadAIToolRegistry() {
|
|
454
|
+
let userEntries = [];
|
|
455
|
+
try {
|
|
456
|
+
if (import_fs5.default.existsSync(USER_REGISTRY_PATH)) {
|
|
457
|
+
const raw = import_fs5.default.readFileSync(USER_REGISTRY_PATH, "utf-8");
|
|
458
|
+
const parsed = JSON.parse(raw);
|
|
459
|
+
if (Array.isArray(parsed)) {
|
|
460
|
+
userEntries = parsed.filter((entry) => typeof entry.tool === "string" && typeof entry.pattern === "string").map((entry) => ({
|
|
461
|
+
tool: entry.tool,
|
|
462
|
+
displayName: entry.displayName ?? entry.tool,
|
|
463
|
+
pattern: entry.pattern,
|
|
464
|
+
hook: entry.hook ?? null,
|
|
465
|
+
contextSource: entry.contextSource ?? null
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
} catch {
|
|
470
|
+
debugLog("registry", "Failed to load user AI tool registry \u2014 using built-ins only");
|
|
471
|
+
}
|
|
472
|
+
if (userEntries.length === 0) return ai_tools_default;
|
|
473
|
+
const userToolSlugs = new Set(userEntries.map((entry) => entry.tool));
|
|
474
|
+
const filteredBuiltins = ai_tools_default.filter((entry) => !userToolSlugs.has(entry.tool));
|
|
475
|
+
return [...filteredBuiltins, ...userEntries];
|
|
476
|
+
}
|
|
477
|
+
|
|
408
478
|
// src/normalize/patterns.ts
|
|
409
479
|
function makeRule(pattern, category, extract = () => ({})) {
|
|
410
480
|
return {
|
|
@@ -418,7 +488,7 @@ function makeRule(pattern, category, extract = () => ({})) {
|
|
|
418
488
|
function extractPackages(cmd, afterKeyword) {
|
|
419
489
|
const idx = cmd.indexOf(afterKeyword);
|
|
420
490
|
if (idx === -1) return [];
|
|
421
|
-
return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((
|
|
491
|
+
return cmd.slice(idx + afterKeyword.length).trim().split(/\s+/).filter((part) => part && !part.startsWith("-"));
|
|
422
492
|
}
|
|
423
493
|
var RULES = [
|
|
424
494
|
// ─── Git operations ───────────────────────────────────────────────────────
|
|
@@ -436,7 +506,7 @@ var RULES = [
|
|
|
436
506
|
}),
|
|
437
507
|
makeRule(/^git\s+(checkout|switch)\b/, "branch_switch", (_m, cmd) => {
|
|
438
508
|
const parts = cmd.split(/\s+/);
|
|
439
|
-
const branch = parts.find((
|
|
509
|
+
const branch = parts.find((part) => !part.startsWith("-") && part !== "git" && part !== parts[1]);
|
|
440
510
|
return { branch: branch ?? null };
|
|
441
511
|
}),
|
|
442
512
|
makeRule(/^git\s+(merge|rebase)\b/, "code_merge", (_m, cmd) => {
|
|
@@ -502,7 +572,7 @@ var RULES = [
|
|
|
502
572
|
makeRule(/^yarn\s+test\b/, "test_run", () => ({ tool: "yarn" })),
|
|
503
573
|
makeRule(/^pnpm\s+test\b/, "test_run", () => ({ tool: "pnpm" })),
|
|
504
574
|
makeRule(/^bun\s+test\b/, "test_run", () => ({ tool: "bun" })),
|
|
505
|
-
makeRule(/^(jest|vitest)\b/, "test_run", (
|
|
575
|
+
makeRule(/^(jest|vitest)\b/, "test_run", (match) => ({ tool: match[1] })),
|
|
506
576
|
makeRule(/^pytest\b/, "test_run", () => ({ tool: "pytest" })),
|
|
507
577
|
makeRule(/^go\s+test\b/, "test_run", () => ({ tool: "go" })),
|
|
508
578
|
makeRule(/^cargo\s+test\b/, "test_run", () => ({ tool: "cargo" })),
|
|
@@ -510,19 +580,19 @@ var RULES = [
|
|
|
510
580
|
makeRule(/^(php\s+)?phpunit\b/, "test_run", () => ({ tool: "phpunit" })),
|
|
511
581
|
// ─── Build ────────────────────────────────────────────────────────────────
|
|
512
582
|
makeRule(/^npm\s+run\s+build\b/, "build", () => ({ tool: "npm" })),
|
|
513
|
-
makeRule(/^(yarn|pnpm)\s+build\b/, "build", (
|
|
583
|
+
makeRule(/^(yarn|pnpm)\s+build\b/, "build", (match) => ({ tool: match[1] })),
|
|
514
584
|
makeRule(/^bun\s+run\s+build\b/, "build", () => ({ tool: "bun" })),
|
|
515
|
-
makeRule(/^(make|gmake)\b/, "build", (
|
|
585
|
+
makeRule(/^(make|gmake)\b/, "build", (match) => ({ tool: match[1] })),
|
|
516
586
|
makeRule(/^cargo\s+build\b/, "build", () => ({ tool: "cargo" })),
|
|
517
587
|
makeRule(/^go\s+build\b/, "build", () => ({ tool: "go" })),
|
|
518
588
|
makeRule(/^docker\s+build\b/, "build", () => ({ tool: "docker" })),
|
|
519
|
-
makeRule(/^(mvn|gradle)\b/, "build", (
|
|
589
|
+
makeRule(/^(mvn|gradle)\b/, "build", (match) => ({ tool: match[1] })),
|
|
520
590
|
// ─── Dev server ───────────────────────────────────────────────────────────
|
|
521
591
|
makeRule(/^npm\s+run\s+(dev|start|serve)\b/, "server_run", (_m, cmd) => ({
|
|
522
592
|
tool: "npm",
|
|
523
593
|
script: cmd.split(/\s+/)[2]
|
|
524
594
|
})),
|
|
525
|
-
makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "server_run", (
|
|
595
|
+
makeRule(/^(yarn|pnpm)\s+(dev|start)\b/, "server_run", (match) => ({ tool: match[1], script: match[2] })),
|
|
526
596
|
makeRule(/^bun\s+run\s+(dev|start|serve)\b/, "server_run", (_m, cmd) => ({
|
|
527
597
|
tool: "bun",
|
|
528
598
|
script: cmd.split(/\s+/)[2]
|
|
@@ -564,7 +634,7 @@ var RULES = [
|
|
|
564
634
|
tool: cmd.split(/\s+/)[1]
|
|
565
635
|
})),
|
|
566
636
|
// ─── Scripts ──────────────────────────────────────────────────────────────
|
|
567
|
-
makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "script_run", (
|
|
637
|
+
makeRule(/^(bash|sh|zsh|fish)\s+\S+/, "script_run", (match) => ({ shell: match[1] })),
|
|
568
638
|
makeRule(/^node\s+\S+\.(?:js|mjs|cjs)\b/, "script_run", () => ({ runtime: "node" })),
|
|
569
639
|
makeRule(/^python3?\s+\S+\.py\b/, "script_run", () => ({ runtime: "python" })),
|
|
570
640
|
// ─── Environment ──────────────────────────────────────────────────────────
|
|
@@ -583,20 +653,27 @@ var RULES = [
|
|
|
583
653
|
script: cmd.split(/\s+/)[2]
|
|
584
654
|
})),
|
|
585
655
|
// ─── Code search ─────────────────────────────────────────────────────────
|
|
586
|
-
makeRule(/^(grep|egrep|fgrep)\b/, "code_search", (
|
|
587
|
-
makeRule(/^(rg|ag|ack|ripgrep)\b/, "code_search", (
|
|
656
|
+
makeRule(/^(grep|egrep|fgrep)\b/, "code_search", (match) => ({ tool: match[1] })),
|
|
657
|
+
makeRule(/^(rg|ag|ack|ripgrep)\b/, "code_search", (match) => ({ tool: match[1] })),
|
|
588
658
|
makeRule(/^find\s/, "code_search", () => ({ tool: "find" })),
|
|
589
659
|
// ─── Code read ────────────────────────────────────────────────────────────
|
|
590
|
-
makeRule(/^(cat|bat|less|more)\s+\S+/, "code_read", (
|
|
591
|
-
makeRule(/^(head|tail)\b/, "code_read", (
|
|
660
|
+
makeRule(/^(cat|bat|less|more)\s+\S+/, "code_read", (match) => ({ tool: match[1] })),
|
|
661
|
+
makeRule(/^(head|tail)\b/, "code_read", (match) => ({ tool: match[1] })),
|
|
592
662
|
// ─── File operations ──────────────────────────────────────────────────────
|
|
593
|
-
makeRule(/^(cp|rsync)\b/, "file_ops", (
|
|
663
|
+
makeRule(/^(cp|rsync)\b/, "file_ops", (match) => ({ tool: match[1] })),
|
|
594
664
|
makeRule(/^(mv)\b/, "file_ops", () => ({ tool: "mv" })),
|
|
595
|
-
makeRule(/^(rm|rmdir)\b/, "file_ops", (
|
|
596
|
-
makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (
|
|
597
|
-
makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (
|
|
665
|
+
makeRule(/^(rm|rmdir)\b/, "file_ops", (match) => ({ tool: match[1] })),
|
|
666
|
+
makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (match) => ({ tool: match[1] })),
|
|
667
|
+
makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (match) => ({ tool: match[1] })),
|
|
598
668
|
// ─── AI terminal tools (registry-driven) ─────────────────────────────────
|
|
599
|
-
...
|
|
669
|
+
...loadAIToolRegistry().flatMap((entry) => {
|
|
670
|
+
try {
|
|
671
|
+
return [makeRule(new RegExp(entry.pattern), "ai_session", () => ({ tool: entry.tool }))];
|
|
672
|
+
} catch {
|
|
673
|
+
debugLog("registry", `Skipping tool "${entry.tool}" \u2014 invalid pattern: ${entry.pattern}`);
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
})
|
|
600
677
|
];
|
|
601
678
|
function normalizeCommand(command) {
|
|
602
679
|
let trimmed = command.trim().replace(/\s+/g, " ");
|
|
@@ -609,7 +686,7 @@ function normalizeCommand(command) {
|
|
|
609
686
|
}
|
|
610
687
|
|
|
611
688
|
// src/config/index.ts
|
|
612
|
-
var
|
|
689
|
+
var import_fs6 = __toESM(require("fs"));
|
|
613
690
|
var DEFAULTS = {
|
|
614
691
|
ai: {
|
|
615
692
|
provider: "",
|
|
@@ -621,7 +698,9 @@ var DEFAULTS = {
|
|
|
621
698
|
idleTimeout: 30
|
|
622
699
|
},
|
|
623
700
|
capture: {
|
|
624
|
-
ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"]
|
|
701
|
+
ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
|
|
702
|
+
aiTools: true,
|
|
703
|
+
includeAiderPrompt: true
|
|
625
704
|
},
|
|
626
705
|
redact: {
|
|
627
706
|
extra: []
|
|
@@ -631,9 +710,9 @@ var DEFAULTS = {
|
|
|
631
710
|
}
|
|
632
711
|
};
|
|
633
712
|
function readConfig() {
|
|
634
|
-
if (!
|
|
713
|
+
if (!import_fs6.default.existsSync(CONFIG_PATH)) return { ...DEFAULTS };
|
|
635
714
|
try {
|
|
636
|
-
const raw =
|
|
715
|
+
const raw = import_fs6.default.readFileSync(CONFIG_PATH, "utf-8");
|
|
637
716
|
const parsed = JSON.parse(raw);
|
|
638
717
|
return deepMerge(DEFAULTS, parsed);
|
|
639
718
|
} catch {
|
|
@@ -641,9 +720,9 @@ function readConfig() {
|
|
|
641
720
|
}
|
|
642
721
|
}
|
|
643
722
|
function writeConfig(config) {
|
|
644
|
-
|
|
723
|
+
import_fs6.default.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
645
724
|
try {
|
|
646
|
-
|
|
725
|
+
import_fs6.default.chmodSync(CONFIG_PATH, 384);
|
|
647
726
|
} catch {
|
|
648
727
|
}
|
|
649
728
|
}
|
|
@@ -710,7 +789,7 @@ function isDuplicate(command, cwd, lastEvent, dedupWindowMs = 5e3, now = Date.no
|
|
|
710
789
|
|
|
711
790
|
// src/capture/index.ts
|
|
712
791
|
function captureCommand(opts) {
|
|
713
|
-
if (
|
|
792
|
+
if (import_fs7.default.existsSync(PAUSE_FLAG_PATH)) return;
|
|
714
793
|
const config = readConfig();
|
|
715
794
|
const { command, cwd, exitCode, durationMs } = opts;
|
|
716
795
|
if (shouldFilter(command, config.capture.ignore)) return;
|
|
@@ -720,10 +799,25 @@ function captureCommand(opts) {
|
|
|
720
799
|
const nameOverride = config.projects?.[cwd] ?? null;
|
|
721
800
|
const { repoName, branch } = getGitContext(cwd, nameOverride);
|
|
722
801
|
const { category, metadata } = normalizeCommand(safeCommand);
|
|
802
|
+
if (category === "ai_session" && config.capture.aiTools === false) return;
|
|
803
|
+
if (category === "other") {
|
|
804
|
+
const firstToken = safeCommand.trim().split(/\s+/)[0];
|
|
805
|
+
if (firstToken) metadata.tool = firstToken;
|
|
806
|
+
metadata.hint = safeCommand.trim().slice(0, 120);
|
|
807
|
+
}
|
|
723
808
|
if (opts.source) metadata.source = opts.source;
|
|
724
|
-
if (category === "ai_session"
|
|
725
|
-
|
|
726
|
-
|
|
809
|
+
if (category === "ai_session") {
|
|
810
|
+
if (opts.gitBefore) {
|
|
811
|
+
const commits = getCommitsSince(opts.gitBefore, cwd);
|
|
812
|
+
if (commits.length > 0) metadata.commits = commits;
|
|
813
|
+
}
|
|
814
|
+
if (config.capture.includeAiderPrompt !== false) {
|
|
815
|
+
const toolEntry = loadAIToolRegistry().find((entry) => entry.tool === metadata.tool);
|
|
816
|
+
if (toolEntry?.contextSource === "history-file") {
|
|
817
|
+
const prompt = readLastAiderPrompt(cwd);
|
|
818
|
+
if (prompt) metadata.prompt = prompt;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
727
821
|
}
|
|
728
822
|
insertEvent({
|
|
729
823
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -738,27 +832,6 @@ function captureCommand(opts) {
|
|
|
738
832
|
});
|
|
739
833
|
}
|
|
740
834
|
|
|
741
|
-
// src/utils/logger.ts
|
|
742
|
-
var import_chalk = __toESM(require("chalk"));
|
|
743
|
-
var import_fs5 = __toESM(require("fs"));
|
|
744
|
-
var isDebug = process.env.WORKSCRIBE_DEBUG === "1";
|
|
745
|
-
var log = {
|
|
746
|
-
info: (msg) => console.log(import_chalk.default.cyan("\u2192"), msg),
|
|
747
|
-
success: (msg) => console.log(import_chalk.default.green("\u2713"), msg),
|
|
748
|
-
warn: (msg) => console.log(import_chalk.default.yellow("\u26A0"), msg),
|
|
749
|
-
error: (msg) => console.error(import_chalk.default.red("\u2717"), msg),
|
|
750
|
-
plain: (msg) => console.log(msg)
|
|
751
|
-
};
|
|
752
|
-
function debugLog(context, error) {
|
|
753
|
-
if (!isDebug) return;
|
|
754
|
-
const entry = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${context}] ${error instanceof Error ? error.stack : String(error)}
|
|
755
|
-
`;
|
|
756
|
-
try {
|
|
757
|
-
import_fs5.default.appendFileSync(DEBUG_LOG_PATH, entry, { mode: 384 });
|
|
758
|
-
} catch {
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
835
|
// src/cli/capture.ts
|
|
763
836
|
function registerCapture(program2) {
|
|
764
837
|
program2.command("_capture").description("Internal: capture a shell command event (called by shell hook)").option("--cmd <command>", "command that was executed").option("--cwd <path>", "working directory").option("--exit <code>", "exit code", parseInt).option("--duration <ms>", "duration in milliseconds", parseInt).option("--git-before <sha>", "git HEAD SHA before this command ran").action((opts) => {
|
|
@@ -796,6 +869,7 @@ function registerHook(program2) {
|
|
|
796
869
|
});
|
|
797
870
|
process.stdin.on("end", () => {
|
|
798
871
|
try {
|
|
872
|
+
if (readConfig().capture.aiTools === false) return;
|
|
799
873
|
const parsed = parseHookPayload(raw);
|
|
800
874
|
if (!parsed) return;
|
|
801
875
|
captureCommand({
|
|
@@ -813,21 +887,21 @@ function registerHook(program2) {
|
|
|
813
887
|
}
|
|
814
888
|
|
|
815
889
|
// src/cli/init.ts
|
|
816
|
-
var
|
|
817
|
-
var
|
|
818
|
-
var
|
|
890
|
+
var import_fs10 = __toESM(require("fs"));
|
|
891
|
+
var import_path7 = __toESM(require("path"));
|
|
892
|
+
var import_os4 = __toESM(require("os"));
|
|
819
893
|
var import_prompts = __toESM(require("prompts"));
|
|
820
894
|
var import_chalk2 = __toESM(require("chalk"));
|
|
821
895
|
|
|
822
896
|
// src/capture/shell-hook.ts
|
|
823
|
-
var
|
|
824
|
-
var
|
|
897
|
+
var import_fs8 = __toESM(require("fs"));
|
|
898
|
+
var import_path5 = __toESM(require("path"));
|
|
825
899
|
var HOOK_START = "# workscribe:hook:start";
|
|
826
900
|
var HOOK_END = "# workscribe:hook:end";
|
|
827
901
|
var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
|
|
828
902
|
function getInstalledHookBlock(rcFile) {
|
|
829
|
-
if (!
|
|
830
|
-
const content =
|
|
903
|
+
if (!import_fs8.default.existsSync(rcFile)) return null;
|
|
904
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
831
905
|
const startIdx = content.indexOf(HOOK_START);
|
|
832
906
|
const endIdx = content.indexOf(HOOK_END);
|
|
833
907
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -843,39 +917,39 @@ function isHookOutdated(rcFile, hookContent) {
|
|
|
843
917
|
}
|
|
844
918
|
function installHook(rcFile, hookContent) {
|
|
845
919
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
846
|
-
|
|
847
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
848
|
-
|
|
920
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
921
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
922
|
+
import_fs8.default.appendFileSync(rcFile, `
|
|
849
923
|
${hookContent}
|
|
850
924
|
`);
|
|
851
925
|
}
|
|
852
926
|
function updateHook(rcFile, hookContent) {
|
|
853
927
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
854
|
-
|
|
855
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
856
|
-
const content =
|
|
928
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
929
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
930
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
857
931
|
const startIdx = content.indexOf(HOOK_START);
|
|
858
932
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
859
|
-
|
|
933
|
+
import_fs8.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
|
|
860
934
|
}
|
|
861
935
|
function removeHook(rcFile) {
|
|
862
936
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
863
|
-
|
|
864
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
865
|
-
const content =
|
|
937
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
938
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
939
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
866
940
|
const startIdx = content.indexOf(HOOK_START);
|
|
867
941
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
868
942
|
const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
|
|
869
943
|
const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
|
|
870
944
|
const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
|
|
871
945
|
const after = content.slice(endIdx).replace(/^\n/, "");
|
|
872
|
-
|
|
946
|
+
import_fs8.default.writeFileSync(rcFile, before + after);
|
|
873
947
|
}
|
|
874
948
|
|
|
875
949
|
// src/capture/hooks/claude-code.ts
|
|
876
|
-
var
|
|
877
|
-
var
|
|
878
|
-
var
|
|
950
|
+
var import_fs9 = __toESM(require("fs"));
|
|
951
|
+
var import_path6 = __toESM(require("path"));
|
|
952
|
+
var import_os3 = __toESM(require("os"));
|
|
879
953
|
|
|
880
954
|
// src/capture/hooks/utils.ts
|
|
881
955
|
var import_child_process3 = require("child_process");
|
|
@@ -889,11 +963,11 @@ function isCommandInstalled(cmd) {
|
|
|
889
963
|
}
|
|
890
964
|
|
|
891
965
|
// src/capture/hooks/claude-code.ts
|
|
892
|
-
var CLAUDE_SETTINGS_PATH =
|
|
966
|
+
var CLAUDE_SETTINGS_PATH = import_path6.default.join(import_os3.default.homedir(), ".claude", "settings.json");
|
|
893
967
|
function readSettings() {
|
|
894
|
-
if (!
|
|
968
|
+
if (!import_fs9.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
|
|
895
969
|
try {
|
|
896
|
-
return JSON.parse(
|
|
970
|
+
return JSON.parse(import_fs9.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
897
971
|
} catch {
|
|
898
972
|
return {};
|
|
899
973
|
}
|
|
@@ -908,8 +982,8 @@ var claudeCodeIntegration = {
|
|
|
908
982
|
const postToolUse = settings.hooks?.PostToolUse ?? [];
|
|
909
983
|
return postToolUse.some((entry) => {
|
|
910
984
|
const hooks = entry.hooks ?? [];
|
|
911
|
-
return hooks.some((
|
|
912
|
-
const hook =
|
|
985
|
+
return hooks.some((hookItem) => {
|
|
986
|
+
const hook = hookItem;
|
|
913
987
|
return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
|
|
914
988
|
});
|
|
915
989
|
});
|
|
@@ -928,8 +1002,8 @@ var claudeCodeIntegration = {
|
|
|
928
1002
|
}
|
|
929
1003
|
]
|
|
930
1004
|
};
|
|
931
|
-
|
|
932
|
-
|
|
1005
|
+
import_fs9.default.mkdirSync(import_path6.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
|
|
1006
|
+
import_fs9.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
933
1007
|
}
|
|
934
1008
|
};
|
|
935
1009
|
|
|
@@ -941,19 +1015,19 @@ var AI_HOOK_INTEGRATIONS = {
|
|
|
941
1015
|
// src/cli/init.ts
|
|
942
1016
|
function detectShell() {
|
|
943
1017
|
const shell = process.env.SHELL ?? "";
|
|
944
|
-
if (shell.includes("zsh")) return { name: "zsh", rcFile:
|
|
945
|
-
if (shell.includes("bash")) return { name: "bash", rcFile:
|
|
1018
|
+
if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path7.default.join(import_os4.default.homedir(), ".zshrc") };
|
|
1019
|
+
if (shell.includes("bash")) return { name: "bash", rcFile: import_path7.default.join(import_os4.default.homedir(), ".bashrc") };
|
|
946
1020
|
return null;
|
|
947
1021
|
}
|
|
948
1022
|
function registerInit(program2) {
|
|
949
1023
|
program2.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
|
|
950
1024
|
log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
|
|
951
|
-
if (!
|
|
952
|
-
|
|
1025
|
+
if (!import_fs10.default.existsSync(WORKSCRIBE_DIR)) {
|
|
1026
|
+
import_fs10.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
|
|
953
1027
|
log.success(`Created ${WORKSCRIBE_DIR}`);
|
|
954
1028
|
} else {
|
|
955
1029
|
try {
|
|
956
|
-
|
|
1030
|
+
import_fs10.default.chmodSync(WORKSCRIBE_DIR, 448);
|
|
957
1031
|
} catch {
|
|
958
1032
|
}
|
|
959
1033
|
}
|
|
@@ -964,12 +1038,12 @@ function registerInit(program2) {
|
|
|
964
1038
|
log.warn("Could not detect shell. Supported: zsh, bash");
|
|
965
1039
|
log.plain(`Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")} or ${import_chalk2.default.cyan("shell/hook.bash")}`);
|
|
966
1040
|
} else {
|
|
967
|
-
const hookFile =
|
|
968
|
-
if (!
|
|
1041
|
+
const hookFile = import_path7.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
|
|
1042
|
+
if (!import_fs10.default.existsSync(hookFile)) {
|
|
969
1043
|
log.warn(`Hook template not found at ${hookFile}`);
|
|
970
1044
|
} else {
|
|
971
|
-
const hookContent =
|
|
972
|
-
if (!
|
|
1045
|
+
const hookContent = import_fs10.default.readFileSync(hookFile, "utf-8");
|
|
1046
|
+
if (!import_fs10.default.existsSync(shell.rcFile)) import_fs10.default.writeFileSync(shell.rcFile, "");
|
|
973
1047
|
if (!isHookInstalled(shell.rcFile)) {
|
|
974
1048
|
installHook(shell.rcFile, hookContent);
|
|
975
1049
|
log.success(`Shell hook installed in ${shell.rcFile}`);
|
|
@@ -991,21 +1065,29 @@ function registerInit(program2) {
|
|
|
991
1065
|
}
|
|
992
1066
|
}
|
|
993
1067
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1068
|
+
const installedTools = loadAIToolRegistry().filter(
|
|
1069
|
+
(entry) => entry.hook !== null && AI_HOOK_INTEGRATIONS[entry.hook]?.isInstalled()
|
|
1070
|
+
);
|
|
1071
|
+
if (installedTools.length === 0) {
|
|
1072
|
+
log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
|
|
1073
|
+
} else {
|
|
1074
|
+
for (const tool of installedTools) {
|
|
1075
|
+
const integration = AI_HOOK_INTEGRATIONS[tool.hook];
|
|
1076
|
+
if (integration.isHookInstalled()) {
|
|
1077
|
+
log.info(`${integration.displayName} session hook already installed`);
|
|
1078
|
+
} else {
|
|
1079
|
+
const { install } = await (0, import_prompts.default)({
|
|
1080
|
+
type: "confirm",
|
|
1081
|
+
name: "install",
|
|
1082
|
+
message: `${integration.displayName} detected. Install session capture hook?`,
|
|
1083
|
+
initial: false
|
|
1084
|
+
});
|
|
1085
|
+
if (install) {
|
|
1086
|
+
integration.install();
|
|
1087
|
+
log.success(`${integration.displayName} session hook installed`);
|
|
1088
|
+
} else {
|
|
1089
|
+
log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
|
|
1090
|
+
}
|
|
1009
1091
|
}
|
|
1010
1092
|
}
|
|
1011
1093
|
}
|
|
@@ -1057,7 +1139,7 @@ function registerInit(program2) {
|
|
|
1057
1139
|
log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
|
|
1058
1140
|
}
|
|
1059
1141
|
}
|
|
1060
|
-
if (!
|
|
1142
|
+
if (!import_fs10.default.existsSync(CONFIG_PATH)) {
|
|
1061
1143
|
writeConfig(config);
|
|
1062
1144
|
log.success("Config file created");
|
|
1063
1145
|
}
|
|
@@ -1076,7 +1158,7 @@ function registerInit(program2) {
|
|
|
1076
1158
|
}
|
|
1077
1159
|
|
|
1078
1160
|
// src/cli/status.ts
|
|
1079
|
-
var
|
|
1161
|
+
var import_fs11 = __toESM(require("fs"));
|
|
1080
1162
|
var import_chalk3 = __toESM(require("chalk"));
|
|
1081
1163
|
|
|
1082
1164
|
// src/utils/format.ts
|
|
@@ -1134,7 +1216,7 @@ function registerStatus(program2) {
|
|
|
1134
1216
|
const total = getEventCount();
|
|
1135
1217
|
const todayEvents = getEventsForDate(todayDate());
|
|
1136
1218
|
const last = getLastEvent();
|
|
1137
|
-
const isPaused =
|
|
1219
|
+
const isPaused = import_fs11.default.existsSync(PAUSE_FLAG_PATH);
|
|
1138
1220
|
log.plain("");
|
|
1139
1221
|
log.plain(import_chalk3.default.bold(" Workscribe Status"));
|
|
1140
1222
|
log.plain("");
|
|
@@ -1182,7 +1264,9 @@ var CATEGORY_COLORS = {
|
|
|
1182
1264
|
file_ops: import_chalk4.default.yellow,
|
|
1183
1265
|
other: import_chalk4.default.gray
|
|
1184
1266
|
};
|
|
1185
|
-
var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
|
|
1267
|
+
var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
|
|
1268
|
+
loadAIToolRegistry().map((entry) => [entry.tool, entry.displayName])
|
|
1269
|
+
);
|
|
1186
1270
|
function registerEvents(program2) {
|
|
1187
1271
|
program2.command("events").description("List captured events").option("--date <YYYY-MM-DD>", "date to list events for (default: today)").option("--limit <n>", "max events to show", parseInt).action((opts) => {
|
|
1188
1272
|
let date = todayDate();
|
|
@@ -1273,18 +1357,18 @@ function getOrBuildSessions(date) {
|
|
|
1273
1357
|
const existing = getSessionsForDate(date);
|
|
1274
1358
|
const events = getEventsForDate(date);
|
|
1275
1359
|
if (events.length === 0) return [];
|
|
1276
|
-
const cachedEventCount = existing.reduce((sum,
|
|
1360
|
+
const cachedEventCount = existing.reduce((sum, sessionRow) => sum + sessionRow.event_count, 0);
|
|
1277
1361
|
if (existing.length > 0 && cachedEventCount === events.length) {
|
|
1278
|
-
return existing.map((
|
|
1279
|
-
id:
|
|
1280
|
-
date:
|
|
1281
|
-
startedAt:
|
|
1282
|
-
endedAt:
|
|
1283
|
-
repoName:
|
|
1284
|
-
durationMinutes: minutesBetween(
|
|
1285
|
-
eventCount:
|
|
1286
|
-
summary:
|
|
1287
|
-
events: getEventsBySessionId(
|
|
1362
|
+
return existing.map((sessionRow) => ({
|
|
1363
|
+
id: sessionRow.id,
|
|
1364
|
+
date: sessionRow.date,
|
|
1365
|
+
startedAt: sessionRow.started_at,
|
|
1366
|
+
endedAt: sessionRow.ended_at,
|
|
1367
|
+
repoName: sessionRow.repo_name,
|
|
1368
|
+
durationMinutes: minutesBetween(sessionRow.started_at, sessionRow.ended_at),
|
|
1369
|
+
eventCount: sessionRow.event_count,
|
|
1370
|
+
summary: sessionRow.summary,
|
|
1371
|
+
events: getEventsBySessionId(sessionRow.id)
|
|
1288
1372
|
}));
|
|
1289
1373
|
}
|
|
1290
1374
|
const savedSummaries = /* @__PURE__ */ new Map();
|
|
@@ -1370,9 +1454,9 @@ function registerSessions(program2) {
|
|
|
1370
1454
|
|
|
1371
1455
|
// src/cli/summary.ts
|
|
1372
1456
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1373
|
-
var
|
|
1374
|
-
var
|
|
1375
|
-
var
|
|
1457
|
+
var import_fs12 = __toESM(require("fs"));
|
|
1458
|
+
var import_os5 = __toESM(require("os"));
|
|
1459
|
+
var import_path8 = __toESM(require("path"));
|
|
1376
1460
|
|
|
1377
1461
|
// src/ai/prompt.ts
|
|
1378
1462
|
var SYSTEM_PROMPT = `You write short developer work summaries. Here is an example:
|
|
@@ -1413,9 +1497,14 @@ function buildUserMessage(payload) {
|
|
|
1413
1497
|
const counts = /* @__PURE__ */ new Map();
|
|
1414
1498
|
for (const event of session.events) {
|
|
1415
1499
|
if (EXCLUDED.has(event.category)) continue;
|
|
1416
|
-
const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
|
|
1417
1500
|
const failed = event.exitCode !== null && event.exitCode !== 0 ? " [failed]" : "";
|
|
1418
|
-
|
|
1501
|
+
let key;
|
|
1502
|
+
if (event.hint) {
|
|
1503
|
+
key = `other [${event.hint}]${failed}`;
|
|
1504
|
+
} else {
|
|
1505
|
+
const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
|
|
1506
|
+
key = `${event.category}${meta}${failed}`;
|
|
1507
|
+
}
|
|
1419
1508
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1420
1509
|
}
|
|
1421
1510
|
for (const [key, count] of counts) {
|
|
@@ -1470,9 +1559,14 @@ function buildWeeklyMessage(payload) {
|
|
|
1470
1559
|
const counts = /* @__PURE__ */ new Map();
|
|
1471
1560
|
for (const e of s.events) {
|
|
1472
1561
|
if (EXCLUDED.has(e.category)) continue;
|
|
1473
|
-
const meta = Object.keys(e.metadata).length > 0 ? ` ${JSON.stringify(e.metadata)}` : "";
|
|
1474
1562
|
const failed = e.exitCode !== null && e.exitCode !== 0 ? " [failed]" : "";
|
|
1475
|
-
|
|
1563
|
+
let key;
|
|
1564
|
+
if (e.hint) {
|
|
1565
|
+
key = `other [${e.hint}]${failed}`;
|
|
1566
|
+
} else {
|
|
1567
|
+
const meta = Object.keys(e.metadata).length > 0 ? ` ${JSON.stringify(e.metadata)}` : "";
|
|
1568
|
+
key = `${e.category}${meta}${failed}`;
|
|
1569
|
+
}
|
|
1476
1570
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1477
1571
|
}
|
|
1478
1572
|
const eventLine = [...counts.entries()].map(([k, n]) => `${n > 1 ? `${n}x ` : ""}${k}`).join(", ");
|
|
@@ -1528,7 +1622,7 @@ var AnthropicProvider = class {
|
|
|
1528
1622
|
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
1529
1623
|
}
|
|
1530
1624
|
const data = await response.json();
|
|
1531
|
-
return data.content.find((
|
|
1625
|
+
return data.content.find((block) => block.type === "text")?.text ?? "";
|
|
1532
1626
|
}
|
|
1533
1627
|
async summarizeWeek(payload) {
|
|
1534
1628
|
const response = await fetchWithTimeout("https://api.anthropic.com/v1/messages", {
|
|
@@ -1550,7 +1644,7 @@ var AnthropicProvider = class {
|
|
|
1550
1644
|
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
1551
1645
|
}
|
|
1552
1646
|
const data = await response.json();
|
|
1553
|
-
return data.content.find((
|
|
1647
|
+
return data.content.find((block) => block.type === "text")?.text ?? "";
|
|
1554
1648
|
}
|
|
1555
1649
|
};
|
|
1556
1650
|
|
|
@@ -1736,7 +1830,7 @@ function isSlackWebhookUrl(url) {
|
|
|
1736
1830
|
|
|
1737
1831
|
// src/cli/summary.ts
|
|
1738
1832
|
function parseBullets(text) {
|
|
1739
|
-
return text.split("\n").map((
|
|
1833
|
+
return text.split("\n").map((line) => line.replace(/^[•\-]\s*/, "").trim()).filter(Boolean);
|
|
1740
1834
|
}
|
|
1741
1835
|
function eventRowToPayload(row) {
|
|
1742
1836
|
let metadata = {};
|
|
@@ -1744,12 +1838,18 @@ function eventRowToPayload(row) {
|
|
|
1744
1838
|
if (row.metadata) metadata = JSON.parse(row.metadata);
|
|
1745
1839
|
} catch {
|
|
1746
1840
|
}
|
|
1747
|
-
|
|
1841
|
+
const payload = {
|
|
1748
1842
|
category: row.category,
|
|
1749
1843
|
metadata,
|
|
1750
1844
|
exitCode: row.exit_code,
|
|
1751
1845
|
durationMs: row.duration_ms
|
|
1752
1846
|
};
|
|
1847
|
+
if (row.category === "other" && typeof metadata.hint === "string") {
|
|
1848
|
+
payload.hint = metadata.hint;
|
|
1849
|
+
const { hint: _h, tool: _t, ...rest } = metadata;
|
|
1850
|
+
payload.metadata = rest;
|
|
1851
|
+
}
|
|
1852
|
+
return payload;
|
|
1753
1853
|
}
|
|
1754
1854
|
function sessionToPayload(s, date) {
|
|
1755
1855
|
return {
|
|
@@ -1767,7 +1867,7 @@ async function handleWebhook(opts, config, summary) {
|
|
|
1767
1867
|
if (webhookUrl) {
|
|
1768
1868
|
try {
|
|
1769
1869
|
log.info("Posting to webhook\u2026");
|
|
1770
|
-
const username =
|
|
1870
|
+
const username = import_os5.default.userInfo().username;
|
|
1771
1871
|
const body = isSlackWebhookUrl(webhookUrl) ? formatForSlack(summary, username) : summary;
|
|
1772
1872
|
await postToWebhook(webhookUrl, body);
|
|
1773
1873
|
log.success("Posted.");
|
|
@@ -1796,7 +1896,7 @@ function registerSummary(program2) {
|
|
|
1796
1896
|
for (const day of days) {
|
|
1797
1897
|
const sessions2 = getOrBuildSessions(day);
|
|
1798
1898
|
if (sessions2.length > 0) {
|
|
1799
|
-
weeklyDays.push({ date: day, sessions: sessions2.map((
|
|
1899
|
+
weeklyDays.push({ date: day, sessions: sessions2.map((session) => sessionToPayload(session, day)) });
|
|
1800
1900
|
}
|
|
1801
1901
|
}
|
|
1802
1902
|
if (weeklyDays.length === 0) {
|
|
@@ -1804,9 +1904,9 @@ function registerSummary(program2) {
|
|
|
1804
1904
|
return;
|
|
1805
1905
|
}
|
|
1806
1906
|
const cacheKey = isoWeekKey(today);
|
|
1807
|
-
const cacheFile =
|
|
1808
|
-
if (!opts.fresh &&
|
|
1809
|
-
const cached =
|
|
1907
|
+
const cacheFile = import_path8.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
|
|
1908
|
+
if (!opts.fresh && import_fs12.default.existsSync(cacheFile)) {
|
|
1909
|
+
const cached = import_fs12.default.readFileSync(cacheFile, "utf-8");
|
|
1810
1910
|
log.plain("");
|
|
1811
1911
|
log.plain(cached);
|
|
1812
1912
|
log.plain("");
|
|
@@ -1818,12 +1918,12 @@ function registerSummary(program2) {
|
|
|
1818
1918
|
log.plain("");
|
|
1819
1919
|
log.info(`Generating weekly summary via ${import_chalk6.default.cyan(config.ai.provider)}\u2026`);
|
|
1820
1920
|
try {
|
|
1821
|
-
const summary = provider.summarizeWeek ? await provider.summarizeWeek(payload) : await provider.summarize({ date: today, sessions: weeklyDays.flatMap((
|
|
1921
|
+
const summary = provider.summarizeWeek ? await provider.summarizeWeek(payload) : await provider.summarize({ date: today, sessions: weeklyDays.flatMap((day) => day.sessions) });
|
|
1822
1922
|
log.plain("");
|
|
1823
1923
|
log.plain(summary);
|
|
1824
1924
|
log.plain("");
|
|
1825
|
-
|
|
1826
|
-
|
|
1925
|
+
import_fs12.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
|
|
1926
|
+
import_fs12.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
|
|
1827
1927
|
await handleWebhook(opts, config, summary);
|
|
1828
1928
|
} catch (e) {
|
|
1829
1929
|
log.error(`Weekly summary failed [${config.ai.provider}]: ${e.message}`);
|
|
@@ -1847,16 +1947,16 @@ function registerSummary(program2) {
|
|
|
1847
1947
|
}
|
|
1848
1948
|
const jsonMode = config.summary?.format === "json";
|
|
1849
1949
|
if (!opts.fresh) {
|
|
1850
|
-
const allCached = sessions.every((
|
|
1950
|
+
const allCached = sessions.every((session) => session.summary);
|
|
1851
1951
|
if (allCached) {
|
|
1852
|
-
const fullSummary2 = sessions.map((
|
|
1952
|
+
const fullSummary2 = sessions.map((session) => session.summary).join("\n\n");
|
|
1853
1953
|
log.plain("");
|
|
1854
1954
|
if (jsonMode) {
|
|
1855
|
-
const jsonOut = sessions.map((
|
|
1856
|
-
project:
|
|
1857
|
-
branch:
|
|
1858
|
-
durationMinutes:
|
|
1859
|
-
bullets: parseBullets(
|
|
1955
|
+
const jsonOut = sessions.map((session) => ({
|
|
1956
|
+
project: session.repoName,
|
|
1957
|
+
branch: session.events[0]?.branch ?? null,
|
|
1958
|
+
durationMinutes: session.durationMinutes,
|
|
1959
|
+
bullets: parseBullets(session.summary.replace(/^## .+\n/, ""))
|
|
1860
1960
|
}));
|
|
1861
1961
|
log.plain(JSON.stringify({ date, sessions: jsonOut }, null, 2));
|
|
1862
1962
|
} else {
|
|
@@ -1910,15 +2010,26 @@ ${bullets.trim()}`;
|
|
|
1910
2010
|
}
|
|
1911
2011
|
|
|
1912
2012
|
// src/cli/export.ts
|
|
1913
|
-
var
|
|
1914
|
-
var
|
|
2013
|
+
var import_fs13 = __toESM(require("fs"));
|
|
2014
|
+
var import_path9 = __toESM(require("path"));
|
|
1915
2015
|
function eventRowToPayload2(row) {
|
|
1916
2016
|
let metadata = {};
|
|
1917
2017
|
try {
|
|
1918
2018
|
if (row.metadata) metadata = JSON.parse(row.metadata);
|
|
1919
2019
|
} catch {
|
|
1920
2020
|
}
|
|
1921
|
-
|
|
2021
|
+
const payload = {
|
|
2022
|
+
category: row.category,
|
|
2023
|
+
metadata,
|
|
2024
|
+
exitCode: row.exit_code,
|
|
2025
|
+
durationMs: row.duration_ms
|
|
2026
|
+
};
|
|
2027
|
+
if (row.category === "other" && typeof metadata.hint === "string") {
|
|
2028
|
+
payload.hint = metadata.hint;
|
|
2029
|
+
const { hint: _h, tool: _t, ...rest } = metadata;
|
|
2030
|
+
payload.metadata = rest;
|
|
2031
|
+
}
|
|
2032
|
+
return payload;
|
|
1922
2033
|
}
|
|
1923
2034
|
function registerExport(program2) {
|
|
1924
2035
|
program2.command("export").description("Export work summary as a markdown file").option("--date <YYYY-MM-DD>", "date to export (default: today)").option("--output <path>", "output file path (default: ~/.workscribe/exports/)").action(async (opts) => {
|
|
@@ -1944,21 +2055,21 @@ function registerExport(program2) {
|
|
|
1944
2055
|
log.error(e.message);
|
|
1945
2056
|
process.exit(1);
|
|
1946
2057
|
}
|
|
1947
|
-
const sessionPayloads = sessions.map((
|
|
2058
|
+
const sessionPayloads = sessions.map((session) => ({
|
|
1948
2059
|
date,
|
|
1949
|
-
project:
|
|
1950
|
-
branch:
|
|
1951
|
-
startTime: formatDate(
|
|
1952
|
-
endTime: formatDate(
|
|
1953
|
-
durationMinutes:
|
|
1954
|
-
events:
|
|
2060
|
+
project: session.repoName,
|
|
2061
|
+
branch: session.events[0]?.branch ?? null,
|
|
2062
|
+
startTime: formatDate(session.startedAt),
|
|
2063
|
+
endTime: formatDate(session.endedAt),
|
|
2064
|
+
durationMinutes: session.durationMinutes,
|
|
2065
|
+
events: session.events.map(eventRowToPayload2)
|
|
1955
2066
|
}));
|
|
1956
2067
|
const payload = { date, sessions: sessionPayloads };
|
|
1957
2068
|
log.info(`Generating summary via ${config.ai.provider}\u2026`);
|
|
1958
2069
|
const summary = await provider.summarize(payload);
|
|
1959
|
-
const outputPath = opts.output ??
|
|
1960
|
-
if (!
|
|
1961
|
-
|
|
2070
|
+
const outputPath = opts.output ?? import_path9.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
|
|
2071
|
+
if (!import_fs13.default.existsSync(EXPORTS_DIR)) import_fs13.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
|
|
2072
|
+
import_fs13.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
|
|
1962
2073
|
|
|
1963
2074
|
${summary}
|
|
1964
2075
|
`, { encoding: "utf-8", mode: 384 });
|
|
@@ -2030,22 +2141,22 @@ function printConfig(obj, indent) {
|
|
|
2030
2141
|
}
|
|
2031
2142
|
|
|
2032
2143
|
// src/cli/pause.ts
|
|
2033
|
-
var
|
|
2144
|
+
var import_fs14 = __toESM(require("fs"));
|
|
2034
2145
|
function registerPause(program2) {
|
|
2035
2146
|
program2.command("pause").description("Pause capture temporarily").action(() => {
|
|
2036
|
-
if (
|
|
2147
|
+
if (import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2037
2148
|
log.info("Capture is already paused. Run workscribe resume to restart.");
|
|
2038
2149
|
return;
|
|
2039
2150
|
}
|
|
2040
|
-
|
|
2151
|
+
import_fs14.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
|
|
2041
2152
|
log.success("Capture paused. Run workscribe resume to restart.");
|
|
2042
2153
|
});
|
|
2043
2154
|
program2.command("resume").description("Resume capture after a pause").action(() => {
|
|
2044
|
-
if (!
|
|
2155
|
+
if (!import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2045
2156
|
log.info("Capture is not paused.");
|
|
2046
2157
|
return;
|
|
2047
2158
|
}
|
|
2048
|
-
|
|
2159
|
+
import_fs14.default.rmSync(PAUSE_FLAG_PATH);
|
|
2049
2160
|
log.success("Capture resumed.");
|
|
2050
2161
|
});
|
|
2051
2162
|
}
|
|
@@ -2130,18 +2241,18 @@ function registerClear(program2) {
|
|
|
2130
2241
|
}
|
|
2131
2242
|
|
|
2132
2243
|
// src/cli/uninstall.ts
|
|
2133
|
-
var
|
|
2134
|
-
var
|
|
2135
|
-
var
|
|
2244
|
+
var import_fs15 = __toESM(require("fs"));
|
|
2245
|
+
var import_path10 = __toESM(require("path"));
|
|
2246
|
+
var import_os6 = __toESM(require("os"));
|
|
2136
2247
|
var import_prompts3 = __toESM(require("prompts"));
|
|
2137
2248
|
var import_chalk9 = __toESM(require("chalk"));
|
|
2138
2249
|
function detectShellRcFiles() {
|
|
2139
2250
|
const candidates = [
|
|
2140
|
-
{ name: "zsh", rcFile:
|
|
2141
|
-
{ name: "bash", rcFile:
|
|
2142
|
-
{ name: "bash", rcFile:
|
|
2251
|
+
{ name: "zsh", rcFile: import_path10.default.join(import_os6.default.homedir(), ".zshrc") },
|
|
2252
|
+
{ name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bashrc") },
|
|
2253
|
+
{ name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bash_profile") }
|
|
2143
2254
|
];
|
|
2144
|
-
return candidates.filter((
|
|
2255
|
+
return candidates.filter((candidate) => import_fs15.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
|
|
2145
2256
|
}
|
|
2146
2257
|
function registerUninstall(program2) {
|
|
2147
2258
|
program2.command("uninstall").description("Remove shell hooks and optionally delete all Workscribe data").option("--hooks-only", "remove shell hooks only, skip data deletion prompt (used by npm preuninstall)").action(async (opts) => {
|
|
@@ -2160,7 +2271,7 @@ function registerUninstall(program2) {
|
|
|
2160
2271
|
}
|
|
2161
2272
|
}
|
|
2162
2273
|
if (opts.hooksOnly) return;
|
|
2163
|
-
if (
|
|
2274
|
+
if (import_fs15.default.existsSync(WORKSCRIBE_DIR)) {
|
|
2164
2275
|
log.plain("");
|
|
2165
2276
|
const { deleteData } = await (0, import_prompts3.default)({
|
|
2166
2277
|
type: "confirm",
|
|
@@ -2169,7 +2280,7 @@ function registerUninstall(program2) {
|
|
|
2169
2280
|
initial: false
|
|
2170
2281
|
});
|
|
2171
2282
|
if (deleteData) {
|
|
2172
|
-
|
|
2283
|
+
import_fs15.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
|
|
2173
2284
|
log.success(`Deleted ${WORKSCRIBE_DIR}`);
|
|
2174
2285
|
} else {
|
|
2175
2286
|
log.info(`Data kept at ${WORKSCRIBE_DIR}`);
|
|
@@ -2183,7 +2294,7 @@ function registerUninstall(program2) {
|
|
|
2183
2294
|
|
|
2184
2295
|
// src/index.ts
|
|
2185
2296
|
var program = new import_commander.Command();
|
|
2186
|
-
program.name("workscribe").description("Automatic developer work journal").version("1.
|
|
2297
|
+
program.name("workscribe").description("Automatic developer work journal").version("1.9.0");
|
|
2187
2298
|
registerCapture(program);
|
|
2188
2299
|
registerHook(program);
|
|
2189
2300
|
registerInit(program);
|